diff --git a/test/server_test.go b/test/server_test.go index 3e3e4ff..80fb089 100644 --- a/test/server_test.go +++ b/test/server_test.go @@ -191,3 +191,54 @@ func TestCreateServerWithAutoRenew(t *ltesting.T) { t.Log("Result: ", server) t.Log("PASS") } + +func TestAttachVolumeFailure(t *ltesting.T) { + vngcloud := validSdkConfig() + opt := lscomputeSvcV2.NewAttachBlockVolumeRequest("this-is-fake-server-id", "vol-a9484a51-243b-4217-81d4-9f55a7ad426d") + sdkerr := vngcloud.VServerGateway().V2().ComputeService().AttachBlockVolume(opt) + + if sdkerr == nil { + t.Fatalf("Expect error but got nil") + } + + t.Log("Result: ", sdkerr) + t.Log("PASS") +} + +func TestAttachVolumeSuccess(t *ltesting.T) { + vngcloud := validSdkConfig() + opt := lscomputeSvcV2.NewAttachBlockVolumeRequest("ins-6f6b7238-1d57-4c4b-a683-225454a2e168", "vol-a9484a51-243b-4217-81d4-9f55a7ad426d") + sdkerr := vngcloud.VServerGateway().V2().ComputeService().AttachBlockVolume(opt) + + if sdkerr != nil { + t.Fatalf("Expect nil but got %v", sdkerr) + } + + t.Log("PASS") +} + +func TestDetachVolumeSuccess(t *ltesting.T) { + vngcloud := validSdkConfig() + opt := lscomputeSvcV2.NewDetachBlockVolumeRequest("undefined", "vol-a9484a51-243b-4217-81d4-9f55a7ad426d") + sdkerr := vngcloud.VServerGateway().V2().ComputeService().DetachBlockVolume(opt) + + if sdkerr != nil { + t.Fatalf("Expect error but got nil") + } + + t.Log("Result: ", sdkerr) + t.Log("PASS") +} + +func TestDetachVolumeFailure(t *ltesting.T) { + vngcloud := validSdkConfig() + opt := lscomputeSvcV2.NewDetachBlockVolumeRequest("ins-6f6b7238-1d57-4c4b-a683-225454a2e168", "this-is-fake-volume-id") + sdkerr := vngcloud.VServerGateway().V2().ComputeService().DetachBlockVolume(opt) + + if sdkerr == nil { + t.Fatalf("Expect error but got nil") + } + + t.Log("Result: ", sdkerr) + t.Log("PASS") +} diff --git a/vngcloud/sdk_error/error_codes.go b/vngcloud/sdk_error/error_codes.go index 50c76ec..a2426c3 100644 --- a/vngcloud/sdk_error/error_codes.go +++ b/vngcloud/sdk_error/error_codes.go @@ -50,10 +50,14 @@ const ( // vServer volume const ( - EcVServerVolumeTypeNotFound = ErrorCode("VngCloudVServerVolumeTypeNotFound") - EcVServerVolumeNameNotValid = ErrorCode("VngCloudVServerVolumeNameNotValid") - EcVServerVolumeSizeOutOfRange = ErrorCode("VngCloudVServerVolumeSizeOutOfRange") - EcVServerVolumeNotFound = ErrorCode("VngCloudVServerVolumeNotFound") + EcVServerVolumeTypeNotFound = ErrorCode("VngCloudVServerVolumeTypeNotFound") + EcVServerVolumeNameNotValid = ErrorCode("VngCloudVServerVolumeNameNotValid") + EcVServerVolumeSizeOutOfRange = ErrorCode("VngCloudVServerVolumeSizeOutOfRange") + EcVServerVolumeNotFound = ErrorCode("VngCloudVServerVolumeNotFound") + EcVServerVolumeAvailable = ErrorCode("VngCloudVServerVolumeAvailable") + EcVServerVolumeAlreadyAttached = ErrorCode("VngCloudVServerVolumeAlreadyAttached") + EcVServerVolumeAlreadyAttachedThisServer = ErrorCode("VngCloudVServerVolumeAlreadyAttachedThisServer") + EcVServerVolumeInProcess = ErrorCode("VngCloudVServerVolumeInProcess") ) // Billing @@ -69,6 +73,7 @@ const ( EcVServerServerExceedQuota = ErrorCode("VngCloudVServerServerExceedQuota") EcVServerServerDeleteDeletingServer = ErrorCode("VngCloudVServerServerDeleteDeletingServer") EcVServerServerDeleteBillingServer = ErrorCode("VngCloudVServerServerDeleteBillingServer") + EcVServerServerVolumeAttachQuotaExceeded = ErrorCode("VngCloudVServerServerVolumeAttachQuotaExceeded") EcVServerCreateBillingPaymentMethodNotAllowed = ErrorCode("VngCloudVServerCreateBillingPaymentMethodNotAllowed") ) diff --git a/vngcloud/sdk_error/server.go b/vngcloud/sdk_error/server.go index 2b927e6..90275a5 100644 --- a/vngcloud/sdk_error/server.go +++ b/vngcloud/sdk_error/server.go @@ -3,12 +3,13 @@ package sdk_error import lstr "strings" const ( - patternServerNotFound = "cannot get server with id" // "Cannot get volume type with id vtype-6790f903-38d2-454d-919e-5b49184b5927" - patternServerCreating = "cannot delete server with status creating" // "Server is creating" - patternServerExceedQuota = "exceeded vm quota" // "The number of servers exceeds the quota" - patternServerDeleting = "cannot delete server with status deleting" // "Server is deleting" - patternServerBilling = "cannot delete server with status creating-billing" - patternBillingPaymentMethodNotAllowed = "payment method is not allowed for the user" + patternServerNotFound = "cannot get server with id" // "Cannot get volume type with id vtype-6790f903-38d2-454d-919e-5b49184b5927" + patternServerCreating = "cannot delete server with status creating" // "Server is creating" + patternServerExceedQuota = "exceeded vm quota" // "The number of servers exceeds the quota" + patternServerDeleting = "cannot delete server with status deleting" // "Server is deleting" + patternServerBilling = "cannot delete server with status creating-billing" + patternBillingPaymentMethodNotAllowed = "payment method is not allowed for the user" + patternServerAttachVolumeQuotaExceeded = "exceeded volume_per_server quota" ) func WithErrorServerNotFound(perrResp IErrorRespone) func(sdkError ISdkError) { @@ -100,3 +101,18 @@ func WithErrorServerCreateBillingPaymentMethodNotAllowed(perrResp IErrorRespone) } } } + +func WithErrorServerAttachVolumeQuotaExceeded(perrResp IErrorRespone) func(sdkError ISdkError) { + return func(sdkError ISdkError) { + if perrResp == nil { + return + } + + errMsg := perrResp.GetMessage() + if lstr.Contains(lstr.ToLower(lstr.TrimSpace(errMsg)), patternServerAttachVolumeQuotaExceeded) { + sdkError.WithErrorCode(EcVServerServerVolumeAttachQuotaExceeded). + WithMessage(errMsg). + WithErrors(perrResp.GetError()) + } + } +} diff --git a/vngcloud/sdk_error/volume.go b/vngcloud/sdk_error/volume.go index d7520a8..9e80203 100644 --- a/vngcloud/sdk_error/volume.go +++ b/vngcloud/sdk_error/volume.go @@ -6,10 +6,14 @@ import ( ) const ( - patternVolumeTypeNotFound = "cannot get volume type with id" // "Cannot get volume type with id vtype-6790f903-38d2-454d-919e-5b49184b5927" - patternVolumeNameNotValid = "only letters (a-z, a-z, 0-9, '.', '@', '_', '-', space) are allowed. your input data length must be between 5 and 50" // "Volume name is not valid" - patternVolumeSizeOutOfRange = "field volume_size must from" - patternVolumeNotFound = `volume with id [^.]+ is not found` + patternVolumeTypeNotFound = "cannot get volume type with id" // "Cannot get volume type with id vtype-6790f903-38d2-454d-919e-5b49184b5927" + patternVolumeNameNotValid = "only letters (a-z, a-z, 0-9, '.', '@', '_', '-', space) are allowed. your input data length must be between 5 and 50" // "Volume name is not valid" + patternVolumeSizeOutOfRange = "field volume_size must from" + patternVolumeNotFound = `volume with id [^.]+ is not found` + patternVolumeAvailable = "this volume is available" + patternVolumeAlreadyAttached = "already attached to instance" + patternVolumeAlreadyAttachedThisServer = "this volume has been attached" + patternVolumeInProcess = "is in-process" ) var ( @@ -75,3 +79,63 @@ func WithErrorVolumeNotFound(perrResp IErrorRespone) func(sdkError ISdkError) { } } } + +func WithErrorVolumeAvailable(perrResp IErrorRespone) func(sdkError ISdkError) { + return func(sdkError ISdkError) { + if perrResp == nil { + return + } + + errMsg := perrResp.GetMessage() + if lstr.Contains(lstr.ToLower(lstr.TrimSpace(errMsg)), patternVolumeAvailable) { + sdkError.WithErrorCode(EcVServerVolumeAvailable). + WithMessage(errMsg). + WithErrors(perrResp.GetError()) + } + } +} + +func WithErrorVolumeAlreadyAttached(perrResp IErrorRespone) func(sdkError ISdkError) { + return func(sdkError ISdkError) { + if perrResp == nil { + return + } + + errMsg := perrResp.GetMessage() + if lstr.Contains(lstr.ToLower(lstr.TrimSpace(errMsg)), patternVolumeAlreadyAttached) { + sdkError.WithErrorCode(EcVServerVolumeAlreadyAttached). + WithMessage(errMsg). + WithErrors(perrResp.GetError()) + } + } +} + +func WithErrorVolumeAlreadyAttachedThisServer(perrResp IErrorRespone) func(sdkError ISdkError) { + return func(sdkError ISdkError) { + if perrResp == nil { + return + } + + errMsg := perrResp.GetMessage() + if lstr.Contains(lstr.ToLower(lstr.TrimSpace(errMsg)), patternVolumeAlreadyAttachedThisServer) { + sdkError.WithErrorCode(EcVServerVolumeAlreadyAttachedThisServer). + WithMessage(errMsg). + WithErrors(perrResp.GetError()) + } + } +} + +func WithErrorVolumeInProcess(perrResp IErrorRespone) func(sdkError ISdkError) { + return func(sdkError ISdkError) { + if perrResp == nil { + return + } + + errMsg := perrResp.GetMessage() + if lstr.Contains(lstr.ToLower(lstr.TrimSpace(errMsg)), patternVolumeInProcess) { + sdkError.WithErrorCode(EcVServerVolumeInProcess). + WithMessage(errMsg). + WithErrors(perrResp.GetError()) + } + } +} diff --git a/vngcloud/services/compute/v2/common.go b/vngcloud/services/common/server.go similarity index 88% rename from vngcloud/services/compute/v2/common.go rename to vngcloud/services/common/server.go index e4fc3b8..9391840 100644 --- a/vngcloud/services/compute/v2/common.go +++ b/vngcloud/services/common/server.go @@ -1,4 +1,4 @@ -package v2 +package common type ServerCommon struct { ServerId string diff --git a/vngcloud/services/volume/v2/common.go b/vngcloud/services/common/volume.go similarity index 90% rename from vngcloud/services/volume/v2/common.go rename to vngcloud/services/common/volume.go index 17f69d9..091b1ec 100644 --- a/vngcloud/services/volume/v2/common.go +++ b/vngcloud/services/common/volume.go @@ -1,4 +1,4 @@ -package v2 +package common type BlockVolumeCommon struct { BlockVolumeId string diff --git a/vngcloud/services/compute/icompute.go b/vngcloud/services/compute/icompute.go index 4fd7f8d..7808fb9 100644 --- a/vngcloud/services/compute/icompute.go +++ b/vngcloud/services/compute/icompute.go @@ -11,4 +11,6 @@ type IComputeServiceV2 interface { GetServerById(popts lscomputeSvcV2.IGetServerByIdRequest) (*lsentity.Server, lserr.ISdkError) DeleteServerById(popts lscomputeSvcV2.IDeleteServerByIdRequest) lserr.ISdkError UpdateServerSecgroupsByServerId(popts lscomputeSvcV2.IUpdateServerSecgroupsByServerIdRequest) (*lsentity.Server, lserr.ISdkError) + AttachBlockVolume(popts lscomputeSvcV2.IAttachBlockVolumeRequest) lserr.ISdkError + DetachBlockVolume(popts lscomputeSvcV2.IDetachBlockVolumeRequest) lserr.ISdkError } diff --git a/vngcloud/services/compute/v2/irequest.go b/vngcloud/services/compute/v2/irequest.go index 0033c62..10a1b1b 100644 --- a/vngcloud/services/compute/v2/irequest.go +++ b/vngcloud/services/compute/v2/irequest.go @@ -28,3 +28,13 @@ type IUpdateServerSecgroupsByServerIdRequest interface { ToRequestBody() interface{} GetListSecgroupsIds() []string } + +type IAttachBlockVolumeRequest interface { + GetServerId() string + GetBlockVolumeId() string +} + +type IDetachBlockVolumeRequest interface { + GetServerId() string + GetBlockVolumeId() string +} diff --git a/vngcloud/services/compute/v2/server.go b/vngcloud/services/compute/v2/server.go index 2e08b24..4553211 100644 --- a/vngcloud/services/compute/v2/server.go +++ b/vngcloud/services/compute/v2/server.go @@ -91,3 +91,48 @@ func (s *ComputeServiceV2) UpdateServerSecgroupsByServerId(popts IUpdateServerSe return resp.ToEntityServer(), nil } + +func (s *ComputeServiceV2) AttachBlockVolume(popts IAttachBlockVolumeRequest) lserr.ISdkError { + url := attachBlockVolumeUrl(s.VServerClient, popts) + errResp := lserr.NewErrorResponse(lserr.NormalErrorType) + req := lsclient.NewRequest(). + WithOkCodes(202). + WithJsonBody(map[string]interface{}{}). + WithJsonError(errResp) + + if _, sdkErr := s.VServerClient.Put(url, req); sdkErr != nil { + return lserr.SdkErrorHandler(sdkErr, errResp, + lserr.WithErrorVolumeNotFound(errResp), + lserr.WithErrorServerNotFound(errResp), + lserr.WithErrorVolumeAvailable(errResp), + lserr.WithErrorVolumeInProcess(errResp), + lserr.WithErrorVolumeAlreadyAttached(errResp), + lserr.WithErrorVolumeAlreadyAttachedThisServer(errResp), + lserr.WithErrorServerAttachVolumeQuotaExceeded(errResp)). + WithKVparameters("projectId", s.getProjectId(), + "volumeId", popts.GetBlockVolumeId(), + "serverId", popts.GetServerId()) + } + + return nil +} + +func (s *ComputeServiceV2) DetachBlockVolume(popts IDetachBlockVolumeRequest) lserr.ISdkError { + url := detachBlockVolumeUrl(s.VServerClient, popts) + errResp := lserr.NewErrorResponse(lserr.NormalErrorType) + req := lsclient.NewRequest(). + WithOkCodes(202). + WithJsonBody(map[string]interface{}{}). + WithJsonError(errResp) + + if _, sdkErr := s.VServerClient.Put(url, req); sdkErr != nil { + return lserr.SdkErrorHandler(sdkErr, errResp, + lserr.WithErrorVolumeNotFound(errResp), + lserr.WithErrorVolumeAvailable(errResp)). + WithKVparameters("projectId", s.getProjectId(), + "volumeId", popts.GetBlockVolumeId(), + "serverId", popts.GetServerId()) + } + + return nil +} diff --git a/vngcloud/services/compute/v2/server_request.go b/vngcloud/services/compute/v2/server_request.go index 26ac0a0..3a3782f 100644 --- a/vngcloud/services/compute/v2/server_request.go +++ b/vngcloud/services/compute/v2/server_request.go @@ -1,5 +1,7 @@ package v2 +import lscommon "github.com/vngcloud/vngcloud-go-sdk/v2/vngcloud/services/common" + func NewCreateServerRequest(pname, pimageId, pflavorId, pnetworkId, psubnetId, prootDiskType string, prootDiskSize int) ICreateServerRequest { opt := new(CreateServerRequest) opt.Name = pname @@ -32,6 +34,20 @@ func NewUpdateServerSecgroupsRequest(pserverId string, psecgroups ...string) IUp return opt } +func NewAttachBlockVolumeRequest(pserverId, pvolumeId string) IAttachBlockVolumeRequest { + opt := new(AttachBlockVolumeRequest) + opt.ServerId = pserverId + opt.BlockVolumeId = pvolumeId + return opt +} + +func NewDetachBlockVolumeRequest(pserverId, pvolumeId string) IDetachBlockVolumeRequest { + opt := new(DetachBlockVolumeRequest) + opt.ServerId = pserverId + opt.BlockVolumeId = pvolumeId + return opt +} + const ( DataDiskEncryptionAesXtsType DataDiskEncryptionType = "aes-xts-plain64_256" ) @@ -70,6 +86,16 @@ type CreateServerRequest struct { AutoRenew bool `json:"isEnableAutoRenew,omitempty"` } +type AttachBlockVolumeRequest struct { + lscommon.BlockVolumeCommon + lscommon.ServerCommon +} + +type DetachBlockVolumeRequest struct { + lscommon.BlockVolumeCommon + lscommon.ServerCommon +} + type DataDiskEncryptionType string type ServerTag struct { @@ -138,12 +164,12 @@ func (s *CreateServerRequest) WithProduct(pproduct string) ICreateServerRequest } type GetServerByIdRequest struct { - ServerCommon + lscommon.ServerCommon } type DeleteServerByIdRequest struct { DeleteAllVolume bool `json:"deleteAllVolume"` - ServerCommon + lscommon.ServerCommon } func (s *DeleteServerByIdRequest) WithDeleteAllVolume(pok bool) IDeleteServerByIdRequest { @@ -158,7 +184,7 @@ func (s *DeleteServerByIdRequest) ToRequestBody() interface{} { type UpdateServerSecgroupsByServerIdRequest struct { Secgroups []string `json:"securityGroup"` - ServerCommon + lscommon.ServerCommon } func (s *UpdateServerSecgroupsByServerIdRequest) ToRequestBody() interface{} { diff --git a/vngcloud/services/compute/v2/url.go b/vngcloud/services/compute/v2/url.go index c50967f..95788ea 100644 --- a/vngcloud/services/compute/v2/url.go +++ b/vngcloud/services/compute/v2/url.go @@ -29,3 +29,24 @@ func updateServerSecgroupsByServerIdUrl(psc lsclient.IServiceClient, popts IUpda popts.GetServerId(), "update-sec-group") } + +func attachBlockVolumeUrl(psc lsclient.IServiceClient, popts IAttachBlockVolumeRequest) string { + return psc.ServiceURL( + psc.GetProjectId(), + "volumes", + popts.GetBlockVolumeId(), + "servers", + popts.GetServerId(), + "attach") +} + +func detachBlockVolumeUrl(psc lsclient.IServiceClient, popts IDetachBlockVolumeRequest) string { + return psc.ServiceURL( + psc.GetProjectId(), + "volumes", + popts.GetBlockVolumeId(), + "servers", + popts.GetServerId(), + "detach", + ) +} diff --git a/vngcloud/services/volume/v2/blockvolume_request.go b/vngcloud/services/volume/v2/blockvolume_request.go index 4236a01..6e62ec3 100644 --- a/vngcloud/services/volume/v2/blockvolume_request.go +++ b/vngcloud/services/volume/v2/blockvolume_request.go @@ -3,6 +3,7 @@ package v2 import ( lfmt "fmt" ljparser "github.com/cuongpiger/joat/parser" + lscommon "github.com/vngcloud/vngcloud-go-sdk/v2/vngcloud/services/common" ) func NewCreateBlockVolumeRequest(pvolumeName, pvolumeType string, psize int64) ICreateBlockVolumeRequest { @@ -54,7 +55,7 @@ type CreateBlockVolumeRequest struct { } type DeleteBlockVolumeByIdRequest struct { - BlockVolumeCommon + lscommon.BlockVolumeCommon } type ListBlockVolumesRequest struct { @@ -63,8 +64,13 @@ type ListBlockVolumesRequest struct { Size int `q:"size"` } +type AttachBlockVolumeRequest struct { + lscommon.BlockVolumeCommon + lscommon.ServerCommon +} + type GetBlockVolumeByIdRequest struct { - BlockVolumeCommon + lscommon.BlockVolumeCommon } type (