/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"time"
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
"github.com/IBM/ibmcloud-volume-interface/lib/utils/reasoncode"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
//VpcVolumeAttachment ...
const (
VpcVolumeAttachment = "vpcVolumeAttachment"
StatusAttached = "attached"
StatusAttaching = "attaching"
StatusDetaching = "detaching"
)
// AttachVolume attach volume based on given volume attachment request
func (vpcs *VPCSession) AttachVolume(volumeAttachmentRequest provider.VolumeAttachmentRequest) (*provider.VolumeAttachmentResponse, error) {
vpcs.Logger.Debug("Entry of AttachVolume method...")
defer vpcs.Logger.Debug("Exit from AttachVolume method...")
defer metrics.UpdateDurationFromStart(vpcs.Logger, "AttachVolume", time.Now())
var err error
//check if ServiceSession is valid
if err = isValidServiceSession(vpcs); err != nil {
return nil, err
}
vpcs.Logger.Info("Validating basic inputs for Attach method...", zap.Reflect("volumeAttachRequest", volumeAttachmentRequest))
err = vpcs.validateAttachVolumeRequest(volumeAttachmentRequest)
if err != nil {
return nil, err
}
var volumeAttachResult *models.VolumeAttachment
var varp *provider.VolumeAttachmentResponse
// If it is Non IKS environment then remove the IKSVolumeAttachment field from request struct which contains clusterID.
// TO-DO : Enhance this check. Put it in right place
if !vpcs.Config.VPCConfig.IsIKS {
volumeAttachmentRequest.IKSVolumeAttachment = nil
}
volumeAttachment := models.NewVolumeAttachment(volumeAttachmentRequest)
err = vpcs.APIRetry.FlexyRetry(vpcs.Logger, func() (error, bool) {
// First , check if volume is already attached or attaching to given instance
vpcs.Logger.Info("Checking if volume is already attached by other thread")
currentVolAttachment, err := vpcs.GetVolumeAttachment(volumeAttachmentRequest)
if err == nil && currentVolAttachment != nil && currentVolAttachment.Status != StatusDetaching {
vpcs.Logger.Info("Volume is already attached", zap.Reflect("currentVolAttachment", currentVolAttachment))
varp = currentVolAttachment
return nil, true // stop retry volume already attached
}
//Try attaching volume if it's not already attached or there is error in getting current volume attachment
vpcs.Logger.Info("Attaching volume from VPC provider...", zap.Bool("IKSEnabled?", vpcs.Config.VPCConfig.IsIKS))
volumeAttachResult, err = vpcs.APIClientVolAttachMgr.AttachVolume(&volumeAttachment, vpcs.Logger)
// Keep retry, until we get the proper volumeAttachResult object
if err != nil {
return err, skipRetryForObviousErrors(err, vpcs.Config.VPCConfig.IsIKS)
}
varp = volumeAttachResult.ToVolumeAttachmentResponse(vpcs.Config.VPCConfig.VPCBlockProviderType)
return err, true // stop retry as no error
})
if err != nil {
userErr := userError.GetUserError(string(userError.VolumeAttachFailed), err, volumeAttachmentRequest.VolumeID, volumeAttachmentRequest.InstanceID)
return nil, userErr
}
vpcs.Logger.Info("Successfully attached volume from VPC provider", zap.Reflect("volumeResponse", varp))
return varp, nil
}
// validateVolume validating volume ID
func (vpcs *VPCSession) validateAttachVolumeRequest(volumeAttachRequest provider.VolumeAttachmentRequest) error {
var err error
// Check for InstanceID - required validation
if len(volumeAttachRequest.InstanceID) == 0 {
err = userError.GetUserError(string(reasoncode.ErrorRequiredFieldMissing), nil, "InstanceID")
vpcs.Logger.Error("volumeAttachRequest.InstanceID is required", zap.Error(err))
return err
}
// Check for VolumeID - required validation
if len(volumeAttachRequest.VolumeID) == 0 {
err = userError.GetUserError(string(reasoncode.ErrorRequiredFieldMissing), nil, "VolumeID")
vpcs.Logger.Error("volumeAttachRequest.VolumeID is required", zap.Error(err))
return err
}
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
"go.uber.org/zap"
)
//AuthorizeVolume allows aceess to volume based on given authorization
func (vpcs *VPCSession) AuthorizeVolume(volumeAuthorization provider.VolumeAuthorization) error {
vpcs.Logger.Info("Entry AuthorizeVolume", zap.Reflect("volumeAuthorization", volumeAuthorization))
defer vpcs.Logger.Info("Exit AuthorizeVolume", zap.Reflect("volumeAuthorization", volumeAuthorization))
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// CreateSnapshot Create snapshot from given volume
func (vpcs *VPCSession) CreateSnapshot(volumeRequest *provider.Volume, tags map[string]string) (*provider.Snapshot, error) {
vpcs.Logger.Info("Entry CreateSnapshot", zap.Reflect("volumeRequest", volumeRequest))
defer vpcs.Logger.Info("Exit CreateSnapshot", zap.Reflect("volumeRequest", volumeRequest))
if volumeRequest == nil {
return nil, userError.GetUserError("StorageFindFailedWithVolumeId", nil, "Not a valid volume ID")
}
var snapshot *models.Snapshot
var err error
// Step 1- validate input which are required
vpcs.Logger.Info("Requested volume is:", zap.Reflect("Volume", volumeRequest))
var volume *models.Volume
err = retry(vpcs.Logger, func() error {
volume, err = vpcs.Apiclient.VolumeService().GetVolume(volumeRequest.VolumeID, vpcs.Logger)
return err
})
if err != nil {
return nil, userError.GetUserError("StorageFindFailedWithVolumeId", err, "Not a valid volume ID")
}
if volume == nil {
return nil, userError.GetUserError("StorageFindFailedWithVolumeId", err, volumeRequest.VolumeID, "Not a valid volume ID")
}
err = retry(vpcs.Logger, func() error {
snapshot, err = vpcs.Apiclient.SnapshotService().CreateSnapshot(volumeRequest.VolumeID, snapshot, vpcs.Logger)
return err
})
if err != nil {
return nil, userError.GetUserError("SnapshotSpaceOrderFailed", err)
}
vpcs.Logger.Info("Successfully created snapshot with backend (vpcclient) call")
vpcs.Logger.Info("Backend created snapshot details", zap.Reflect("Snapshot", snapshot))
// Converting volume to lib volume type
volumeResponse := FromProviderToLibVolume(volume, vpcs.Logger)
if volumeResponse != nil {
respSnapshot := &provider.Snapshot{
Volume: *volumeResponse,
SnapshotID: snapshot.ID,
SnapshotCreationTime: *snapshot.CreatedAt,
}
return respSnapshot, nil
}
return nil, userError.GetUserError("CoversionNotSuccessful", err, "Not able to prepare provider volume")
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"time"
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
const (
customProfile = "custom"
minSize = 10
)
// CreateVolume Get the volume by using ID
func (vpcs *VPCSession) CreateVolume(volumeRequest provider.Volume) (volumeResponse *provider.Volume, err error) {
vpcs.Logger.Debug("Entry of CreateVolume method...")
defer vpcs.Logger.Debug("Exit from CreateVolume method...")
defer metrics.UpdateDurationFromStart(vpcs.Logger, "CreateVolume", time.Now())
vpcs.Logger.Info("Basic validation for CreateVolume request... ", zap.Reflect("RequestedVolumeDetails", volumeRequest))
resourceGroup, iops, err := validateVolumeRequest(volumeRequest)
if err != nil {
return nil, err
}
vpcs.Logger.Info("Successfully validated inputs for CreateVolume request... ")
// Build the template to send to backend
volumeTemplate := &models.Volume{
Name: *volumeRequest.Name,
Capacity: int64(*volumeRequest.Capacity),
Iops: iops,
ResourceGroup: &resourceGroup,
Profile: &models.Profile{
Name: volumeRequest.VPCVolume.Profile.Name,
},
Zone: &models.Zone{
Name: volumeRequest.Az,
},
}
var encryptionKeyCRN string
if volumeRequest.VPCVolume.VolumeEncryptionKey != nil && len(volumeRequest.VPCVolume.VolumeEncryptionKey.CRN) > 0 {
encryptionKeyCRN = volumeRequest.VPCVolume.VolumeEncryptionKey.CRN
volumeTemplate.VolumeEncryptionKey = &models.VolumeEncryptionKey{CRN: encryptionKeyCRN}
}
vpcs.Logger.Info("Calling VPC provider for volume creation...")
var volume *models.Volume
err = retry(vpcs.Logger, func() error {
volume, err = vpcs.Apiclient.VolumeService().CreateVolume(volumeTemplate, vpcs.Logger)
return err
})
if err != nil {
vpcs.Logger.Debug("Failed to create volume from VPC provider", zap.Reflect("BackendError", err))
return nil, userError.GetUserError("FailedToPlaceOrder", err)
}
vpcs.Logger.Info("Successfully created volume from VPC provider...", zap.Reflect("VolumeDetails", volume))
vpcs.Logger.Info("Waiting for volume to be in valid (available) state", zap.Reflect("VolumeDetails", volume))
err = WaitForValidVolumeState(vpcs, volume.ID)
if err != nil {
return nil, userError.GetUserError("VolumeNotInValidState", err, volume.ID)
}
vpcs.Logger.Info("Volume got valid (available) state", zap.Reflect("VolumeDetails", volume))
// Converting volume to lib volume type
volumeResponse = FromProviderToLibVolume(volume, vpcs.Logger)
// VPC does have region yet . So use requested region in response
volumeResponse.Region = volumeRequest.Region
// Return reuested tag as is if not tags returned by backend
if len(volumeResponse.Tags) == 0 && len(volumeRequest.Tags) > 0 {
volumeResponse.Tags = volumeRequest.Tags
}
vpcs.Logger.Info("VolumeResponse", zap.Reflect("volumeResponse", volumeResponse))
return volumeResponse, err
}
// validateVolumeRequest validating volume request
func validateVolumeRequest(volumeRequest provider.Volume) (models.ResourceGroup, int64, error) {
resourceGroup := models.ResourceGroup{}
var iops int64
iops = 0
// Volume name should not be empty
if volumeRequest.Name == nil {
return resourceGroup, iops, userError.GetUserError("InvalidVolumeName", nil, nil)
} else if len(*volumeRequest.Name) == 0 {
return resourceGroup, iops, userError.GetUserError("InvalidVolumeName", nil, *volumeRequest.Name)
}
// Capacity should not be empty
if volumeRequest.Capacity == nil {
return resourceGroup, iops, userError.GetUserError("VolumeCapacityInvalid", nil, nil)
} else if *volumeRequest.Capacity < minSize {
return resourceGroup, iops, 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.VPCVolume.Profile.Name != customProfile && iops > 0 {
return resourceGroup, iops, userError.GetUserError("VolumeProfileIopsInvalid", 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)
}
// 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)
}
if len(volumeRequest.VPCVolume.ResourceGroup.ID) > 0 {
resourceGroup.ID = volumeRequest.VPCVolume.ResourceGroup.ID
}
if len(volumeRequest.VPCVolume.ResourceGroup.Name) > 0 {
// get the resource group ID from resource group name as Name is not supported by RIaaS
resourceGroup.Name = volumeRequest.VPCVolume.ResourceGroup.Name
}
return resourceGroup, iops, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
"go.uber.org/zap"
)
// CreateVolumeFromSnapshot creates the volume by using ID
func (vpcs *VPCSession) CreateVolumeFromSnapshot(snapshot provider.Snapshot, tags map[string]string) (*provider.Volume, error) {
vpcs.Logger.Info("Entry CreateVolumeFromSnapshot", zap.Reflect("Snapshot", snapshot))
defer vpcs.Logger.Info("Exit CreateVolumeFromSnapshot", zap.Reflect("Snapshot", snapshot))
return nil, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"go.uber.org/zap"
)
// DeleteSnapshot delete snapshot
func (vpcs *VPCSession) DeleteSnapshot(snapshot *provider.Snapshot) error {
vpcs.Logger.Info("Entry DeleteSnapshot", zap.Reflect("snapshot", snapshot))
defer vpcs.Logger.Info("Exit DeleteSnapshot", zap.Reflect("snapshot", snapshot))
var err error
_, err = vpcs.GetSnapshot(snapshot.SnapshotID)
if err != nil {
return userError.GetUserError("StorageFindFailedWithSnapshotId", err, snapshot.SnapshotID, "Not a valid snapshot ID")
}
err = retry(vpcs.Logger, func() error {
err = vpcs.Apiclient.SnapshotService().DeleteSnapshot(snapshot.Volume.VolumeID, snapshot.SnapshotID, vpcs.Logger)
return err
})
if err != nil {
return userError.GetUserError("FailedToDeleteSnapshot", err)
}
vpcs.Logger.Info("Successfully deleted the snapshot with backend (vpcclient) call)")
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"time"
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// DeleteVolume deletes the volume
func (vpcs *VPCSession) DeleteVolume(volume *provider.Volume) (err error) {
vpcs.Logger.Debug("Entry of DeleteVolume method...")
defer vpcs.Logger.Debug("Exit from DeleteVolume method...")
defer metrics.UpdateDurationFromStart(vpcs.Logger, "DeleteVolume", time.Now())
vpcs.Logger.Info("Validating basic inputs for DeleteVolume method...", zap.Reflect("VolumeDetails", volume))
err = validateVolume(volume)
if err != nil {
return err
}
vpcs.Logger.Info("Deleting volume from VPC provider...")
err = retry(vpcs.Logger, func() error {
err = vpcs.Apiclient.VolumeService().DeleteVolume(volume.VolumeID, vpcs.Logger)
return err
})
if err != nil {
return userError.GetUserError("failedToDeleteVolume", err, volume.VolumeID)
}
err = WaitForVolumeDeletion(vpcs, volume.VolumeID)
if err != nil {
return userError.GetUserError("failedToDeleteVolume", err, volume.VolumeID)
}
vpcs.Logger.Info("Successfully deleted volume from VPC provider")
return err
}
// validateVolume validating volume ID
func validateVolume(volume *provider.Volume) (err error) {
if volume == nil {
err = userError.GetUserError("InvalidVolumeID", nil, nil)
return
}
if IsValidVolumeIDFormat(volume.VolumeID) {
return nil
}
err = userError.GetUserError("InvalidVolumeID", nil, volume.VolumeID)
return
}
// WaitForVolumeDeletion checks the volume for valid status
func WaitForVolumeDeletion(vpcs *VPCSession, volumeID string) (err error) {
vpcs.Logger.Debug("Entry of WaitForVolumeDeletion method...")
defer vpcs.Logger.Debug("Exit from WaitForVolumeDeletion method...")
var skip = false
vpcs.Logger.Info("Getting volume details from VPC provider...", zap.Reflect("VolumeID", volumeID))
err = vpcs.APIRetry.FlexyRetry(vpcs.Logger, func() (error, bool) {
_, err = vpcs.Apiclient.VolumeService().GetVolume(volumeID, vpcs.Logger)
// Keep retry, until GetVolume returns volume not found
if err != nil {
skip = skipRetry(err.(*models.Error))
return nil, skip
}
return err, false // continue retry as we are not seeing error which means volume is available
})
if err == nil && skip {
vpcs.Logger.Info("Volume got deleted.", zap.Reflect("volumeID", volumeID))
}
return err
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"net/http"
"time"
"go.uber.org/zap"
)
// DetachVolume detach volume based on given volume attachment request
func (vpcs *VPCSession) DetachVolume(volumeAttachmentTemplate provider.VolumeAttachmentRequest) (*http.Response, error) {
vpcs.Logger.Debug("Entry of DetachVolume method...")
defer vpcs.Logger.Debug("Exit from DetachVolume method...")
defer metrics.UpdateDurationFromStart(vpcs.Logger, "DetachVolume", time.Now())
var err error
//check if ServiceSession is valid
if err = isValidServiceSession(vpcs); err != nil {
return nil, err
}
vpcs.Logger.Info("Validating basic inputs for detach method...", zap.Reflect("volumeAttachmentTemplate", volumeAttachmentTemplate))
err = vpcs.validateAttachVolumeRequest(volumeAttachmentTemplate)
if err != nil {
return nil, err
}
var response *http.Response
var volumeAttachment models.VolumeAttachment
err = vpcs.APIRetry.FlexyRetry(vpcs.Logger, func() (error, bool) {
// First , check if volume is already attached to given instance
vpcs.Logger.Info("Checking if volume is already attached ")
currentVolAttachment, err := vpcs.GetVolumeAttachment(volumeAttachmentTemplate)
if err == nil && currentVolAttachment.Status != StatusDetaching {
// If no error and current volume is not already in detaching state ( i.e in attached or attaching state) attempt to detach
vpcs.Logger.Info("Found volume attachment", zap.Reflect("currentVolAttachment", currentVolAttachment))
volumeAttachment := models.NewVolumeAttachment(volumeAttachmentTemplate)
volumeAttachment.ID = currentVolAttachment.VPCVolumeAttachment.ID
vpcs.Logger.Info("Detaching volume from VPC provider...")
response, err = vpcs.APIClientVolAttachMgr.DetachVolume(&volumeAttachment, vpcs.Logger) //nolint:bodyclose
if err != nil {
return err, skipRetryForObviousErrors(err, vpcs.Config.VPCConfig.IsIKS) // Retry in case of all errors
}
}
vpcs.Logger.Info("No volume attachment found for", zap.Reflect("currentVolAttachment", currentVolAttachment), zap.Error(err))
// consider volume detach success if its already in Detaching or VolumeAttachment is not found
response = &http.Response{
StatusCode: http.StatusOK,
}
return nil, true // skip retry if volume is not found OR alreadd in detaching state
})
if err != nil {
userErr := userError.GetUserError(string(userError.VolumeDetachFailed), err, volumeAttachmentTemplate.VolumeID, volumeAttachmentTemplate.InstanceID, volumeAttachment.ID)
vpcs.Logger.Error("Volume detach failed with error", zap.Error(err))
return response, userErr
}
vpcs.Logger.Info("Successfully detached volume from VPC provider", zap.Reflect("resp", response))
return response, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"github.com/IBM/ibmcloud-volume-interface/lib/utils/reasoncode"
)
// Error implements the error interface for a Fault.
// Most easily constructed using util.NewError() or util.NewErrorWithProperties()
type Error struct {
// Fault ...
Fault Fault
}
// Fault encodes a fault condition.
// Does not implement the error interface so that cannot be accidentally
// misassigned to error variables when returned in a function response.
type Fault struct {
// Message is the fault message (required)
Message string `json:"msg"`
// ReasonCode is fault reason code (required) //TODO: will have better reasoncode mechanism
ReasonCode reasoncode.ReasonCode `json:"code"`
// WrappedErrors contains wrapped error messages (if applicable)
Wrapped []string `json:"wrapped,omitempty"`
// Properties contains diagnostic properties (if applicable)
Properties map[string]string `json:"properties,omitempty"`
}
// FaultResponse is an optional Fault
type FaultResponse struct {
Fault *Fault `json:"fault,omitempty"`
}
var _ error = Error{}
// Error satisfies the error contract
func (err Error) Error() string {
return err.Fault.Message
}
// Code satisfies the legacy provider.Error interface
func (err Error) Code() reasoncode.ReasonCode {
if err.Fault.ReasonCode == "" {
return reasoncode.ErrorUnclassified
}
return err.Fault.ReasonCode
}
// Wrapped mirrors the legacy provider.Error interface
func (err Error) Wrapped() []string {
return err.Fault.Wrapped
}
// Properties satisfies the legacy provider.Error interface
func (err Error) Properties() map[string]string {
return err.Fault.Properties
}
/**
* Copyright 2021 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"time"
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// GiB ...
const (
GiB = 1024 * 1024 * 1024
)
// ExpandVolume Get the volume by using ID
func (vpcs *VPCSession) ExpandVolume(expandVolumeRequest provider.ExpandVolumeRequest) (size int64, err error) {
vpcs.Logger.Debug("Entry of ExpandVolume method...")
defer vpcs.Logger.Debug("Exit from ExpandVolume method...")
defer metrics.UpdateDurationFromStart(vpcs.Logger, "ExpandVolume", time.Now())
// Get volume details
existVolume, err := vpcs.GetVolume(expandVolumeRequest.VolumeID)
if err != nil {
return -1, err
}
// Return existing Capacity if its greater or equal to expandable size
if existVolume.Capacity != nil && int64(*existVolume.Capacity) >= expandVolumeRequest.Capacity {
return int64(*existVolume.Capacity), nil
}
vpcs.Logger.Info("Successfully validated inputs for ExpandVolume request... ")
newSize := roundUpSize(expandVolumeRequest.Capacity, GiB)
// Build the template to send to backend
volumeTemplate := &models.Volume{
Capacity: newSize,
}
vpcs.Logger.Info("Calling VPC provider for volume expand...")
var volume *models.Volume
err = retry(vpcs.Logger, func() error {
volume, err = vpcs.Apiclient.VolumeService().ExpandVolume(expandVolumeRequest.VolumeID, volumeTemplate, vpcs.Logger)
return err
})
if err != nil {
vpcs.Logger.Debug("Failed to expand volume from VPC provider", zap.Reflect("BackendError", err))
return -1, userError.GetUserError("FailedToExpandVolume", err, volume.ID)
}
vpcs.Logger.Info("Successfully accepted volume expansion request, now waiting for volume state equal to available")
err = WaitForValidVolumeState(vpcs, volume.ID)
if err != nil {
return -1, userError.GetUserError("VolumeNotInValidState", err, volume.ID)
}
vpcs.Logger.Info("Volume got valid (available) state", zap.Reflect("VolumeDetails", volume))
return expandVolumeRequest.Capacity, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// GetSnapshot get snapshot
func (vpcs *VPCSession) GetSnapshot(snapshotID string) (*provider.Snapshot, error) {
vpcs.Logger.Info("Entry GetSnapshot", zap.Reflect("SnapshotID", snapshotID))
defer vpcs.Logger.Info("Exit GetSnapshot", zap.Reflect("SnapshotID", snapshotID))
return nil, nil
}
// GetSnapshotWithVolumeID get snapshot
func (vpcs *VPCSession) GetSnapshotWithVolumeID(volumeID string, snapshotID string) (*provider.Snapshot, error) {
vpcs.Logger.Info("Entry GetSnapshot", zap.Reflect("SnapshotID", snapshotID))
defer vpcs.Logger.Info("Exit GetSnapshot", zap.Reflect("SnapshotID", snapshotID))
var err error
var snapshot *models.Snapshot
err = retry(vpcs.Logger, func() error {
snapshot, err = vpcs.Apiclient.SnapshotService().GetSnapshot(volumeID, snapshotID, vpcs.Logger)
return err
})
if err != nil {
return nil, userError.GetUserError("FailedToDeleteSnapshot", err)
}
vpcs.Logger.Info("Successfully retrieved the snapshot details", zap.Reflect("Snapshot", snapshot))
volume, err := vpcs.GetVolume(volumeID)
if err != nil {
return nil, userError.GetUserError("StorageFindFailedWithVolumeId", err, volume.VolumeID, "Not a valid volume ID")
}
respSnapshot := &provider.Snapshot{
SnapshotID: snapshot.ID,
Volume: *volume,
}
vpcs.Logger.Info("Successfully retrieved the snapshot details", zap.Reflect("Provider snapshot", respSnapshot))
return respSnapshot, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// GetVolume gets the volume by using ID
func (vpcs *VPCSession) GetVolume(id string) (respVolume *provider.Volume, err error) {
vpcs.Logger.Debug("Entry of GetVolume method...")
defer vpcs.Logger.Debug("Exit from GetVolume method...")
vpcs.Logger.Info("Basic validation for volume ID...", zap.Reflect("VolumeID", id))
// validating volume ID
err = validateVolumeID(id)
if err != nil {
return nil, err
}
vpcs.Logger.Info("Getting volume details from VPC provider...", zap.Reflect("VolumeID", id))
var volume *models.Volume
err = retry(vpcs.Logger, func() error {
volume, err = vpcs.Apiclient.VolumeService().GetVolume(id, vpcs.Logger)
return err
})
if err != nil {
return nil, userError.GetUserError("StorageFindFailedWithVolumeId", err, id)
}
vpcs.Logger.Info("Successfully retrieved volume details from VPC backend", zap.Reflect("VolumeDetails", volume))
// Converting volume to lib volume type
respVolume = FromProviderToLibVolume(volume, vpcs.Logger)
return respVolume, err
}
// GetVolumeByName ...
func (vpcs *VPCSession) GetVolumeByName(name string) (respVolume *provider.Volume, err error) {
vpcs.Logger.Debug("Entry of GetVolumeByName method...")
defer vpcs.Logger.Debug("Exit from GetVolumeByName method...")
vpcs.Logger.Info("Basic validation for volume Name...", zap.Reflect("VolumeName", name))
if len(name) <= 0 {
err = userError.GetUserError("InvalidVolumeName", nil, name)
return
}
vpcs.Logger.Info("Getting volume details from VPC provider...", zap.Reflect("VolumeName", name))
var volume *models.Volume
err = retry(vpcs.Logger, func() error {
volume, err = vpcs.Apiclient.VolumeService().GetVolumeByName(name, vpcs.Logger)
return err
})
if err != nil {
return nil, userError.GetUserError("StorageFindFailedWithVolumeName", err, name)
}
vpcs.Logger.Info("Successfully retrieved volume details from VPC backend", zap.Reflect("VolumeDetails", volume))
// Converting volume to lib volume type
respVolume = FromProviderToLibVolume(volume, vpcs.Logger)
return respVolume, err
}
// validateVolumeID validating basic volume ID
func validateVolumeID(volumeID string) (err error) {
if IsValidVolumeIDFormat(volumeID) {
return nil
}
err = userError.GetUserError("InvalidVolumeID", nil, volumeID)
return
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"errors"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// GetVolumeAttachment get the volume attachment based on the request
func (vpcs *VPCSession) GetVolumeAttachment(volumeAttachmentRequest provider.VolumeAttachmentRequest) (*provider.VolumeAttachmentResponse, error) {
vpcs.Logger.Debug("Entry of GetVolumeAttachment method...", zap.Reflect("volumeAttachmentRequest", volumeAttachmentRequest))
defer vpcs.Logger.Debug("Exit from GetVolumeAttachment method...")
var err error
//check if ServiceSession is valid
if err = isValidServiceSession(vpcs); err != nil {
return nil, err
}
vpcs.Logger.Info("Validating basic inputs for GetVolumeAttachment method...", zap.Reflect("volumeAttachRequest", volumeAttachmentRequest))
err = vpcs.validateAttachVolumeRequest(volumeAttachmentRequest)
if err != nil {
return nil, err
}
var volumeAttachmentResponse *provider.VolumeAttachmentResponse
volumeAttachment := models.NewVolumeAttachment(volumeAttachmentRequest)
if len(volumeAttachment.ID) > 0 {
//Get volume attachments by ID if it is specified
volumeAttachmentResponse, err = vpcs.getVolumeAttachmentByID(volumeAttachment)
} else {
// Get volume attachment by Volume ID. This is inefficient operation which requires iteration over volume attachment list
volumeAttachmentResponse, err = vpcs.getVolumeAttachmentByVolumeID(volumeAttachment)
}
vpcs.Logger.Info("Volume attachment response", zap.Reflect("volumeAttachmentResponse", volumeAttachmentResponse), zap.Error(err))
return volumeAttachmentResponse, err
}
func (vpcs *VPCSession) getVolumeAttachmentByID(volumeAttachmentRequest models.VolumeAttachment) (*provider.VolumeAttachmentResponse, error) {
vpcs.Logger.Debug("Entry of getVolumeAttachmentByID()")
defer vpcs.Logger.Debug("Exit from getVolumeAttachmentByID()")
vpcs.Logger.Info("Getting VolumeAttachment from VPC provider...")
var err error
var volumeAttachmentResult *models.VolumeAttachment
/*err = retry(vpcs.Logger, func() error {
volumeAttachmentResult, err = vpcs.APIClientVolAttachMgr.GetVolumeAttachment(&volumeAttachmentRequest, vpcs.Logger)
return err
})*/
err = vpcs.APIRetry.FlexyRetry(vpcs.Logger, func() (error, bool) {
volumeAttachmentResult, err = vpcs.APIClientVolAttachMgr.GetVolumeAttachment(&volumeAttachmentRequest, vpcs.Logger)
// Keep retry, until we get the proper volumeAttachmentRequest object
if err != nil {
return err, skipRetryForObviousErrors(err, vpcs.Config.VPCConfig.IsIKS)
}
return err, true // stop retry as no error
})
if err != nil {
// API call is failed
userErr := userError.GetUserError(string(userError.VolumeAttachFindFailed), err, volumeAttachmentRequest.Volume.ID, *volumeAttachmentRequest.InstanceID)
return nil, userErr
}
volumeAttachmentResponse := volumeAttachmentResult.ToVolumeAttachmentResponse(vpcs.Config.VPCConfig.VPCBlockProviderType)
vpcs.Logger.Info("Successfully retrieved volume attachment", zap.Reflect("volumeAttachmentResponse", volumeAttachmentResponse))
return volumeAttachmentResponse, err
}
func (vpcs *VPCSession) getVolumeAttachmentByVolumeID(volumeAttachmentRequest models.VolumeAttachment) (*provider.VolumeAttachmentResponse, error) {
vpcs.Logger.Debug("Entry of getVolumeAttachmentByVolumeID()")
defer vpcs.Logger.Debug("Exit from getVolumeAttachmentByVolumeID()")
vpcs.Logger.Info("Getting VolumeAttachmentList from VPC provider...")
var volumeAttachmentList *models.VolumeAttachmentList
var err error
err = vpcs.APIRetry.FlexyRetry(vpcs.Logger, func() (error, bool) {
volumeAttachmentList, err = vpcs.APIClientVolAttachMgr.ListVolumeAttachments(&volumeAttachmentRequest, vpcs.Logger)
// Keep retry, until we get the proper volumeAttachmentRequest object
if err != nil {
return err, skipRetryForObviousErrors(err, vpcs.Config.VPCConfig.IsIKS)
}
return err, true // stop retry as no error
})
if err != nil {
// API call is failed
userErr := userError.GetUserError(string(userError.VolumeAttachFindFailed), err, volumeAttachmentRequest.Volume.ID, *volumeAttachmentRequest.InstanceID)
return nil, userErr
}
// Iterate over the volume attachment list for given instance
for _, volumeAttachmentItem := range volumeAttachmentList.VolumeAttachments {
// Check if volume ID is matching with requested volume ID
if volumeAttachmentItem.Volume.ID == volumeAttachmentRequest.Volume.ID {
vpcs.Logger.Info("Successfully found volume attachment", zap.Reflect("volumeAttachment", volumeAttachmentItem))
volumeResponse := volumeAttachmentItem.ToVolumeAttachmentResponse(vpcs.Config.VPCConfig.VPCBlockProviderType)
vpcs.Logger.Info("Successfully fetched volume attachment from VPC provider", zap.Reflect("volumeResponse", volumeResponse))
return volumeResponse, nil
}
}
// No volume attahment found in the list. So return error
userErr := userError.GetUserError(string(userError.VolumeAttachFindFailed), errors.New("no VolumeAttachment Found"), volumeAttachmentRequest.Volume.ID, *volumeAttachmentRequest.InstanceID)
vpcs.Logger.Error("Volume attachment not found", zap.Error(err))
return nil, userErr
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
"go.uber.org/zap"
)
// GetVolumeByRequestID get volume by volume ID
func (vpcs *VPCSession) GetVolumeByRequestID(requestID string) (*provider.Volume, error) {
vpcs.Logger.Info("Entry GetVolumeByRequestID", zap.Reflect("requestID", requestID))
defer vpcs.Logger.Info("Exit GetVolumeByRequestID", zap.Reflect("requestID", requestID))
return nil, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
"go.uber.org/zap"
)
// ListAllSnapshots list all snapshots
func (vpcs *VPCSession) ListAllSnapshots(volumeID string) ([]*provider.Snapshot, error) {
vpcs.Logger.Info("Entry ListAllSnapshots", zap.Reflect("VolumeID", volumeID))
defer vpcs.Logger.Info("Exit ListAllSnapshots", zap.Reflect("VolumeID", volumeID))
return nil, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
)
// ListSnapshots list all snapshots
func (vpcs *VPCSession) ListSnapshots() ([]*provider.Snapshot, error) {
vpcs.Logger.Info("Entry ListeSnapshots")
defer vpcs.Logger.Info("Exit ListSnapshots")
return nil, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"fmt"
"strings"
"time"
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
const (
maxLimit = 100
startVolumeIDNotFoundMsg = "start parameter is not valid"
)
// ListVolumes list all volumes
func (vpcs *VPCSession) ListVolumes(limit int, start string, tags map[string]string) (*provider.VolumeList, error) {
vpcs.Logger.Info("Entry ListVolumes", zap.Reflect("start", start), zap.Reflect("filters", tags))
defer vpcs.Logger.Info("Exit ListVolumes", zap.Reflect("start", start), zap.Reflect("filters", tags))
defer metrics.UpdateDurationFromStart(vpcs.Logger, "ListVolumes", time.Now())
if limit < 0 {
return nil, userError.GetUserError("InvalidListVolumesLimit", nil, limit)
}
if limit > maxLimit {
vpcs.Logger.Warn(fmt.Sprintf("listVolumes requested max entries of %v, supports values <= %v so defaulting value back to %v", limit, maxLimit, maxLimit))
limit = maxLimit
}
filters := &models.ListVolumeFilters{
// Tag: tags["tag"],
ResourceGroupID: tags["resource_group.id"],
ZoneName: tags["zone.name"],
VolumeName: tags["name"],
}
vpcs.Logger.Info("Getting volumes list from VPC provider...", zap.Reflect("start", start), zap.Reflect("filters", filters))
var volumes *models.VolumeList
var err error
err = retry(vpcs.Logger, func() error {
volumes, err = vpcs.Apiclient.VolumeService().ListVolumes(limit, start, filters, vpcs.Logger)
return err
})
if err != nil {
if strings.Contains(err.Error(), startVolumeIDNotFoundMsg) {
return nil, userError.GetUserError("StartVolumeIDNotFound", err, start)
}
return nil, userError.GetUserError("ListVolumesFailed", err)
}
vpcs.Logger.Info("Successfully retrieved volumes list from VPC backend", zap.Reflect("VolumesList", volumes))
var respVolumesList = &provider.VolumeList{}
if volumes != nil {
if volumes.Next != nil {
var next string
// "Next":{"href":"https://eu-gb.iaas.cloud.ibm.com/v1/volumes?start=3e898aa7-ac71-4323-952d-a8d741c65a68\u0026limit=1\u0026zone.name=eu-gb-1"}
if strings.Contains(volumes.Next.Href, "start=") {
next = strings.Split(strings.Split(volumes.Next.Href, "start=")[1], "\u0026")[0]
} else {
vpcs.Logger.Warn("Volumes.Next.Href is not in expected format", zap.Reflect("volumes.Next.Href", volumes.Next.Href))
}
respVolumesList.Next = next
}
volumeslist := volumes.Volumes
if len(volumeslist) > 0 {
for _, volItem := range volumeslist {
volumeResponse := FromProviderToLibVolume(volItem, vpcs.Logger)
respVolumesList.Volumes = append(respVolumesList.Volumes, volumeResponse)
}
}
}
return respVolumesList, err
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// OrderSnapshot order snapshot
func (vpcs *VPCSession) OrderSnapshot(volumeRequest provider.Volume) error {
vpcs.Logger.Info("Entry OrderSnapshot", zap.Reflect("volumeRequest", volumeRequest))
defer vpcs.Logger.Info("Exit OrderSnapshot", zap.Reflect("volumeRequest", volumeRequest))
var snapshot *models.Snapshot
var err error
// Step 1- validate input which are required
vpcs.Logger.Info("Requested volume is:", zap.Reflect("Volume", volumeRequest))
var volume *models.Volume
err = retry(vpcs.Logger, func() error {
volume, err = vpcs.Apiclient.VolumeService().GetVolume(volumeRequest.VolumeID, vpcs.Logger)
return err
})
if err != nil {
return userError.GetUserError("StorageFindFailedWithVolumeId", err, volumeRequest.VolumeID, "Not a valid volume ID")
}
vpcs.Logger.Info("Successfully retrieved given volume details from VPC provider", zap.Reflect("VolumeDetails", volume))
err = retry(vpcs.Logger, func() error {
snapshot, err = vpcs.Apiclient.SnapshotService().CreateSnapshot(volumeRequest.VolumeID, snapshot, vpcs.Logger)
return err
})
if err != nil {
return userError.GetUserError("SnapshotSpaceOrderFailed", err)
}
vpcs.Logger.Info("Successfully created the snapshot with backend (vpcclient) call.", zap.Reflect("Snapshot", snapshot))
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/IBM/ibmcloud-volume-interface/config"
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-interface/provider/iam"
"github.com/IBM/ibmcloud-volume-interface/provider/local"
vpcconfig "github.com/IBM/ibmcloud-volume-vpc/block/vpcconfig"
vpcauth "github.com/IBM/ibmcloud-volume-vpc/common/auth"
"github.com/IBM/ibmcloud-volume-vpc/common/messages"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/riaas"
"go.uber.org/zap"
)
const (
// VPCClassic ...
VPCClassic = "gc"
// VPCNextGen ...
VPCNextGen = "g2"
// PrivatePrefix ...
PrivatePrefix = "private-"
// BasePrivateURL ...
BasePrivateURL = "https://" + PrivatePrefix
// HTTPSLength ...
HTTPSLength = 8
// NEXTGenProvider ...
NEXTGenProvider = 2
)
// VPCBlockProvider implements provider.Provider
type VPCBlockProvider struct {
timeout time.Duration
Config *vpcconfig.VPCBlockConfig
tokenGenerator *tokenGenerator
ContextCF local.ContextCredentialsFactory
ClientProvider riaas.RegionalAPIClientProvider
httpClient *http.Client
APIConfig riaas.Config
}
var _ local.Provider = &VPCBlockProvider{}
// NewProvider initialises an instance of an IaaS provider.
func NewProvider(conf *vpcconfig.VPCBlockConfig, logger *zap.Logger) (local.Provider, error) {
logger.Info("Entering NewProvider")
if conf.VPCConfig == nil {
return nil, errors.New("incomplete config for VPCBlockProvider")
}
//Do config validation and enable only one generationType (i.e VPC-Classic | VPC-NG)
gcConfigFound := (conf.VPCConfig.EndpointURL != "" || conf.VPCConfig.PrivateEndpointURL != "") && (conf.VPCConfig.TokenExchangeURL != "" || conf.VPCConfig.IKSTokenExchangePrivateURL != "") && (conf.VPCConfig.APIKey != "") && (conf.VPCConfig.ResourceGroupID != "")
g2ConfigFound := (conf.VPCConfig.G2EndpointPrivateURL != "" || conf.VPCConfig.G2EndpointURL != "") && (conf.VPCConfig.IKSTokenExchangePrivateURL != "" || conf.VPCConfig.G2TokenExchangeURL != "") && (conf.VPCConfig.G2APIKey != "") && (conf.VPCConfig.G2ResourceGroupID != "")
//if both config found, look for VPCTypeEnabled, otherwise default to GC
//Incase of NG configurations, override the base properties.
if (gcConfigFound && g2ConfigFound && conf.VPCConfig.VPCTypeEnabled == VPCNextGen) || (!gcConfigFound && g2ConfigFound) {
// overwrite the common variable in case of g2 i.e gen2, first preferences would be private endpoint
if conf.VPCConfig.G2EndpointPrivateURL != "" {
conf.VPCConfig.EndpointURL = conf.VPCConfig.G2EndpointPrivateURL
} else {
conf.VPCConfig.EndpointURL = conf.VPCConfig.G2EndpointURL
}
// update iam based public toke exchange endpoint
conf.VPCConfig.TokenExchangeURL = conf.VPCConfig.G2TokenExchangeURL
conf.VPCConfig.APIKey = conf.VPCConfig.G2APIKey
conf.VPCConfig.ResourceGroupID = conf.VPCConfig.G2ResourceGroupID
//Set API Generation As 2 (if unspecified in config/ENV-VAR)
if conf.VPCConfig.G2VPCAPIGeneration <= 0 {
conf.VPCConfig.G2VPCAPIGeneration = NEXTGenProvider
}
conf.VPCConfig.VPCAPIGeneration = conf.VPCConfig.G2VPCAPIGeneration
//Set the APIVersion Date, it can be different in GC and NG
if conf.VPCConfig.G2APIVersion != "" {
conf.VPCConfig.APIVersion = conf.VPCConfig.G2APIVersion
}
//set provider-type (this usually comes from the secret)
if conf.VPCConfig.VPCBlockProviderType != VPCNextGen {
conf.VPCConfig.VPCBlockProviderType = VPCNextGen
}
//Mark this as enabled/active
if conf.VPCConfig.VPCTypeEnabled != VPCNextGen {
conf.VPCConfig.VPCTypeEnabled = VPCNextGen
}
} else { //This is GC, no-override required
conf.VPCConfig.VPCBlockProviderType = VPCClassic //incase of gc, i dont see its being set in slclient.toml, but NG cluster has this
// For backward compatibility as some of the cluster storage secret may not have private gc endpoint url
if conf.VPCConfig.PrivateEndpointURL != "" {
conf.VPCConfig.EndpointURL = conf.VPCConfig.PrivateEndpointURL
}
}
contextCF, err := vpcauth.NewVPCContextCredentialsFactory(conf)
if err != nil {
return nil, err
}
timeoutString := conf.VPCConfig.VPCTimeout
if timeoutString == "" || timeoutString == "0s" {
logger.Info("Using VPC default timeout")
timeoutString = "120s"
}
timeout, err := time.ParseDuration(timeoutString)
if err != nil {
return nil, err
}
httpClient, err := config.GeneralCAHttpClientWithTimeout(timeout)
if err != nil {
logger.Error("Failed to prepare HTTP client", util.ZapError(err))
return nil, err
}
// SetRetryParameters sets the retry logic parameters
SetRetryParameters(conf.VPCConfig.MaxRetryAttempt, conf.VPCConfig.MaxRetryGap)
provider := &VPCBlockProvider{
timeout: timeout,
Config: conf,
tokenGenerator: &tokenGenerator{config: conf.VPCConfig},
ContextCF: contextCF,
httpClient: httpClient,
APIConfig: riaas.Config{
BaseURL: conf.VPCConfig.EndpointURL,
HTTPClient: httpClient,
APIVersion: conf.VPCConfig.APIVersion,
APIGeneration: conf.VPCConfig.VPCAPIGeneration,
ResourceGroup: conf.VPCConfig.ResourceGroupID,
},
}
// Update VPC config for IKS deployment
provider.Config.VPCConfig.IsIKS = conf.IKSConfig != nil && conf.IKSConfig.Enabled
userError.MessagesEn = messages.InitMessages()
return provider, nil
}
// ContextCredentialsFactory ...
func (vpcp *VPCBlockProvider) ContextCredentialsFactory(zone *string) (local.ContextCredentialsFactory, error) {
// Datacenter name not required by VPC provider implementation
return vpcp.ContextCF, nil
}
// OpenSession opens a session on the provider
func (vpcp *VPCBlockProvider) OpenSession(ctx context.Context, contextCredentials provider.ContextCredentials, ctxLogger *zap.Logger) (provider.Session, error) {
ctxLogger.Info("Entering OpenSession")
defer metrics.UpdateDurationFromStart(ctxLogger, "OpenSession", time.Now())
defer func() {
ctxLogger.Debug("Exiting OpenSession")
}()
// validate that we have what we need - i.e. valid credentials
if contextCredentials.Credential == "" {
return nil, util.NewError("Error Insufficient Authentication", "No authentication credential provided")
}
if vpcp.Config.ServerConfig.DebugTrace {
vpcp.APIConfig.DebugWriter = os.Stdout
}
if vpcp.ClientProvider == nil {
vpcp.ClientProvider = riaas.DefaultRegionalAPIClientProvider{}
}
ctxLogger.Debug("", zap.Reflect("apiConfig.BaseURL", vpcp.APIConfig.BaseURL))
if ctx != nil && ctx.Value(provider.RequestID) != nil {
// set ContextID only of speicifed in the context
vpcp.APIConfig.ContextID = fmt.Sprintf("%v", ctx.Value(provider.RequestID))
ctxLogger.Info("", zap.Reflect("apiConfig.ContextID", vpcp.APIConfig.ContextID))
}
client, err := vpcp.ClientProvider.New(vpcp.APIConfig)
if err != nil {
return nil, err
}
// Create a token for all other API calls
token, err := getAccessToken(contextCredentials, ctxLogger)
if err != nil {
return nil, err
}
ctxLogger.Debug("", zap.Reflect("Token", token.Token))
err = client.Login(token.Token)
if err != nil {
return nil, err
}
// Update retry logic default values
if vpcp.Config.VPCConfig.MaxRetryAttempt > 0 {
ctxLogger.Debug("", zap.Reflect("MaxRetryAttempt", vpcp.Config.VPCConfig.MaxRetryAttempt))
maxRetryAttempt = vpcp.Config.VPCConfig.MaxRetryAttempt
}
if vpcp.Config.VPCConfig.MaxRetryGap > 0 {
ctxLogger.Debug("", zap.Reflect("MaxRetryGap", vpcp.Config.VPCConfig.MaxRetryGap))
maxRetryGap = vpcp.Config.VPCConfig.MaxRetryGap
}
vpcSession := &VPCSession{
VPCAccountID: contextCredentials.IAMAccountID,
Config: vpcp.Config,
ContextCredentials: contextCredentials,
VolumeType: "vpc-block",
Provider: VPC,
Apiclient: client,
APIClientVolAttachMgr: client.VolumeAttachService(),
Logger: ctxLogger,
APIRetry: NewFlexyRetryDefault(),
SessionError: nil,
}
return vpcSession, nil
}
// UpdateAPIKey ...
func (vpcp *VPCBlockProvider) UpdateAPIKey(conf interface{}, logger *zap.Logger) error {
logger.Info("Updating api key in vpc block provider")
vpcConfig, ok := conf.(*vpcconfig.VPCBlockConfig)
if !ok {
logger.Error("Error fetching vpc block config from interface")
return errors.New("error unmarshaling vpc block config")
}
if vpcp.ContextCF == nil {
logger.Error("Error updating api key, context credentials is not intiliazed")
return errors.New("credentials not initliazed in the provider")
}
err := vpcp.ContextCF.UpdateAPIKey(vpcConfig.VPCConfig.G2APIKey, logger)
if err != nil {
logger.Error("Error updating api key in provider", zap.Error(err))
return err
}
// Updating the api key in VPC block provider
vpcp.Config.VPCConfig.APIKey = vpcConfig.VPCConfig.G2APIKey
vpcp.Config.VPCConfig.G2APIKey = vpcConfig.VPCConfig.G2APIKey
vpcp.tokenGenerator.config.G2APIKey = vpcConfig.VPCConfig.G2APIKey
vpcp.tokenGenerator.config.APIKey = vpcConfig.VPCConfig.G2APIKey
return nil
}
// getAccessToken ...
func getAccessToken(creds provider.ContextCredentials, logger *zap.Logger) (token *iam.AccessToken, err error) {
switch creds.AuthType {
case provider.IAMAccessToken:
token = &iam.AccessToken{Token: creds.Credential}
default:
err = errors.New("unknown AuthType")
}
return
}
// getPrivateEndpoint ...
func getPrivateEndpoint(logger *zap.Logger, publicEndPoint string) string {
logger.Info("In getPrivateEndpoint, RIaaS public endpoint", zap.Reflect("URL", publicEndPoint))
if !strings.Contains(publicEndPoint, PrivatePrefix) {
if len(publicEndPoint) > HTTPSLength {
return BasePrivateURL + publicEndPoint[HTTPSLength:]
}
} else {
return publicEndPoint
}
return ""
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
vpcconfig "github.com/IBM/ibmcloud-volume-vpc/block/vpcconfig"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/instances"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/riaas"
"go.uber.org/zap"
)
// VPCSession implements lib.Session
type VPCSession struct {
provider.DefaultVolumeProvider
VPCAccountID string
Config *vpcconfig.VPCBlockConfig
ContextCredentials provider.ContextCredentials
VolumeType provider.VolumeType
Provider provider.VolumeProvider
Apiclient riaas.RegionalAPI
APIClientVolAttachMgr instances.VolumeAttachManager
APIVersion string
Logger *zap.Logger
APIRetry FlexyRetry
SessionError error
}
const (
// VPC storage provider
VPC = provider.VolumeProvider("VPC")
// VolumeType ...
VolumeType = provider.VolumeType("vpc-block")
// SnapshotMask ...
SnapshotMask = "id,username,capacityGb,createDate,snapshotCapacityGb,parentVolume[snapshotSizeBytes],parentVolume[snapshotCapacityGb],parentVolume[id],parentVolume[storageTierLevel],parentVolume[notes],storageType[keyName],serviceResource[datacenter[name]],billingItem[location,hourlyFlag],provisionedIops,lunId,originalVolumeName,storageTierLevel,notes"
)
var (
// DeleteVolumeReason ...
DeleteVolumeReason = "deleted by ibm-volume-lib on behalf of user request"
)
// Close at present does nothing
func (*VPCSession) Close() {
// Do nothing for now
}
// GetProviderDisplayName returns the name of the VPC provider
func (vpcs *VPCSession) GetProviderDisplayName() provider.VolumeProvider {
return VPC
}
// ProviderName ...
func (vpcs *VPCSession) ProviderName() provider.VolumeProvider {
return VPC
}
// Type ...
func (vpcs *VPCSession) Type() provider.VolumeType {
return VolumeType
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"crypto/rsa"
"errors"
"io/ioutil"
"path/filepath"
"time"
"github.com/dgrijalva/jwt-go"
"go.uber.org/zap"
"github.com/IBM/ibmcloud-volume-interface/config"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
"github.com/IBM/ibmcloud-volume-interface/provider/auth"
"github.com/IBM/ibmcloud-volume-interface/provider/local"
)
// tokenGenerator ...
type tokenGenerator struct {
config *config.VPCProviderConfig
tokenKID string
tokenTTL time.Duration
tokenBeforeTime time.Duration
privateKey *rsa.PrivateKey // Secret. Do not export
}
// readConfig ...
func (tg *tokenGenerator) readConfig(logger zap.Logger) (err error) {
logger.Info("Entering readConfig")
defer func() {
logger.Info("Exiting readConfig", zap.Duration("tokenTTL", tg.tokenTTL), zap.Duration("tokenBeforeTime", tg.tokenBeforeTime), zap.String("tokenKID", tg.tokenKID), local.ZapError(err))
}()
if tg.privateKey != nil {
return
}
path := filepath.Join(GetEtcPath(), tg.tokenKID)
pem, err := ioutil.ReadFile(filepath.Clean(path))
if err != nil {
logger.Error("Error reading PEM", local.ZapError(err))
return
}
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(pem)
if err != nil {
logger.Error("Error parsing PEM", local.ZapError(err))
return
}
tg.privateKey = privateKey
return
}
// buildToken ...
func (tg *tokenGenerator) buildToken(contextCredentials provider.ContextCredentials, ts time.Time, logger zap.Logger) (token *jwt.Token, err error) {
logger.Info("Entering getJWTToken", zap.Reflect("contextCredentials", contextCredentials))
defer func() {
logger.Info("Exiting getJWTToken", zap.Reflect("token", token), local.ZapError(err))
}()
err = tg.readConfig(logger)
if err != nil {
return
}
claims := jwt.MapClaims{
"iss": "armada",
"exp": ts.Add(tg.tokenTTL).Unix(),
"nbf": ts.Add(tg.tokenBeforeTime).Unix(),
"iat": ts.Unix(),
}
switch {
case contextCredentials.UserID == "":
errStr := "User ID is not configured"
logger.Error(errStr)
err = errors.New(errStr)
return
case contextCredentials.AuthType == auth.IMSToken:
claims["ims_user_id"] = contextCredentials.UserID
default:
claims["ims_username"] = contextCredentials.UserID
}
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
token.Header["kid"] = tg.tokenKID
return
}
// getServiceToken ...
func (tg *tokenGenerator) getServiceToken(contextCredentials provider.ContextCredentials, logger zap.Logger) (signedToken *string, err error) {
token, err := tg.buildToken(contextCredentials, time.Now(), logger)
if err != nil {
return
}
signedString, err := token.SignedString(tg.privateKey)
if err != nil {
return
}
signedToken = &signedString
return
}
// GetEtcPath returns the path to the etc directory
func GetEtcPath() string {
goPath := config.GetGoPath()
srcPath := filepath.Join("src", "github.com", "IBM",
"ibmcloud-volume-vpc")
return filepath.Join(goPath, srcPath, "etc")
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"errors"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
)
// UpdateVolume POSTs to /volumes
func (vpc *VPCSession) UpdateVolume(volumeTemplate provider.Volume) error {
return errors.New("unsupported Operation")
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"strconv"
"strings"
"time"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// maxRetryAttempt ...
var maxRetryAttempt = 10
// maxRetryGap ...
var maxRetryGap = 60
//ConstantRetryGap ...
const (
ConstantRetryGap = 10 // seconds
)
var volumeIDPartsCount = 5
var skipErrorCodes = map[string]bool{
"validation_invalid_name": true,
"volume_capacity_max": true,
"volume_id_invalid": true,
"volume_profile_iops_invalid": true,
"volume_capacity_zero_or_negative": true,
"not_found": true,
"volume_id_not_found": true,
"volume_name_not_found": true,
"internal_error": false,
"invalid_route": false,
// IKS ms error code for skip re-try
"ST0008": true, //resources not found
"ST0005": true, //worker node could not be found
"ST0014": true, // Required parameter missing or invalid
"ST0015": true, // Required parameter missing
"ST0016": true, // Tagging failed .. Do not repeat
"P4106": true, // Instnace not found
"P4107": true, // Volume not found
"P4109": true, // Volume attachment not found
}
// retry ...
func retry(logger *zap.Logger, retryfunc func() error) error {
var err error
retryGap := 10
for i := 0; i < maxRetryAttempt; i++ {
if i > 0 {
time.Sleep(time.Duration(retryGap) * time.Second)
}
err = retryfunc()
if err != nil {
//Skip retry for the below type of Errors
modelError, ok := err.(*models.Error)
if !ok {
continue
}
if skipRetry(modelError) {
break
}
if i >= 1 {
retryGap = 2 * retryGap
if retryGap > maxRetryGap {
retryGap = maxRetryGap
}
}
if (i + 1) < maxRetryAttempt {
logger.Info("Error while executing the function. Re-attempting execution ..", zap.Int("attempt..", i+2), zap.Int("retry-gap", retryGap), zap.Int("max-retry-Attempts", maxRetryAttempt), zap.Error(err))
}
continue
}
return err
}
return err
}
// skipRetry skip retry as per listed error codes
func skipRetry(err *models.Error) bool {
for _, errorItem := range err.Errors {
skipStatus, ok := skipErrorCodes[string(errorItem.Code)]
if ok {
return skipStatus
}
}
return false
}
// SkipRetryForIKS skip retry as per listed error codes
func SkipRetryForIKS(err error) bool {
iksError, iksok := err.(*models.IksError)
if iksok {
skipStatus, ok := skipErrorCodes[iksError.Code]
if ok {
return skipStatus
}
}
return false
}
// skipRetryForObviousErrors skip retry as per listed error codes
func skipRetryForObviousErrors(err error, isIKS bool) bool {
// Only for storage-api ms related calls error
if isIKS {
return SkipRetryForIKS(err)
}
// Only for RIaaS attachment related calls error
riaasError, ok := err.(*models.Error)
if ok {
return skipRetry(riaasError)
}
return false
}
// FlexyRetry ...
type FlexyRetry struct {
maxRetryAttempt int
maxRetryGap int
}
// NewFlexyRetryDefault ...
func NewFlexyRetryDefault() FlexyRetry {
return FlexyRetry{
// Default values as we configuration
maxRetryAttempt: maxRetryAttempt,
maxRetryGap: maxRetryGap,
}
}
// NewFlexyRetry ...
func NewFlexyRetry(maxRtyAtmpt int, maxrRtyGap int) FlexyRetry {
return FlexyRetry{
maxRetryAttempt: maxRtyAtmpt,
maxRetryGap: maxrRtyGap,
}
}
// FlexyRetry ...
func (fRetry *FlexyRetry) FlexyRetry(logger *zap.Logger, funcToRetry func() (error, bool)) error {
var err error
var stopRetry bool
retryGap := 10
for i := 0; i < fRetry.maxRetryAttempt; i++ {
if i > 0 {
time.Sleep(time.Duration(retryGap) * time.Second)
}
// Call function which required retry, retry is decided by function itself
err, stopRetry = funcToRetry()
if stopRetry {
break
}
// Update retry gap as per exponentioal
if i >= 1 {
retryGap = 2 * retryGap
if retryGap > fRetry.maxRetryGap {
retryGap = fRetry.maxRetryGap
}
}
if (i + 1) < fRetry.maxRetryAttempt {
logger.Info("UNEXPECTED RESULT, Re-attempting execution ..", zap.Int("attempt..", i+2),
zap.Int("retry-gap", retryGap), zap.Int("max-retry-Attempts", fRetry.maxRetryAttempt),
zap.Bool("stopRetry", stopRetry), zap.Error(err))
}
}
return err
}
// FlexyRetryWithConstGap ...
func (fRetry *FlexyRetry) FlexyRetryWithConstGap(logger *zap.Logger, funcToRetry func() (error, bool)) error {
var err error
var stopRetry bool
// lets have more number of try for wait for attach and detach specially
totalAttempt := fRetry.maxRetryAttempt * 4 // 40 time as per default values i.e 400 seconds
for i := 0; i < totalAttempt; i++ {
if i > 0 {
time.Sleep(time.Duration(ConstantRetryGap) * time.Second)
}
// Call function which required retry, retry is decided by function itself
err, stopRetry = funcToRetry()
if stopRetry {
break
}
if (i + 1) < totalAttempt {
logger.Info("UNEXPECTED RESULT from FlexyRetryWithConstGap, Re-attempting execution ..", zap.Int("attempt..", i+2),
zap.Int("retry-gap", ConstantRetryGap), zap.Int("max-retry-Attempts", totalAttempt),
zap.Bool("stopRetry", stopRetry), zap.Error(err))
}
}
return err
}
// ToInt ...
func ToInt(valueInInt string) int {
value, err := strconv.Atoi(valueInInt)
if err != nil {
return 0
}
return value
}
// ToInt64 ...
func ToInt64(valueInInt string) int64 {
value, err := strconv.ParseInt(valueInInt, 10, 64)
if err != nil {
return 0
}
return value
}
// FromProviderToLibVolume converting vpc provider volume type to generic lib volume type
func FromProviderToLibVolume(vpcVolume *models.Volume, logger *zap.Logger) (libVolume *provider.Volume) {
logger.Debug("Entry of FromProviderToLibVolume method...")
defer logger.Debug("Exit from FromProviderToLibVolume method...")
if vpcVolume == nil {
logger.Info("Volume details are empty")
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.Capacity)
iops := strconv.Itoa(int(vpcVolume.Iops))
var createdDate time.Time
if vpcVolume.CreatedAt != nil {
createdDate = *vpcVolume.CreatedAt
}
libVolume = &provider.Volume{
VolumeID: vpcVolume.ID,
Provider: VPC,
Capacity: &volumeCap,
Iops: &iops,
VolumeType: VolumeType,
CreationTime: createdDate,
}
if vpcVolume.Zone != nil {
libVolume.Az = vpcVolume.Zone.Name
}
libVolume.CRN = vpcVolume.CRN
return
}
// IsValidVolumeIDFormat validating(gc has 5 parts and NG has 6 parts)
func IsValidVolumeIDFormat(volID string) bool {
parts := strings.Split(volID, "-")
return len(parts) >= volumeIDPartsCount
}
// SetRetryParameters sets the retry logic parameters
func SetRetryParameters(maxAttempts int, maxGap int) {
if maxAttempts > 0 {
maxRetryAttempt = maxAttempts
}
if maxGap > 0 {
maxRetryGap = maxGap
}
}
func roundUpSize(volumeSizeBytes int64, allocationUnitBytes int64) int64 {
return (volumeSizeBytes + allocationUnitBytes - 1) / allocationUnitBytes
}
// isValidServiceSession check if Service Session is valid
func isValidServiceSession(vpcs *VPCSession) (err error) {
//If VPC session contains valid SessionError then there is some session related issue
if vpcs.SessionError != nil {
vpcs.Logger.Warn("Provider session is not valid")
return userError.GetUserError(string(userError.InvalidServiceSession), vpcs.SessionError)
}
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"time"
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"go.uber.org/zap"
)
// WaitForAttachVolume waits for volume to be attached to node. e.g waits till status becomes attached
func (vpcs *VPCSession) WaitForAttachVolume(volumeAttachmentTemplate provider.VolumeAttachmentRequest) (*provider.VolumeAttachmentResponse, error) {
vpcs.Logger.Debug("Entry of WaitForAttachVolume method...")
defer vpcs.Logger.Debug("Exit from WaitForAttachVolume method...")
defer metrics.UpdateDurationFromStart(vpcs.Logger, "WaitForAttachVolume", time.Now())
var err error
//check if ServiceSession is valid
if err = isValidServiceSession(vpcs); err != nil {
return nil, err
}
vpcs.Logger.Info("Validating basic inputs for WaitForAttachVolume method...", zap.Reflect("volumeAttachmentTemplate", volumeAttachmentTemplate))
err = vpcs.validateAttachVolumeRequest(volumeAttachmentTemplate)
if err != nil {
return nil, err
}
var currentVolAttachment *provider.VolumeAttachmentResponse
err = vpcs.APIRetry.FlexyRetryWithConstGap(vpcs.Logger, func() (error, bool) {
currentVolAttachment, err = vpcs.GetVolumeAttachment(volumeAttachmentTemplate)
if err != nil {
// Need to stop retry as there is an error while getting attachment
// considering that vpcs.GetVolumeAttachment already re-tried
return err, true
}
// Stop retry in case of volume is attached
return err, currentVolAttachment != nil && currentVolAttachment.Status == StatusAttached
})
// Success case, checks are required in case of timeout happened and volume is still not attached state
if err == nil && (currentVolAttachment != nil && currentVolAttachment.Status == StatusAttached) {
return currentVolAttachment, nil
}
userErr := userError.GetUserError(string(userError.VolumeAttachTimedOut), nil, volumeAttachmentTemplate.VolumeID, volumeAttachmentTemplate.InstanceID)
vpcs.Logger.Info("Wait for attach timed out", zap.Error(userErr))
return nil, userErr
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"time"
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"go.uber.org/zap"
)
// WaitForDetachVolume waits for volume to be detached from node. e.g waits till no volume attachment is found
func (vpcs *VPCSession) WaitForDetachVolume(volumeAttachmentTemplate provider.VolumeAttachmentRequest) error {
vpcs.Logger.Debug("Entry of WaitForDetachVolume method...")
defer vpcs.Logger.Debug("Exit from WaitForDetachVolume method...")
defer metrics.UpdateDurationFromStart(vpcs.Logger, "WaitForDetachVolume", time.Now())
var err error
//check if ServiceSession is valid
if err = isValidServiceSession(vpcs); err != nil {
return err
}
vpcs.Logger.Info("Validating basic inputs for WaitForDetachVolume method...", zap.Reflect("volumeAttachmentTemplate", volumeAttachmentTemplate))
err = vpcs.validateAttachVolumeRequest(volumeAttachmentTemplate)
if err != nil {
return err
}
err = vpcs.APIRetry.FlexyRetryWithConstGap(vpcs.Logger, func() (error, bool) {
_, err := vpcs.GetVolumeAttachment(volumeAttachmentTemplate)
// In case of error we should not retry as there are two conditions for error
// 1- some issues at endpoint side --> Which is already covered in vpcs.GetVolumeAttachment
// 2- Attachment not found i.e err != nil --> in this case we should not re-try as it has been deleted
if err != nil {
return err, true
}
return err, false
})
// Could be a success case
if err != nil {
if errMsg, ok := err.(util.Message); ok {
if errMsg.Code == userError.VolumeAttachFindFailed {
vpcs.Logger.Info("Volume detachment is complete")
return nil
}
}
}
userErr := userError.GetUserError(string(userError.VolumeDetachTimedOut), err, volumeAttachmentTemplate.VolumeID, volumeAttachmentTemplate.InstanceID)
vpcs.Logger.Info("Wait for detach timed out", zap.Error(userErr))
return userErr
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"time"
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
const (
validVolumeStatus = "available"
)
// WaitForValidVolumeState checks the volume for valid status
func WaitForValidVolumeState(vpcs *VPCSession, volumeID string) (err error) {
vpcs.Logger.Debug("Entry of WaitForValidVolumeState method...")
defer vpcs.Logger.Debug("Exit from WaitForValidVolumeState method...")
defer metrics.UpdateDurationFromStart(vpcs.Logger, "WaitForValidVolumeState", time.Now())
vpcs.Logger.Info("Getting volume details from VPC provider...", zap.Reflect("VolumeID", volumeID))
var volume *models.Volume
err = retry(vpcs.Logger, func() error {
volume, err = vpcs.Apiclient.VolumeService().GetVolume(volumeID, vpcs.Logger)
if err != nil {
return err
}
vpcs.Logger.Info("Getting volume details from VPC provider...", zap.Reflect("volume", volume))
if volume != nil && volume.Status == validVolumeStatus {
vpcs.Logger.Info("Volume got valid (available) state", zap.Reflect("VolumeDetails", volume))
return nil
}
return userError.GetUserError("VolumeNotInValidState", err, volumeID)
})
if err != nil {
vpcs.Logger.Info("Volume could not get valid (available) state", zap.Reflect("VolumeDetails", volume))
return userError.GetUserError("VolumeNotInValidState", err, volumeID)
}
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package auth ...
package auth
import (
"github.com/IBM/ibmcloud-volume-interface/provider/auth"
"github.com/IBM/ibmcloud-volume-interface/provider/iam"
vpcconfig "github.com/IBM/ibmcloud-volume-vpc/block/vpcconfig"
vpciam "github.com/IBM/ibmcloud-volume-vpc/common/iam"
)
// NewVPCContextCredentialsFactory ...
func NewVPCContextCredentialsFactory(config *vpcconfig.VPCBlockConfig) (*auth.ContextCredentialsFactory, error) {
authConfig := &iam.AuthConfiguration{
IamURL: config.VPCConfig.TokenExchangeURL,
IamClientID: config.VPCConfig.IamClientID,
IamClientSecret: config.VPCConfig.IamClientSecret,
}
ccf, err := auth.NewContextCredentialsFactory(authConfig)
if config.VPCConfig.IKSTokenExchangePrivateURL != "" {
authIKSConfig := &vpciam.IksAuthConfiguration{
IamAPIKey: config.VPCConfig.APIKey,
PrivateAPIRoute: config.VPCConfig.IKSTokenExchangePrivateURL, // Only for private cluster
CSRFToken: config.APIConfig.PassthroughSecret, // required for private cluster
}
ccf.TokenExchangeService, err = vpciam.NewTokenExchangeIKSService(authIKSConfig)
}
if err != nil {
return nil, err
}
return ccf, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package iam ...
package iam
import (
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/IBM-Cloud/ibm-cloud-cli-sdk/common/rest"
"github.com/IBM/ibmcloud-volume-interface/config"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-interface/provider/iam"
"go.uber.org/zap"
)
// tokenExchangeIKSService ...
type tokenExchangeIKSService struct {
iksAuthConfig *IksAuthConfiguration
httpClient *http.Client
}
// IksAuthConfiguration ...
type IksAuthConfiguration struct {
PrivateAPIRoute string
IamAPIKey string
CSRFToken string
}
// TokenExchangeService ...
var _ iam.TokenExchangeService = &tokenExchangeIKSService{}
// NewTokenExchangeIKSService ...
func NewTokenExchangeIKSService(iksAuthConfig *IksAuthConfiguration) (iam.TokenExchangeService, error) {
httpClient, err := config.GeneralCAHttpClient()
if err != nil {
return nil, err
}
return &tokenExchangeIKSService{
iksAuthConfig: iksAuthConfig,
httpClient: httpClient,
}, nil
}
// tokenExchangeIKSRequest ...
type tokenExchangeIKSRequest struct {
tes *tokenExchangeIKSService
request *rest.Request
client *rest.Client
logger *zap.Logger
errorRetrier *util.ErrorRetrier
}
// tokenExchangeIKSResponse ...
type tokenExchangeIKSResponse struct {
AccessToken string `json:"token"`
//ImsToken string `json:"ims_token"`
}
// ExchangeRefreshTokenForAccessToken ...
func (tes *tokenExchangeIKSService) ExchangeRefreshTokenForAccessToken(refreshToken string, logger *zap.Logger) (*iam.AccessToken, error) {
r := tes.newTokenExchangeRequest(logger)
return r.exchangeForAccessToken()
}
// ExchangeIAMAPIKeyForAccessToken ...
func (tes *tokenExchangeIKSService) ExchangeIAMAPIKeyForAccessToken(iamAPIKey string, logger *zap.Logger) (*iam.AccessToken, error) {
r := tes.newTokenExchangeRequest(logger)
return r.exchangeForAccessToken()
}
// newTokenExchangeRequest ...
func (tes *tokenExchangeIKSService) newTokenExchangeRequest(logger *zap.Logger) *tokenExchangeIKSRequest {
client := rest.NewClient()
client.HTTPClient = tes.httpClient
retyrInterval, _ := time.ParseDuration("3s")
return &tokenExchangeIKSRequest{
tes: tes,
request: rest.PostRequest(fmt.Sprintf("%s/v1/iam/apikey", tes.iksAuthConfig.PrivateAPIRoute)),
client: client,
logger: logger,
errorRetrier: util.NewErrorRetrier(40, retyrInterval, logger),
}
}
// ExchangeAccessTokenForIMSToken ...
func (tes *tokenExchangeIKSService) ExchangeAccessTokenForIMSToken(accessToken iam.AccessToken, logger *zap.Logger) (*iam.IMSToken, error) {
return nil, nil
}
// ExchangeIAMAPIKeyForIMSToken ...
func (tes *tokenExchangeIKSService) ExchangeIAMAPIKeyForIMSToken(iamAPIKey string, logger *zap.Logger) (*iam.IMSToken, error) {
return nil, nil
}
func (tes *tokenExchangeIKSService) GetIAMAccountIDFromAccessToken(accessToken iam.AccessToken, logger *zap.Logger) (accountID string, err error) {
return "Not required to implement", nil
}
// exchangeForAccessToken ...
func (r *tokenExchangeIKSRequest) exchangeForAccessToken() (*iam.AccessToken, error) {
var iamResp *tokenExchangeIKSResponse
var err error
err = r.errorRetrier.ErrorRetry(func() (error, bool) {
iamResp, err = r.sendTokenExchangeRequest()
return err, !iam.IsConnectionError(err) // Skip retry if its not connection error
})
if err != nil {
return nil, err
}
return &iam.AccessToken{Token: iamResp.AccessToken}, nil
}
// sendTokenExchangeRequest ...
func (r *tokenExchangeIKSRequest) sendTokenExchangeRequest() (*tokenExchangeIKSResponse, error) {
r.logger.Info("In tokenExchangeIKSRequest's sendTokenExchangeRequest()")
// Set headers
r.request = r.request.Add("X-CSRF-TOKEN", r.tes.iksAuthConfig.CSRFToken)
// Setting body
var apikey = struct {
APIKey string `json:"apikey"`
}{
APIKey: r.tes.iksAuthConfig.IamAPIKey,
}
r.request = r.request.Body(&apikey)
var successV tokenExchangeIKSResponse
var errorV = struct {
ErrorCode string `json:"code"`
ErrorDescription string `json:"description"`
ErrorType string `json:"type"`
IncidentID string `json:"incidentID"`
}{}
r.logger.Info("Sending IAM token exchange request to container api server")
resp, err := r.client.Do(r.request, &successV, &errorV)
if err != nil {
errString := err.Error()
r.logger.Error("IAM token exchange request failed", zap.Reflect("Response", resp), zap.Error(err))
if strings.Contains(errString, "no such host") {
return nil, util.NewError("EndpointNotReachable", errString)
} else if strings.Contains(errString, "Timeout") {
return nil, util.NewError("Timeout", errString)
} else {
return nil, util.NewError("ErrorUnclassified",
"IAM token exchange request failed", err)
}
}
if resp != nil && resp.StatusCode == 200 {
r.logger.Debug("IAM token exchange request successful")
return &successV, nil
}
// closing resp body only when some issues, in case of success its not required
// to close here
defer resp.Body.Close()
if errorV.ErrorDescription != "" {
r.logger.Error("IAM token exchange request failed with message",
zap.Int("StatusCode", resp.StatusCode), zap.Reflect("API IncidentID", errorV.IncidentID),
zap.Reflect("Error", errorV))
err := util.NewError("ErrorFailedTokenExchange",
"IAM token exchange request failed: "+errorV.ErrorDescription,
errors.New(errorV.ErrorCode+" "+errorV.ErrorType+", Description: "+errorV.ErrorDescription+", API IncidentID:"+errorV.IncidentID))
return nil, err
}
r.logger.Error("Unexpected IAM token exchange response",
zap.Int("StatusCode", resp.StatusCode), zap.Reflect("Response", resp))
return nil,
util.NewError("ErrorUnclassified",
"Unexpected IAM token exchange response")
}
// UpdateAPIKey ...
func (tes *tokenExchangeIKSService) UpdateAPIKey(apiKey string, logger *zap.Logger) error {
logger.Info("Updating api key")
if tes.iksAuthConfig == nil {
return errors.New("failed to update api key")
}
tes.iksAuthConfig.IamAPIKey = apiKey
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package client ...
package client
import (
"errors"
)
// ErrAuthenticationRequired is returned if a request is made before an authentication
// token has been provided to the client
var ErrAuthenticationRequired = errors.New("authentication token required")
type authenticationHandler struct {
authToken string
resourceGroup string
}
// Before is called before each request
func (a *authenticationHandler) Before(request *Request) error {
request.resourceGroup = a.resourceGroup
if a.authToken == "" {
return ErrAuthenticationRequired
}
request.headers.Set("Authorization", "Bearer "+a.authToken)
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package client ...
package client
import (
"context"
"io"
"net/http"
"net/url"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
)
// handler ...
type handler interface {
Before(request *Request) error
}
// SessionClient provides an interface for a REST API client
// go:generate counterfeiter -o fakes/client.go --fake-name SessionClient . SessionClient
type SessionClient interface {
NewRequest(operation *Operation) *Request
WithDebug(writer io.Writer) SessionClient
WithAuthToken(authToken string) SessionClient
WithPathParameter(name, value string) SessionClient
WithQueryValue(name, value string) SessionClient
}
type client struct {
baseURL string
httpClient *http.Client
pathParams Params
queryValues url.Values
authenHandler handler
debugWriter io.Writer
resourceGroup string
contextID string
context context.Context
}
// New creates a new instance of a SessionClient
func New(ctx context.Context, baseURL string, queryValues url.Values, httpClient *http.Client, contextID string, resourceGroupID string) SessionClient {
return &client{
baseURL: baseURL,
httpClient: httpClient,
pathParams: Params{},
queryValues: queryValues,
authenHandler: &authenticationHandler{},
contextID: contextID,
context: ctx,
resourceGroup: resourceGroupID,
}
}
// NewRequest creates a request and configures it with the supplied operation
func (c *client) NewRequest(operation *Operation) *Request {
headers := http.Header{}
headers.Set("Accept", "application/json")
headers.Set("User-Agent", models.UserAgent)
if c.contextID != "" {
headers.Set("X-Request-ID", c.contextID)
headers.Set("X-Transaction-ID", c.contextID) // To avoid IKS cloudflare overriding X-Request-ID
}
if c.resourceGroup != "" {
headers.Set("X-Auth-Resource-Group-ID", c.resourceGroup)
}
// Copy the query values to a new map
qv := url.Values{}
for k, v := range c.queryValues {
qv[k] = v
}
return &Request{
httpClient: c.httpClient,
context: c.context,
baseURL: c.baseURL,
operation: operation,
pathParams: c.pathParams.Copy(),
authenHandler: c.authenHandler,
headers: headers,
debugWriter: c.debugWriter,
resourceGroup: c.resourceGroup,
queryValues: qv,
}
}
// WithDebug enables debug for this SessionClient, outputting to the supplied writer
func (c *client) WithDebug(writer io.Writer) SessionClient {
c.debugWriter = writer
return c
}
// WithAuthToken supplies the authentication token to use for all requests made by this session
func (c *client) WithAuthToken(authToken string) SessionClient {
c.authenHandler = &authenticationHandler{
authToken: authToken,
}
return c
}
// WithPathParameter adds a path parameter to the request
func (c *client) WithPathParameter(name, value string) SessionClient {
c.pathParams[name] = value
return c
}
// WithQueryValue adds a query parameter to the request
func (c *client) WithQueryValue(name, value string) SessionClient {
c.queryValues.Set(name, value)
return c
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package client ...
package client
// Params ...
type Params map[string]string
// Copy performs a shallow copy of a Params object
func (p Params) Copy() Params {
params := Params{}
for k, v := range p {
params[k] = v
}
return params
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package client ...
package client
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httputil"
"net/url"
"reflect"
"regexp"
"strings"
"time"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client/payload"
"github.com/fatih/structs"
)
// Operation defines the API operation to be invoked
type Operation struct {
Name string
Method string
PathPattern string
}
// Request defines the properties of an API request. It can then be invoked to
// call the underlying API specified by the supplied operation
type Request struct {
httpClient *http.Client
baseURL string
authenHandler handler
context context.Context
operation *Operation
pathParams Params
headers http.Header
debugWriter io.Writer
queryValues url.Values
bodyProvider BodyProvider
successConsumer ResponseConsumer
errorConsumer ResponseConsumer
resourceGroup string
}
// BodyProvider declares an interface that describes an HTTP body, for
// both request and response
type BodyProvider interface {
ContentType() string
Body() (io.Reader, error)
}
// ResponseConsumer ...
type ResponseConsumer interface {
Consume(io.Reader) error
Receiver() interface{}
}
func (r *Request) path() string {
path := r.operation.PathPattern
for k, v := range r.pathParams {
path = strings.Replace(path, "{"+k+"}", v, -1)
}
return path
}
// URL constructs the full URL for a request
func (r *Request) URL() string {
baseURL, baseErr := url.Parse(r.baseURL)
if baseErr != nil {
return ""
}
if !strings.HasSuffix(baseURL.Path, "/") {
baseURL.Path += "/"
}
pathURL, pathErr := url.Parse(r.path())
if pathErr != nil {
return ""
}
resolvedURL := baseURL.ResolveReference(pathURL)
resolvedURL.RawQuery = r.queryValues.Encode()
return resolvedURL.String()
}
// PathParameter sets a path parameter to be resolved on invocation of a request
func (r *Request) PathParameter(name, value string) *Request {
r.pathParams[name] = value
return r
}
// AddQueryValue ...
func (r *Request) AddQueryValue(key, value string) *Request {
if r.queryValues == nil {
r.queryValues = url.Values{}
}
r.queryValues.Add(key, value)
return r
}
// SetQueryValue ...
func (r *Request) SetQueryValue(key, value string) *Request {
if r.queryValues == nil {
r.queryValues = url.Values{}
}
r.queryValues.Set(key, value)
return r
}
// JSONBody converts the supplied argument to JSON to use as the body of a request
func (r *Request) JSONBody(p interface{}) *Request {
if r.operation.Method == http.MethodPost && reflect.ValueOf(p).Kind() == reflect.Struct {
structs.DefaultTagName = "json"
m := structs.Map(p)
if r.resourceGroup != "" {
m["resourceGroup"] = r.resourceGroup
}
r.bodyProvider = payload.NewJSONBodyProvider(m)
} else {
r.bodyProvider = payload.NewJSONBodyProvider(p)
}
return r
}
// MultipartFileBody configures the POST payload to be sent in multi-part format. The
// content is read from the supplied Reader.
func (r *Request) MultipartFileBody(name string, contents io.Reader) *Request {
r.bodyProvider = payload.NewMultipartFileBody(name, contents)
return r
}
// JSONSuccess configures the receiver to use to process a JSON response
// for a successful (2xx) response
func (r *Request) JSONSuccess(receiver interface{}) *Request {
r.successConsumer = payload.NewJSONConsumer(receiver)
return r
}
// JSONError configures the error to populate in the event of an unsuccessful
// (non-2xx) response
func (r *Request) JSONError(receiver error) *Request {
r.errorConsumer = payload.NewJSONConsumer(receiver)
return r
}
// Invoke performs the request, and populates the response or error as appropriate
func (r *Request) Invoke() (*http.Response, error) {
err := r.authenHandler.Before(r)
if err != nil {
return nil, err
}
var body io.Reader
if r.bodyProvider != nil {
body, err = r.bodyProvider.Body()
if err != nil {
return nil, err
}
if contentType := r.bodyProvider.ContentType(); contentType != "" {
r.headers.Set("Content-Type", contentType)
}
}
httpRequest, err := http.NewRequest(r.operation.Method, r.URL(), body)
if err != nil {
return nil, err
}
for k, v := range r.headers {
httpRequest.Header[k] = v
}
r.debugRequest(httpRequest)
resp, err := r.httpClient.Do(httpRequest.WithContext(r.context))
if err != nil {
return nil, err
}
defer resp.Body.Close()
r.debugResponse(resp)
switch {
case resp.StatusCode == http.StatusNoContent:
break
case resp.StatusCode >= 200 && resp.StatusCode <= 299:
if r.successConsumer != nil {
err = r.successConsumer.Consume(resp.Body)
}
default:
if r.errorConsumer != nil {
err = r.errorConsumer.Consume(resp.Body)
if err == nil {
err = r.errorConsumer.Receiver().(error)
}
}
}
return resp, err
}
func (r *Request) debugRequest(req *http.Request) {
if r.debugWriter == nil {
return
}
multipart := strings.Contains(req.Header.Get("Content-Type"), "multipart/form-data")
dumpedRequest, err := httputil.DumpRequest(req, !multipart)
if err != nil {
r.debugf("Error dumping request\n%s\n", err)
return
}
r.debugf("\nREQUEST: [%s]\n%s\n", time.Now().Format(time.RFC3339), sanitize(dumpedRequest))
if multipart {
r.debugf("[MULTIPART/FORM-DATA CONTENT HIDDEN]\n")
}
}
func (r *Request) debugResponse(resp *http.Response) {
if r.debugWriter == nil {
return
}
dumpedResponse, err := httputil.DumpResponse(resp, true)
if err != nil {
fmt.Fprintf(r.debugWriter, "Error dumping response\n%s\n", err)
return
}
r.debugf("\nRESPONSE: [%s]\n%s\n", time.Now().Format(time.RFC3339), sanitize(dumpedResponse))
}
func (r *Request) debugf(format string, args ...interface{}) {
fmt.Fprintf(r.debugWriter, format, args...)
}
// RedactedFillin used as a replacement string in debug logs for sensitive data
const RedactedFillin = "[REDACTED]"
func sanitize(input []byte) string {
sanitized := string(input)
re := regexp.MustCompile(`(?mi)^Authorization: .*`)
sanitized = re.ReplaceAllString(sanitized, "Authorization: "+RedactedFillin)
re = regexp.MustCompile(`(?mi)^X-Auth-Token: .*`)
sanitized = re.ReplaceAllString(sanitized, "X-Auth-Token: "+RedactedFillin)
re = regexp.MustCompile(`(?mi)^APIKey: .*`)
sanitized = re.ReplaceAllString(sanitized, "APIKey: "+RedactedFillin)
sanitized = sanitizeJSON("key", sanitized)
sanitized = sanitizeJSON("password", sanitized)
sanitized = sanitizeJSON("passphrase", sanitized)
return sanitized
}
func sanitizeJSON(propertySubstring string, json string) string {
regex := regexp.MustCompile(fmt.Sprintf(`(?i)"([^"]*%s[^"]*)":\s*"[^\,]*"`, propertySubstring))
return regex.ReplaceAllString(json, fmt.Sprintf(`"$1":"%s"`, RedactedFillin))
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package instances ...
package instances
import (
"time"
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// AttachVolume attached volume to instances with givne volume attachment details
func (vs *VolumeAttachService) AttachVolume(volumeAttachmentTemplate *models.VolumeAttachment, ctxLogger *zap.Logger) (*models.VolumeAttachment, error) {
methodName := "VolumeAttachService.AttachVolume"
defer util.TimeTracker(methodName, time.Now())
defer metrics.UpdateDurationFromStart(ctxLogger, methodName, time.Now())
operation := &client.Operation{
Name: "AttachVolume",
Method: "POST",
PathPattern: vs.pathPrefix + instanceIDvolumeAttachmentPath,
}
var volumeAttachment models.VolumeAttachment
apiErr := vs.receiverError
operationRequest := vs.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command and payload details", zap.Reflect("URL", operationRequest.URL()), zap.Reflect("Payload", volumeAttachmentTemplate), zap.Reflect("Operation", operation), zap.Reflect("PathParameters", volumeAttachmentTemplate.InstanceID))
_, err := vs.populatePathPrefixParameters(operationRequest, volumeAttachmentTemplate).JSONBody(volumeAttachmentTemplate).JSONSuccess(&volumeAttachment).JSONError(apiErr).Invoke()
if err != nil {
return nil, err
}
ctxLogger.Info("Successfully attached the volume")
return &volumeAttachment, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package instances ...
package instances
import (
"net/http"
"time"
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// DetachVolume retrives the volume attach status with givne volume attachment details
func (vs *VolumeAttachService) DetachVolume(volumeAttachmentTemplate *models.VolumeAttachment, ctxLogger *zap.Logger) (*http.Response, error) {
methodName := "VolumeAttachService.DetachVolume"
defer util.TimeTracker(methodName, time.Now())
defer metrics.UpdateDurationFromStart(ctxLogger, methodName, time.Now())
operation := &client.Operation{
Name: "DetachVolume",
Method: "DELETE",
PathPattern: vs.pathPrefix + instanceIDattachmentIDPath,
}
apiErr := vs.receiverError
operationRequest := vs.client.NewRequest(operation)
operationRequest = vs.populatePathPrefixParameters(operationRequest, volumeAttachmentTemplate)
operationRequest = operationRequest.PathParameter(attachmentIDParam, volumeAttachmentTemplate.ID)
ctxLogger.Info("Equivalent curl command details", zap.Reflect("URL", operationRequest.URL()), zap.Reflect("volumeAttachmentTemplate", volumeAttachmentTemplate), zap.Reflect("Operation", operation))
ctxLogger.Info("Pathparameters", zap.Reflect(instanceIDParam, volumeAttachmentTemplate.InstanceID), zap.Reflect(attachmentIDParam, volumeAttachmentTemplate.ID))
resp, err := operationRequest.JSONError(apiErr).Invoke()
if err != nil {
ctxLogger.Error("Error occurred while deleting volume attachment", zap.Error(err))
if resp != nil && resp.StatusCode == http.StatusNotFound {
// volume Attachment is deleted. So do not want to retry
ctxLogger.Info("Exit DetachVolume", zap.Any("resp", resp.StatusCode), zap.Error(err), zap.Error(apiErr))
return resp, apiErr
}
}
ctxLogger.Info("Exit DetachVolume", zap.Error(err), zap.Error(apiErr))
return resp, err
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package instances ...
package instances
import (
"time"
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// GetVolumeAttachment retrives the volume attach status with given volume attachment details
func (vs *VolumeAttachService) GetVolumeAttachment(volumeAttachmentTemplate *models.VolumeAttachment, ctxLogger *zap.Logger) (*models.VolumeAttachment, error) {
methodName := "VolumeAttachService.GetVolumeAttachment"
defer util.TimeTracker(methodName, time.Now())
defer metrics.UpdateDurationFromStart(ctxLogger, methodName, time.Now())
operation := &client.Operation{
Name: "GetVolumeAttachment",
Method: "GET",
PathPattern: vs.pathPrefix + instanceIDattachmentIDPath,
}
apiErr := vs.receiverError
var volumeAttachment models.VolumeAttachment
operationRequest := vs.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command details", zap.Reflect("URL", operationRequest.URL()), zap.Reflect("volumeAttachmentTemplate", volumeAttachmentTemplate), zap.Reflect("Operation", operation))
ctxLogger.Info("Pathparameters", zap.Reflect(instanceIDParam, volumeAttachmentTemplate.InstanceID), zap.Reflect(attachmentIDParam, volumeAttachmentTemplate.ID))
operationRequest = vs.populatePathPrefixParameters(operationRequest, volumeAttachmentTemplate)
operationRequest = operationRequest.PathParameter(attachmentIDParam, volumeAttachmentTemplate.ID)
_, err := operationRequest.JSONSuccess(&volumeAttachment).JSONError(apiErr).Invoke()
if err != nil {
ctxLogger.Error("Error occurred while getting volume attachment", zap.Error(err))
return nil, err
}
ctxLogger.Info("Successfully retrieved the volume attachment", zap.Reflect("volumeAttachment", volumeAttachment))
return &volumeAttachment, err
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package instances ...
package instances
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// AttachVolume attached volume to instances with givne volume attachment details
func (vs *IKSVolumeAttachService) AttachVolume(volumeAttachmentTemplate *models.VolumeAttachment, ctxLogger *zap.Logger) (*models.VolumeAttachment, error) {
defer util.TimeTracker("IKS AttachVolume", time.Now())
operation := &client.Operation{
Name: "AttachVolume",
Method: "POST",
PathPattern: vs.pathPrefix + "createAttachment",
}
var volumeAttachment models.VolumeAttachment
apiErr := vs.receiverError
operationRequest := vs.client.NewRequest(operation)
operationRequest = operationRequest.SetQueryValue(IksClusterQueryKey, *volumeAttachmentTemplate.ClusterID)
operationRequest = operationRequest.SetQueryValue(IksWorkerQueryKey, *volumeAttachmentTemplate.InstanceID)
vol := *volumeAttachmentTemplate.Volume
operationRequest = operationRequest.SetQueryValue(IksVolumeQueryKey, vol.ID)
ctxLogger.Info("Equivalent curl command and query parameters", zap.Reflect("URL", operationRequest.URL()), zap.Reflect("Payload", volumeAttachmentTemplate), zap.Reflect("Operation", operation), zap.Reflect(IksClusterQueryKey, volumeAttachmentTemplate.ClusterID), zap.Reflect(IksWorkerQueryKey, volumeAttachmentTemplate.InstanceID), zap.Reflect(IksVolumeQueryKey, vol.ID))
_, err := operationRequest.JSONBody(volumeAttachmentTemplate).JSONSuccess(&volumeAttachment).JSONError(apiErr).Invoke()
if err != nil {
return nil, err
}
ctxLogger.Info("Successfully attached the volume")
return &volumeAttachment, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package instances ...
package instances
import (
"net/http"
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// DetachVolume retrives the volume attach status with givne volume attachment details
func (vs *IKSVolumeAttachService) DetachVolume(volumeAttachmentTemplate *models.VolumeAttachment, ctxLogger *zap.Logger) (*http.Response, error) {
defer util.TimeTracker("IKS DetachVolume", time.Now())
operation := &client.Operation{
Name: "DetachVolume",
Method: "DELETE",
PathPattern: vs.pathPrefix + "deleteAttachment",
}
apiErr := vs.receiverError
operationRequest := vs.client.NewRequest(operation)
operationRequest = operationRequest.SetQueryValue(IksClusterQueryKey, *volumeAttachmentTemplate.ClusterID)
operationRequest = operationRequest.SetQueryValue(IksWorkerQueryKey, *volumeAttachmentTemplate.InstanceID)
operationRequest = operationRequest.SetQueryValue(IksVolumeAttachmentIDQueryKey, volumeAttachmentTemplate.ID)
ctxLogger.Info("Equivalent curl command and query parameters", zap.Reflect("URL", operationRequest.URL()), zap.Reflect("volumeAttachmentTemplate", volumeAttachmentTemplate), zap.Reflect("Operation", operation), zap.Reflect(IksClusterQueryKey, *volumeAttachmentTemplate.ClusterID), zap.Reflect(IksWorkerQueryKey, *volumeAttachmentTemplate.InstanceID), zap.Reflect(IksVolumeAttachmentIDQueryKey, volumeAttachmentTemplate.ID))
resp, err := operationRequest.JSONError(apiErr).Invoke()
if err != nil {
ctxLogger.Error("Error occurred while deleting volume attachment", zap.Error(err))
if resp != nil && resp.StatusCode == http.StatusNotFound {
// volume attachment is deleted, no need to retry
return resp, apiErr
}
}
ctxLogger.Info("Successfully deleted the volume attachment")
return resp, err
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package instances ...
package instances
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// GetVolumeAttachment retrives the volume attach status with given volume attachment details
func (vs *IKSVolumeAttachService) GetVolumeAttachment(volumeAttachmentTemplate *models.VolumeAttachment, ctxLogger *zap.Logger) (*models.VolumeAttachment, error) {
defer util.TimeTracker("IKS GetVolumeAttachment", time.Now())
operation := &client.Operation{
Name: "GetVolumeAttachment",
Method: "GET",
PathPattern: vs.pathPrefix + "getAttachment",
}
apiErr := vs.receiverError
var volumeAttachment models.VolumeAttachment
operationRequest := vs.client.NewRequest(operation)
operationRequest = operationRequest.SetQueryValue(IksClusterQueryKey, *volumeAttachmentTemplate.ClusterID)
operationRequest = operationRequest.SetQueryValue(IksWorkerQueryKey, *volumeAttachmentTemplate.InstanceID)
operationRequest = operationRequest.SetQueryValue(IksVolumeAttachmentIDQueryKey, volumeAttachmentTemplate.ID)
ctxLogger.Info("Equivalent curl command and query parameters", zap.Reflect("URL", operationRequest.URL()), zap.Reflect(IksClusterQueryKey, *volumeAttachmentTemplate.ClusterID), zap.Reflect(IksWorkerQueryKey, *volumeAttachmentTemplate.InstanceID), zap.Reflect(IksVolumeAttachmentIDQueryKey, volumeAttachmentTemplate.ID))
_, err := operationRequest.JSONSuccess(&volumeAttachment).JSONError(apiErr).Invoke()
if err != nil {
ctxLogger.Error("Error occurred while getting volume attachment", zap.Error(err))
return nil, err
}
ctxLogger.Info("Successfully retrieved the volume attachment", zap.Reflect("volumeAttachment", volumeAttachment))
return &volumeAttachment, err
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package instances ...
package instances
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// ListVolumeAttachments retrives the list volume attachments with givne volume attachment details
func (vs *IKSVolumeAttachService) ListVolumeAttachments(volumeAttachmentTemplate *models.VolumeAttachment, ctxLogger *zap.Logger) (*models.VolumeAttachmentList, error) {
defer util.TimeTracker("IKS ListVolumeAttachments", time.Now())
operation := &client.Operation{
Name: "ListVolumeAttachment",
Method: "GET",
PathPattern: vs.pathPrefix + "getAttachmentsList",
}
var volumeAttachmentList models.VolumeAttachmentList
apiErr := vs.receiverError
vs.client = vs.client.WithQueryValue(IksClusterQueryKey, *volumeAttachmentTemplate.ClusterID)
vs.client = vs.client.WithQueryValue(IksWorkerQueryKey, *volumeAttachmentTemplate.InstanceID)
operationRequest := vs.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command and query parameters", zap.Reflect("URL", operationRequest.URL()), zap.Reflect("volumeAttachmentTemplate", volumeAttachmentTemplate), zap.Reflect("Operation", operation), zap.Reflect(IksClusterQueryKey, *volumeAttachmentTemplate.ClusterID), zap.Reflect(IksWorkerQueryKey, *volumeAttachmentTemplate.InstanceID))
_, err := operationRequest.JSONSuccess(&volumeAttachmentList).JSONError(apiErr).Invoke()
if err != nil {
ctxLogger.Error("Error occurred while getting volume attachments list", zap.Error(err))
return nil, err
}
ctxLogger.Info("Successfully retrieved the volume attachments")
return &volumeAttachmentList, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package instances ...
package instances
import (
"time"
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// ListVolumeAttachments retrives the list volume attachments with givne volume attachment details
func (vs *VolumeAttachService) ListVolumeAttachments(volumeAttachmentTemplate *models.VolumeAttachment, ctxLogger *zap.Logger) (*models.VolumeAttachmentList, error) {
methodName := "VolumeAttachService.ListVolumeAttachments"
defer util.TimeTracker(methodName, time.Now())
defer metrics.UpdateDurationFromStart(ctxLogger, methodName, time.Now())
operation := &client.Operation{
Name: "ListVolumeAttachment",
Method: "GET",
PathPattern: vs.pathPrefix + instanceIDvolumeAttachmentPath,
}
var volumeAttachmentList models.VolumeAttachmentList
apiErr := vs.receiverError
operationRequest := vs.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command details", zap.Reflect("URL", operationRequest.URL()), zap.Reflect("volumeAttachmentTemplate", volumeAttachmentTemplate), zap.Reflect("Operation", operation))
operationRequest = vs.populatePathPrefixParameters(operationRequest, volumeAttachmentTemplate)
_, err := operationRequest.JSONSuccess(&volumeAttachmentList).JSONError(apiErr).Invoke()
if err != nil {
ctxLogger.Error("Error occurred while getting volume attachments list", zap.Error(err))
return nil, err
}
ctxLogger.Info("Successfully retrieved the volume attachments")
return &volumeAttachmentList, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package instances ...
package instances
import (
"net/http"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// VolumeAttachManager operations
//go:generate counterfeiter -o fakes/volume_attach_service.go --fake-name VolumeAttachService . VolumeAttachManager
type VolumeAttachManager interface {
// Create the volume with authorisation by passing required information in the volume object
AttachVolume(*models.VolumeAttachment, *zap.Logger) (*models.VolumeAttachment, error)
// GetVolumeAttachment retrives the single VolumeAttachment based on the instance ID and attachmentID
GetVolumeAttachment(*models.VolumeAttachment, *zap.Logger) (*models.VolumeAttachment, error)
// ListVolumeAttachments retrives the VolumeAttachment list for given server
ListVolumeAttachments(*models.VolumeAttachment, *zap.Logger) (*models.VolumeAttachmentList, error)
// Delete the volume
DetachVolume(*models.VolumeAttachment, *zap.Logger) (*http.Response, error)
}
// VolumeAttachService ...
type VolumeAttachService struct {
client client.SessionClient
pathPrefix string
receiverError error
populatePathPrefixParameters func(request *client.Request, volumeAttachmentTemplate *models.VolumeAttachment) *client.Request
}
// IKSVolumeAttachService ...
type IKSVolumeAttachService struct {
client client.SessionClient
pathPrefix string
receiverError error
}
var _ VolumeAttachManager = &VolumeAttachService{}
// New ...
func New(clientIn client.SessionClient) VolumeAttachManager {
err := models.Error{}
return &VolumeAttachService{
client: clientIn,
pathPrefix: VpcPathPrefix,
receiverError: &err,
populatePathPrefixParameters: func(request *client.Request, volumeAttachmentTemplate *models.VolumeAttachment) *client.Request {
request.PathParameter(instanceIDParam, *volumeAttachmentTemplate.InstanceID)
return request
},
}
}
var _ VolumeAttachManager = &IKSVolumeAttachService{}
// NewIKSVolumeAttachmentManager ...
func NewIKSVolumeAttachmentManager(clientIn client.SessionClient) VolumeAttachManager {
err := models.IksError{}
return &IKSVolumeAttachService{
client: clientIn,
pathPrefix: IksPathPrefix,
receiverError: &err,
}
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package riaas ...
package riaas
import (
"context"
"io"
"net/http"
)
// Config for the Session
type Config struct {
BaseURL string
AccountID string
Username string
APIKey string
ResourceGroup string
Password string
ContextID string
DebugWriter io.Writer
HTTPClient *http.Client
Context context.Context
APIVersion string
APIGeneration int
}
func (c Config) httpClient() *http.Client {
if c.HTTPClient != nil {
return c.HTTPClient
}
return http.DefaultClient
}
func (c Config) baseURL() string {
return c.BaseURL
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package riaas ...
package riaas
import (
"context"
"net/url"
"strconv"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/instances"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/vpcvolume"
)
// RegionalAPI is the main interface for the RIAAS API client. From here, service
// objects for the individual parts of the API can be obtained
//go:generate counterfeiter -o fakes/regional_api.go --fake-name RegionalAPI . RegionalAPI
type RegionalAPI interface {
Login(token string) error
VolumeService() vpcvolume.VolumeManager
VolumeAttachService() instances.VolumeAttachManager
IKSVolumeAttachService() instances.VolumeAttachManager
SnapshotService() vpcvolume.SnapshotManager
}
var _ RegionalAPI = &Session{}
// Session is a base implementation of the RegionalAPI interface
type Session struct {
client client.SessionClient
config Config
}
// New creates a new Session volume, using the supplied config
func New(config Config) (*Session, error) {
ctx := config.Context
if ctx == nil {
ctx = context.Background()
}
// Default API version
backendAPIVersion := models.APIVersion
// Overwrite if the version is passed
if len(config.APIVersion) > 0 {
backendAPIVersion = config.APIVersion
}
// Overwrite if the generation is passed
apiGen := models.APIGeneration
if config.APIGeneration > 0 {
apiGen = config.APIGeneration
}
queryValues := url.Values{
"version": []string{backendAPIVersion},
"generation": []string{strconv.Itoa(apiGen)},
}
riaasClient := client.New(ctx, config.baseURL(), queryValues, config.httpClient(), config.ContextID, config.ResourceGroup)
if config.DebugWriter != nil {
riaasClient.WithDebug(config.DebugWriter)
}
return &Session{
client: riaasClient,
config: config,
}, nil
}
// Login configures the session with the supplied Authentication token
// which is used for all requests to the API
func (s *Session) Login(token string) error {
s.client.WithAuthToken(token)
return nil
}
// VolumeService returns the Volume service for managing volumes
func (s *Session) VolumeService() vpcvolume.VolumeManager {
return vpcvolume.New(s.client)
}
// VolumeAttachService returns the VolumeAttachService for managing volumes
func (s *Session) VolumeAttachService() instances.VolumeAttachManager {
return instances.New(s.client)
}
// IKSVolumeAttachService returns the VolumeAttachService for managing volumes through IKS
func (s *Session) IKSVolumeAttachService() instances.VolumeAttachManager {
return instances.NewIKSVolumeAttachmentManager(s.client)
}
// SnapshotService returns the Snapshot service for managing snapshot
func (s *Session) SnapshotService() vpcvolume.SnapshotManager {
return vpcvolume.NewSnapshotManager(s.client)
}
// RegionalAPIClientProvider declares an interface for a provider that can supply a new
// RegionalAPI client session
//go:generate counterfeiter -o fakes/client_provider.go --fake-name RegionalAPIClientProvider . RegionalAPIClientProvider
type RegionalAPIClientProvider interface {
New(config Config) (RegionalAPI, error)
}
// DefaultRegionalAPIClientProvider declares a basic client provider that delegates to
// New(). Can be used for dependency injection.
type DefaultRegionalAPIClientProvider struct {
}
var _ RegionalAPIClientProvider = DefaultRegionalAPIClientProvider{}
// New creates a new Session volume, using the supplied config
func (d DefaultRegionalAPIClientProvider) New(config Config) (RegionalAPI, error) {
return New(config)
}
// IKSSession ...
type IKSSession struct {
Session
}
var _ RegionalAPI = &IKSSession{}
// VolumeService returns the Volume service for managing volumes
func (s *IKSSession) VolumeService() vpcvolume.VolumeManager {
return vpcvolume.NewIKSVolumeService(s.client)
}
//IKSRegionalAPIClientProvider ...
type IKSRegionalAPIClientProvider struct {
RegionalAPIClientProvider
}
var _ RegionalAPIClientProvider = IKSRegionalAPIClientProvider{}
// New creates a new Session , using the supplied config
func (d IKSRegionalAPIClientProvider) New(config Config) (RegionalAPI, error) {
session, err := New(config)
if err != nil || session == nil {
return nil, err
}
iksSession := &IKSSession{
Session: *session,
}
return iksSession, err
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// CheckSnapshotTag checks if the given tag exists on a snapshot
func (ss *SnapshotService) CheckSnapshotTag(volumeID string, snapshotID string, tagName string, ctxLogger *zap.Logger) error {
ctxLogger.Debug("Entry Backend CreateSnapshotTag")
defer ctxLogger.Debug("Exit Backend CreateSnapshotTag")
defer util.TimeTracker("CheckSnapshotTag", time.Now())
operation := &client.Operation{
Name: "CheckSnapshotTag",
Method: "GET",
PathPattern: snapshotTagNamePath,
}
var apiErr models.Error
request := ss.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()))
req := request.PathParameter(volumeIDParam, volumeID).PathParameter(snapshotIDParam, snapshotID).PathParameter(snapshotTagParam, tagName).JSONError(&apiErr)
_, err := req.Invoke()
if err != nil {
return err
}
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// CheckVolumeTag checks if the given tag exists on a volume
func (vs *VolumeService) CheckVolumeTag(volumeID string, tagName string, ctxLogger *zap.Logger) error {
ctxLogger.Debug("Entry Backend CheckVolumeTag")
defer ctxLogger.Debug("Exit Backend CheckVolumeTag")
defer util.TimeTracker("CheckVolumeTag", time.Now())
operation := &client.Operation{
Name: "CheckVolumeTag",
Method: "GET",
PathPattern: volumeTagNamePath,
}
var apiErr models.Error
request := vs.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()), zap.Reflect("Operation", operation))
req := request.PathParameter(volumeIDParam, volumeID).PathParameter(volumeTagParam, tagName).JSONError(&apiErr)
_, err := req.Invoke()
if err != nil {
return err
}
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// CreateSnapshot POSTs to /volumes
func (ss *SnapshotService) CreateSnapshot(volumeID string, snapshotTemplate *models.Snapshot, ctxLogger *zap.Logger) (*models.Snapshot, error) {
ctxLogger.Debug("Entry Backend CreateSpanShot")
defer ctxLogger.Debug("Exit Backend CreateSnapshot")
defer util.TimeTracker("CreateSnapshot", time.Now())
operation := &client.Operation{
Name: "CreateSnapshot",
Method: "POST",
PathPattern: snapshotsPath,
}
var snapshot models.Snapshot
var apiErr models.Error
request := ss.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command and payload details", zap.Reflect("URL", request.URL()), zap.Reflect("Payload", snapshotTemplate), zap.Reflect("Operation", operation))
_, err := request.PathParameter(volumeIDParam, volumeID).JSONBody(snapshotTemplate).JSONSuccess(&snapshot).JSONError(&apiErr).Invoke()
if err != nil {
return nil, err
}
return &snapshot, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// CreateVolume POSTs to /volumes
func (vs *VolumeService) CreateVolume(volumeTemplate *models.Volume, ctxLogger *zap.Logger) (*models.Volume, error) {
ctxLogger.Debug("Entry Backend CreateVolume")
defer ctxLogger.Debug("Exit Backend CreateVolume")
defer util.TimeTracker("CreateVolume", time.Now())
operation := &client.Operation{
Name: "CreateVolume",
Method: "POST",
PathPattern: volumesPath,
}
var volume models.Volume
var apiErr models.Error
request := vs.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command and payload details", zap.Reflect("URL", request.URL()), zap.Reflect("Payload", volumeTemplate), zap.Reflect("Operation", operation))
_, err := request.JSONBody(volumeTemplate).JSONSuccess(&volume).JSONError(&apiErr).Invoke()
if err != nil {
return nil, err
}
return &volume, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// DeleteSnapshot DELETEs to /volumes
func (ss *SnapshotService) DeleteSnapshot(volumeID string, snapshotID string, ctxLogger *zap.Logger) error {
ctxLogger.Debug("Entry Backend DeleteSpanshot")
defer ctxLogger.Debug("Exit Backend DeleteSnapshot")
defer util.TimeTracker("DeleteSnapshot", time.Now())
operation := &client.Operation{
Name: "DeleteSnapshot",
Method: "DELETE",
PathPattern: snapshotIDPath,
}
var apiErr models.Error
request := ss.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()), zap.Reflect("Operation", operation))
_, err := request.PathParameter(volumeIDParam, volumeID).PathParameter(snapshotIDParam, snapshotID).JSONError(&apiErr).Invoke()
if err != nil {
return err
}
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// DeleteSnapshotTag deletes tag of a snapshot
func (ss *SnapshotService) DeleteSnapshotTag(volumeID string, snapshotID string, tagName string, ctxLogger *zap.Logger) error {
ctxLogger.Debug("Entry Backend DeleteSnapshotTag")
defer ctxLogger.Debug("Exit Backend DeleteSnapshotTag")
defer util.TimeTracker("DeleteSnapshotTag", time.Now())
operation := &client.Operation{
Name: "DeleteSnapshotTag",
Method: "DELETE",
PathPattern: snapshotTagNamePath,
}
var apiErr models.Error
request := ss.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()), zap.Reflect("Operation", operation))
req := request.PathParameter(volumeIDParam, volumeID).PathParameter(snapshotIDParam, snapshotID).PathParameter(snapshotTagParam, tagName).JSONError(&apiErr)
_, err := req.Invoke()
if err != nil {
return err
}
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// DeleteVolume POSTs to /volumes
func (vs *VolumeService) DeleteVolume(volumeID string, ctxLogger *zap.Logger) error {
ctxLogger.Debug("Entry Backend DeleteVolume")
defer ctxLogger.Debug("Exit Backend DeleteVolume")
defer util.TimeTracker("DeleteVolume", time.Now())
operation := &client.Operation{
Name: "DeleteVolume",
Method: "DELETE",
PathPattern: volumeIDPath,
}
var apiErr models.Error
request := vs.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()), zap.Reflect("Operation", operation))
_, err := request.PathParameter(volumeIDParam, volumeID).JSONError(&apiErr).Invoke()
if err != nil {
return err
}
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// DeleteVolumeTag deletes tag of a volume
func (vs *VolumeService) DeleteVolumeTag(volumeID string, tagName string, ctxLogger *zap.Logger) error {
ctxLogger.Debug("Entry Backend DeleteVolumeTag")
defer ctxLogger.Debug("Exit Backend DeleteVolumeTag")
defer util.TimeTracker("DeleteVolumeTag", time.Now())
operation := &client.Operation{
Name: "DeleteVolumeTag",
Method: "DELETE",
PathPattern: volumeTagNamePath,
}
var apiErr models.Error
request := vs.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()), zap.Reflect("Operation", operation))
req := request.PathParameter(volumeIDParam, volumeID).PathParameter(volumeTagParam, tagName).JSONError(&apiErr)
_, err := req.Invoke()
if err != nil {
return err
}
return nil
}
/**
* Copyright 2021 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// ExpandVolume PATCH to /volumes
func (vs *VolumeService) ExpandVolume(volumeID string, volumeTemplate *models.Volume, ctxLogger *zap.Logger) (*models.Volume, error) {
ctxLogger.Debug("Entry Backend ExpandVolume")
defer ctxLogger.Debug("Exit Backend ExpandVolume")
defer util.TimeTracker("ExpandVolume", time.Now())
operation := &client.Operation{
Name: "ExpandVolume",
Method: "PATCH",
PathPattern: volumeIDPath,
}
var volume models.Volume
var apiErr models.Error
request := vs.client.NewRequest(operation)
req := request.PathParameter(volumeIDParam, volumeID)
ctxLogger.Info("Equivalent curl command and payload details", zap.Reflect("URL", req.URL()), zap.Reflect("Payload", volumeTemplate), zap.Reflect("Operation", operation))
_, err := req.JSONBody(volumeTemplate).JSONSuccess(&volume).JSONError(&apiErr).Invoke()
if err != nil {
return nil, err
}
return &volume, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// GetSnapshot GETs from /volumes
func (ss *SnapshotService) GetSnapshot(volumeID string, snapshotID string, ctxLogger *zap.Logger) (*models.Snapshot, error) {
ctxLogger.Debug("Entry Backend GetSnapshot")
defer ctxLogger.Debug("Exit Backend GetSnapshot")
defer util.TimeTracker("GetSnapshot", time.Now())
operation := &client.Operation{
Name: "GetSnapshot",
Method: "GET",
PathPattern: snapshotIDPath,
}
var snapshot models.Snapshot
var apiErr models.Error
request := ss.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()), zap.Reflect("Operation", operation))
req := request.PathParameter(volumeIDParam, volumeID).PathParameter(snapshotIDParam, snapshotID)
_, err := req.JSONSuccess(&snapshot).JSONError(&apiErr).Invoke()
if err != nil {
return nil, err
}
return &snapshot, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// GetVolume POSTs to /volumes
func (vs *VolumeService) GetVolume(volumeID string, ctxLogger *zap.Logger) (*models.Volume, error) {
ctxLogger.Debug("Entry Backend GetVolume")
defer ctxLogger.Debug("Exit Backend GetVolume")
defer util.TimeTracker("GetVolume", time.Now())
operation := &client.Operation{
Name: "GetVolume",
Method: "GET",
PathPattern: volumeIDPath,
}
var volume models.Volume
var apiErr models.Error
request := vs.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()), zap.Reflect("Operation", operation))
req := request.PathParameter(volumeIDParam, volumeID)
_, err := req.JSONSuccess(&volume).JSONError(&apiErr).Invoke()
if err != nil {
return nil, err
}
return &volume, nil
}
// GetVolumeByName GETs /volumes
func (vs *VolumeService) GetVolumeByName(volumeName string, ctxLogger *zap.Logger) (*models.Volume, error) {
ctxLogger.Debug("Entry Backend GetVolumeByName")
defer ctxLogger.Debug("Exit Backend GetVolumeByName")
defer util.TimeTracker("GetVolumeByName", time.Now())
// Get the volume details for a single volume, ListVolumeFilters will return only 1 volume in list
filters := &models.ListVolumeFilters{VolumeName: volumeName}
volumes, err := vs.ListVolumes(1, "", filters, ctxLogger)
if err != nil {
return nil, err
}
if volumes != nil {
volumeslist := volumes.Volumes
if len(volumeslist) > 0 {
return volumeslist[0], nil
}
}
return nil, err
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// UpdateVolume POSTs to /volumes
func (vs *IKSVolumeService) UpdateVolume(volumeTemplate *models.Volume, ctxLogger *zap.Logger) error {
ctxLogger.Debug("Entry Backend IKSVolumeService.UpdateVolume")
defer ctxLogger.Debug("Exit Backend IKSVolumeService.UpdateVolume")
defer util.TimeTracker("IKSVolumeService.UpdateVolume", time.Now())
operation := &client.Operation{
Name: "UpdateVolume",
Method: "POST",
PathPattern: vs.pathPrefix + updateVolume,
}
apiErr := vs.receiverError
request := vs.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()), zap.Reflect("Operation", operation), zap.Reflect("volumeTemplate", volumeTemplate))
_, err := request.JSONBody(volumeTemplate).JSONError(apiErr).Invoke()
if err != nil {
ctxLogger.Error("Update volume failed with error", zap.Error(err), zap.Error(apiErr))
}
return err
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
)
const (
//IksV2PathPrefix ...
IksV2PathPrefix = "v2/storage/"
)
// IKSVolumeService ...
type IKSVolumeService struct {
VolumeService
pathPrefix string
receiverError error
}
var _ VolumeManager = &IKSVolumeService{}
// NewIKSVolumeService ...
func NewIKSVolumeService(client client.SessionClient) VolumeManager {
err := models.IksError{}
iksVolumeService := &IKSVolumeService{
VolumeService: VolumeService{
client: client,
},
pathPrefix: IksV2PathPrefix,
receiverError: &err,
}
return iksVolumeService
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// ListSnapshotTags GETs /volumes/snapshots/tags
func (ss *SnapshotService) ListSnapshotTags(volumeID string, snapshotID string, ctxLogger *zap.Logger) (*[]string, error) {
ctxLogger.Debug("Entry Backend ListSnapshotTags")
defer ctxLogger.Debug("Exit Backend ListSnapshotTags")
defer util.TimeTracker("ListSnapshotTags", time.Now())
operation := &client.Operation{
Name: "ListSnapshotTags",
Method: "GET",
PathPattern: snapshotTagsPath,
}
var tags []string
var apiErr models.Error
request := ss.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()), zap.Reflect("Operation", operation))
req := request.PathParameter(volumeIDParam, volumeID).PathParameter(snapshotIDParam, snapshotID).JSONSuccess(&tags).JSONError(&apiErr)
_, err := req.Invoke()
if err != nil {
return nil, err
}
return &tags, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// ListSnapshots GETs /volumes/snapshots
func (ss *SnapshotService) ListSnapshots(volumeID string, ctxLogger *zap.Logger) (*models.SnapshotList, error) {
ctxLogger.Debug("Entry Backend ListSnapshots")
defer ctxLogger.Debug("Exit Backend ListSnapshots")
defer util.TimeTracker("ListSnapshots", time.Now())
operation := &client.Operation{
Name: "ListSnapshots",
Method: "GET",
PathPattern: snapshotsPath,
}
var snapshots models.SnapshotList
var apiErr models.Error
request := ss.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()), zap.Reflect("Operation", operation))
_, err := request.PathParameter(volumeIDParam, volumeID).JSONSuccess(&snapshots).JSONError(&apiErr).Invoke()
if err != nil {
return nil, err
}
return &snapshots, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// ListVolumeTags GETs /volumes/tags
func (vs *VolumeService) ListVolumeTags(volumeID string, ctxLogger *zap.Logger) (*[]string, error) {
ctxLogger.Debug("Entry Backend ListVolumeTags")
defer ctxLogger.Debug("Exit Backend ListVolumeTags")
defer util.TimeTracker("ListVolumeTags", time.Now())
operation := &client.Operation{
Name: "ListVolumeTags",
Method: "GET",
PathPattern: volumeTagsPath,
}
var tags []string
var apiErr models.Error
request := vs.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()), zap.Reflect("Operation", operation))
req := request.PathParameter(volumeIDParam, volumeID).JSONSuccess(&tags).JSONError(&apiErr)
_, err := req.Invoke()
if err != nil {
return nil, err
}
return &tags, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"strconv"
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// ListVolumes GETs /volumes
func (vs *VolumeService) ListVolumes(limit int, start string, filters *models.ListVolumeFilters, ctxLogger *zap.Logger) (*models.VolumeList, error) {
ctxLogger.Debug("Entry Backend ListVolumes")
defer ctxLogger.Debug("Exit Backend ListVolumes")
defer util.TimeTracker("ListVolumes", time.Now())
operation := &client.Operation{
Name: "ListVolumes",
Method: "GET",
PathPattern: volumesPath,
}
var volumes models.VolumeList
var apiErr models.Error
request := vs.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()), zap.Reflect("Operation", operation))
req := request.JSONSuccess(&volumes).JSONError(&apiErr)
if limit > 0 {
req.AddQueryValue("limit", strconv.Itoa(limit))
}
if start != "" {
req.AddQueryValue("start", start)
}
if filters != nil {
if filters.ResourceGroupID != "" {
req.AddQueryValue("resource_group.id", filters.ResourceGroupID)
}
if filters.Tag != "" {
req.AddQueryValue("tag", filters.Tag)
}
if filters.ZoneName != "" {
req.AddQueryValue("zone.name", filters.ZoneName)
}
if filters.VolumeName != "" {
req.AddQueryValue("name", filters.VolumeName)
}
}
_, err := req.Invoke()
if err != nil {
return nil, err
}
return &volumes, nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// SetSnapshotTag sets tag for a snapshot
func (ss *SnapshotService) SetSnapshotTag(volumeID string, snapshotID string, tagName string, ctxLogger *zap.Logger) error {
ctxLogger.Debug("Entry Backend SetVolumeTag")
defer ctxLogger.Debug("Exit Backend SetVolumeTag")
defer util.TimeTracker("SetSnapshotTag", time.Now())
operation := &client.Operation{
Name: "SetSnapshotTag",
Method: "PUT",
PathPattern: snapshotTagNamePath,
}
var apiErr models.Error
request := ss.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()), zap.Reflect("Operation", operation))
req := request.PathParameter(volumeIDParam, volumeID).PathParameter(snapshotIDParam, snapshotID).PathParameter(snapshotTagParam, tagName).JSONError(&apiErr)
_, err := req.Invoke()
if err != nil {
return err
}
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"time"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// SetVolumeTag sets tag for a volume
func (vs *VolumeService) SetVolumeTag(volumeID string, tagName string, ctxLogger *zap.Logger) error {
ctxLogger.Debug("Entry Backend SetVolumeTag")
defer ctxLogger.Debug("Exit Backend SetVolumeTag")
defer util.TimeTracker("SetVolumeTag", time.Now())
operation := &client.Operation{
Name: "SetVolumeTag",
Method: "PUT",
PathPattern: volumeTagNamePath,
}
var apiErr models.Error
request := vs.client.NewRequest(operation)
ctxLogger.Info("Equivalent curl command", zap.Reflect("URL", request.URL()), zap.Reflect("Operation", operation))
req := request.PathParameter(volumeIDParam, volumeID).PathParameter(volumeTagParam, tagName).JSONError(&apiErr)
_, err := req.Invoke()
if err != nil {
return err
}
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// SnapshotManager operations
type SnapshotManager interface {
// Create the snapshot on the volume
CreateSnapshot(volumeID string, snapshotTemplate *models.Snapshot, ctxLogger *zap.Logger) (*models.Snapshot, error)
// Delete the snapshot
DeleteSnapshot(volumeID string, snapshotID string, ctxLogger *zap.Logger) error
// Get the snapshot
GetSnapshot(volumeID string, snapshotID string, ctxLogger *zap.Logger) (*models.Snapshot, error)
// List all the snapshots for a given volume
ListSnapshots(volumeID string, ctxLogger *zap.Logger) (*models.SnapshotList, error)
// Set tag for a snapshot
SetSnapshotTag(volumeID string, snapshotID string, tagName string, ctxLogger *zap.Logger) error
// Delete tag of a snapshot
DeleteSnapshotTag(volumeID string, snapshotID string, tagName string, ctxLogger *zap.Logger) error
// List all tags of a snapshot
ListSnapshotTags(volumeID string, snapshotID string, ctxLogger *zap.Logger) (*[]string, error)
// Check if the given tag exists on a snapshot
CheckSnapshotTag(volumeID string, snapshotID string, tagName string, ctxLogger *zap.Logger) error
}
// SnapshotService ...
type SnapshotService struct {
client client.SessionClient
}
var _ SnapshotManager = &SnapshotService{}
// NewSnapshotManager ...
func NewSnapshotManager(client client.SessionClient) SnapshotManager {
return &SnapshotService{
client: client,
}
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"errors"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// UpdateVolume POSTs to /volumes. Riaas/VPC does have volume update support yet
func (vs *VolumeService) UpdateVolume(volumeTemplate *models.Volume, ctxLogger *zap.Logger) error {
return errors.New("unsupported Operation")
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package vpcvolume ...
package vpcvolume
import (
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/client"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// VolumeManager operations
type VolumeManager interface {
// Create the volume with authorisation by passing required information in the volume object
CreateVolume(volumeTemplate *models.Volume, ctxLogger *zap.Logger) (*models.Volume, error)
// UpdateVolume updates the volume with authorisation by passing required information in the volume object
UpdateVolume(volumeTemplate *models.Volume, ctxLogger *zap.Logger) error
// ExpandVolume ...
ExpandVolume(volumeID string, volumeTemplate *models.Volume, ctxLogger *zap.Logger) (*models.Volume, error)
// Delete the volume
DeleteVolume(volumeID string, ctxLogger *zap.Logger) error
// Get the volume by using ID
GetVolume(volumeID string, ctxLogger *zap.Logger) (*models.Volume, error)
// Get the volume by using volume name
GetVolumeByName(volumeName string, ctxLogger *zap.Logger) (*models.Volume, error)
// Others
// Get volume lists by using snapshot tags
ListVolumes(limit int, start string, filters *models.ListVolumeFilters, ctxLogger *zap.Logger) (*models.VolumeList, error)
// Set tag for a volume
SetVolumeTag(volumeID string, tagName string, ctxLogger *zap.Logger) error
// Delete tag of a volume
DeleteVolumeTag(volumeID string, tagName string, ctxLogger *zap.Logger) error
// List all tags of a volume
ListVolumeTags(volumeID string, ctxLogger *zap.Logger) (*[]string, error)
// Check if the given tag exists on a volume
CheckVolumeTag(volumeID string, tagName string, ctxLogger *zap.Logger) error
}
// VolumeService ...
type VolumeService struct {
client client.SessionClient
}
var _ VolumeManager = &VolumeService{}
// New ...
func New(client client.SessionClient) VolumeManager {
return &VolumeService{
client: client,
}
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"context"
"errors"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
util "github.com/IBM/ibmcloud-volume-interface/lib/utils"
utilReasonCode "github.com/IBM/ibmcloud-volume-interface/lib/utils/reasoncode"
"github.com/IBM/ibmcloud-volume-interface/provider/local"
vpcprovider "github.com/IBM/ibmcloud-volume-vpc/block/provider"
vpcconfig "github.com/IBM/ibmcloud-volume-vpc/block/vpcconfig"
vpcauth "github.com/IBM/ibmcloud-volume-vpc/common/auth"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/riaas"
"go.uber.org/zap"
)
//IksVpcBlockProvider handles both IKS and RIAAS sessions
type IksVpcBlockProvider struct {
vpcprovider.VPCBlockProvider
vpcBlockProvider *vpcprovider.VPCBlockProvider // Holds VPC provider. Requires to avoid recursive calls
iksBlockProvider *vpcprovider.VPCBlockProvider // Holds IKS provider
}
var _ local.Provider = &IksVpcBlockProvider{}
//NewProvider handles both IKS and RIAAS sessions
func NewProvider(conf *vpcconfig.VPCBlockConfig, logger *zap.Logger) (local.Provider, error) {
//Setup vpc provider
provider, _ := vpcprovider.NewProvider(conf, logger)
vpcBlockProvider, _ := provider.(*vpcprovider.VPCBlockProvider)
// Setup IKS provider
provider, _ = vpcprovider.NewProvider(conf, logger)
iksBlockProvider, _ := provider.(*vpcprovider.VPCBlockProvider)
//Overrider Base URL
iksBlockProvider.APIConfig.BaseURL = conf.VPCConfig.IKSTokenExchangePrivateURL
// Setup IKS-VPC dual provider
iksVpcBlockProvider := &IksVpcBlockProvider{
VPCBlockProvider: *vpcBlockProvider,
vpcBlockProvider: vpcBlockProvider,
iksBlockProvider: iksBlockProvider,
}
//vpcBlockProvider.ApiConfig.BaseURL = conf.VPC.IKSTokenExchangePrivateURL
return iksVpcBlockProvider, nil
}
// OpenSession opens a session on the provider
func (iksp *IksVpcBlockProvider) OpenSession(ctx context.Context, contextCredentials provider.ContextCredentials, ctxLogger *zap.Logger) (provider.Session, error) {
ctxLogger.Info("Entering IksVpcBlockProvider.OpenSession")
defer func() {
ctxLogger.Debug("Exiting IksVpcBlockProvider.OpenSession")
}()
ctxLogger.Info("Opening VPC block session")
ccf, _ := iksp.vpcBlockProvider.ContextCredentialsFactory(nil)
ctxLogger.Info("Its IKS dual session. Getttng IAM token for VPC block session")
vpcContextCredentials, err := ccf.ForIAMAccessToken(iksp.iksBlockProvider.Config.VPCConfig.APIKey, ctxLogger)
if err != nil {
ctxLogger.Error("Error occurred while generating IAM token for VPC", zap.Error(err))
if util.ErrorReasonCode(err) == utilReasonCode.EndpointNotReachable {
userErr := userError.GetUserError(string(userError.EndpointNotReachable), err)
return nil, userErr
}
if util.ErrorReasonCode(err) == utilReasonCode.Timeout {
userErr := userError.GetUserError(string(userError.Timeout), err)
return nil, userErr
}
return nil, err
}
session, err := iksp.vpcBlockProvider.OpenSession(ctx, vpcContextCredentials, ctxLogger)
if err != nil {
ctxLogger.Error("Error occurred while opening VPCSession", zap.Error(err))
return nil, err
}
vpcSession, _ := session.(*vpcprovider.VPCSession)
ctxLogger.Info("Opening IKS block session")
//Create ContextCredentialsFactory
ccf, err = iksp.ContextCredentialsFactory(nil)
if err != nil {
ctxLogger.Error("Error while creating the ContextCredentialsFactory", zap.Error(err))
return nil, err
}
iksp.iksBlockProvider.ContextCF = ccf
iksp.iksBlockProvider.ClientProvider = riaas.IKSRegionalAPIClientProvider{}
ctxLogger.Info("Its ISK dual session. Getttng IAM token for IKS block session")
iksContextCredentials, err := ccf.ForIAMAccessToken(iksp.iksBlockProvider.Config.VPCConfig.APIKey, ctxLogger)
if err != nil {
ctxLogger.Warn("Error occurred while generating IAM token for IKS. But continue with VPC session alone. \n Volume Mount operation will fail but volume provisioning will work", zap.Error(err))
session = &vpcprovider.VPCSession{
Logger: ctxLogger,
SessionError: err,
} // Empty session to avoid Nil references.
} else {
session, err = iksp.iksBlockProvider.OpenSession(ctx, iksContextCredentials, ctxLogger)
if err != nil {
ctxLogger.Error("Error occurred while opening IKSSession", zap.Error(err))
}
}
iksSession, ok := session.(*vpcprovider.VPCSession)
if ok && iksSession.Apiclient != nil {
iksSession.APIClientVolAttachMgr = iksSession.Apiclient.IKSVolumeAttachService()
}
// Setup Dual Session that handles for VPC and IKS connections
vpcIksSession := IksVpcSession{
VPCSession: *vpcSession,
IksSession: iksSession,
}
ctxLogger.Debug("IksVpcSession", zap.Reflect("IksVpcSession", vpcIksSession))
return &vpcIksSession, nil
}
// ContextCredentialsFactory ...
func (iksp *IksVpcBlockProvider) ContextCredentialsFactory(zone *string) (local.ContextCredentialsFactory, error) {
return vpcauth.NewVPCContextCredentialsFactory(iksp.vpcBlockProvider.Config)
}
// UpdateAPIKey ...
func (iksp *IksVpcBlockProvider) UpdateAPIKey(conf interface{}, logger *zap.Logger) error {
logger.Info("Updating api key in iks vpc provider")
vpcConfig, ok := conf.(*vpcconfig.VPCBlockConfig)
if !ok {
logger.Error("Error fetching vpc block config from interface")
return errors.New("error unmarshaling vpc block config")
}
if iksp.vpcBlockProvider == nil {
logger.Error("VPC Block provider not initialized, hence unable to update api key")
return errors.New("vpc block provider not initialized")
}
err := iksp.vpcBlockProvider.UpdateAPIKey(vpcConfig, logger)
if err != nil {
logger.Error("Error updating api key in vpc block provider", zap.Error(err))
return err
}
err = iksp.iksBlockProvider.UpdateAPIKey(vpcConfig, logger)
if err != nil {
logger.Error("Error updating api key in iks block provider", zap.Error(err))
return err
}
iksp.VPCBlockProvider = *iksp.vpcBlockProvider
return nil
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"net/http"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
vpcprovider "github.com/IBM/ibmcloud-volume-vpc/block/provider"
)
// IksVpcSession implements lib.Session for VPC IKS dual session
type IksVpcSession struct {
vpcprovider.VPCSession // Holds VPC/Riaas session by default
IksSession *vpcprovider.VPCSession // Holds IKS session
}
var _ provider.Session = &IksVpcSession{}
const (
// Provider storage provider
Provider = provider.VolumeProvider("IKS-VPC-Block")
// VolumeType ...
VolumeType = provider.VolumeType("VPC-Block")
)
// Close at present does nothing
func (vpcIks *IksVpcSession) Close() {
// Do nothing for now
}
// GetProviderDisplayName returns the name of the VPC provider
func (vpcIks *IksVpcSession) GetProviderDisplayName() provider.VolumeProvider {
return Provider
}
// ProviderName ...
func (vpcIks *IksVpcSession) ProviderName() provider.VolumeProvider {
return Provider
}
// Type ...
func (vpcIks *IksVpcSession) Type() provider.VolumeType {
return VolumeType
}
// AttachVolume attach volume based on given volume attachment request
func (vpcIks *IksVpcSession) AttachVolume(volumeAttachmentRequest provider.VolumeAttachmentRequest) (*provider.VolumeAttachmentResponse, error) {
vpcIks.Logger.Debug("Entry of IksVpcSession.AttachVolume method...")
defer vpcIks.Logger.Debug("Exit from IksVpcSession.AttachVolume method...")
return vpcIks.IksSession.AttachVolume(volumeAttachmentRequest)
}
// DetachVolume attach volume based on given volume attachment request
func (vpcIks *IksVpcSession) DetachVolume(volumeAttachmentRequest provider.VolumeAttachmentRequest) (*http.Response, error) {
vpcIks.IksSession.Logger.Debug("Entry of IksVpcSession.DetachVolume method...")
defer vpcIks.Logger.Debug("Exit from IksVpcSession.DetachVolume method...")
return vpcIks.IksSession.DetachVolume(volumeAttachmentRequest)
}
// GetVolumeAttachment attach volume based on given volume attachment request
func (vpcIks *IksVpcSession) GetVolumeAttachment(volumeAttachmentRequest provider.VolumeAttachmentRequest) (*provider.VolumeAttachmentResponse, error) {
vpcIks.Logger.Debug("Entry of IksVpcSession.GetVolumeAttachment method...")
defer vpcIks.Logger.Debug("Exit from IksVpcSession.GetVolumeAttachment method...")
return vpcIks.IksSession.GetVolumeAttachment(volumeAttachmentRequest)
}
// WaitForAttachVolume attach volume based on given volume attachment request
func (vpcIks *IksVpcSession) WaitForAttachVolume(volumeAttachmentRequest provider.VolumeAttachmentRequest) (*provider.VolumeAttachmentResponse, error) {
vpcIks.Logger.Debug("Entry of IksVpcSession.WaitForAttachVolume method...")
defer vpcIks.Logger.Debug("Exit from IksVpcSession.WaitForAttachVolume method...")
return vpcIks.IksSession.WaitForAttachVolume(volumeAttachmentRequest)
}
// WaitForDetachVolume attach volume based on given volume attachment request
func (vpcIks *IksVpcSession) WaitForDetachVolume(volumeAttachmentRequest provider.VolumeAttachmentRequest) error {
vpcIks.Logger.Debug("Entry of IksVpcSession.WaitForDetachVolume method...")
defer vpcIks.Logger.Debug("Exit from IksVpcSession.WaitForDetachVolume method...")
return vpcIks.IksSession.WaitForDetachVolume(volumeAttachmentRequest)
}
/**
* Copyright 2020 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package provider ...
package provider
import (
"time"
"github.com/IBM/ibmcloud-volume-interface/lib/metrics"
"github.com/IBM/ibmcloud-volume-interface/lib/provider"
vpc_provider "github.com/IBM/ibmcloud-volume-vpc/block/provider"
userError "github.com/IBM/ibmcloud-volume-vpc/common/messages"
"github.com/IBM/ibmcloud-volume-vpc/common/vpcclient/models"
"go.uber.org/zap"
)
// UpdateVolume updates the volume with given information
func (vpcIks *IksVpcSession) UpdateVolume(volumeRequest provider.Volume) (err error) {
vpcIks.Logger.Debug("Entry of UpdateVolume method...")
defer vpcIks.Logger.Debug("Exit from UpdateVolume method...")
defer metrics.UpdateDurationFromStart(vpcIks.Logger, "UpdateVolume", time.Now())
vpcIks.Logger.Info("Basic validation for UpdateVolume request... ", zap.Reflect("RequestedVolumeDetails", volumeRequest))
// Build the template to send to backend
volumeTemplate := models.NewVolume(volumeRequest)
err = validateVolumeRequest(volumeRequest)
if err != nil {
return err
}
vpcIks.Logger.Info("Successfully validated inputs for UpdateVolume request... ")
vpcIks.Logger.Info("Calling provider for volume update...")
err = vpcIks.APIRetry.FlexyRetry(vpcIks.Logger, func() (error, bool) {
err = vpcIks.IksSession.Apiclient.VolumeService().UpdateVolume(&volumeTemplate, vpcIks.Logger)
return err, err == nil || vpc_provider.SkipRetryForIKS(err)
})
if err != nil {
vpcIks.Logger.Debug("Failed to update volume", zap.Reflect("BackendError", err))
return userError.GetUserError("UpdateFailed", err)
}
return err
}
// validateVolumeRequest validating volume request
func validateVolumeRequest(volumeRequest provider.Volume) error {
// Volume name should not be empty
if len(volumeRequest.VolumeID) == 0 {
return userError.GetUserError("ErrorRequiredFieldMissing", nil, "VolumeID")
}
// Provider name should not be empty
if len(volumeRequest.Provider) == 0 {
return userError.GetUserError("ErrorRequiredFieldMissing", nil, "Provider")
}
// VolumeType should not be empty
if len(volumeRequest.VolumeType) == 0 {
return userError.GetUserError("ErrorRequiredFieldMissing", nil, "VolumeType")
}
return nil
}