2018-01-09 18:57:14 +00:00
/ *
Copyright 2017 The Kubernetes Authors .
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 hostpath
import (
2018-07-18 14:47:22 +00:00
"fmt"
2018-11-26 18:23:56 +00:00
"math"
2018-01-09 18:57:14 +00:00
"os"
2018-11-26 18:23:56 +00:00
"sort"
"strconv"
"github.com/golang/protobuf/ptypes"
2018-01-09 18:57:14 +00:00
"github.com/golang/glog"
"github.com/pborman/uuid"
"golang.org/x/net/context"
2018-02-15 13:50:31 +00:00
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
2018-01-09 18:57:14 +00:00
2018-11-26 18:23:56 +00:00
"github.com/container-storage-interface/spec/lib/go/csi"
2018-01-09 18:57:14 +00:00
"github.com/kubernetes-csi/drivers/pkg/csi-common"
2018-11-26 18:23:56 +00:00
utilexec "k8s.io/utils/exec"
2018-01-09 18:57:14 +00:00
)
const (
2018-07-18 14:47:22 +00:00
deviceID = "deviceID"
provisionRoot = "/tmp/"
2018-11-26 18:23:56 +00:00
snapshotRoot = "/tmp/"
2018-07-18 14:47:22 +00:00
maxStorageCapacity = tib
2018-01-09 18:57:14 +00:00
)
type controllerServer struct {
* csicommon . DefaultControllerServer
}
func ( cs * controllerServer ) CreateVolume ( ctx context . Context , req * csi . CreateVolumeRequest ) ( * csi . CreateVolumeResponse , error ) {
2018-07-18 14:47:22 +00:00
if err := cs . Driver . ValidateControllerServiceRequest ( csi . ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME ) ; err != nil {
glog . V ( 3 ) . Infof ( "invalid create volume req: %v" , req )
return nil , err
}
2018-02-15 13:50:31 +00:00
// Check arguments
if len ( req . GetName ( ) ) == 0 {
return nil , status . Error ( codes . InvalidArgument , "Name missing in request" )
}
if req . GetVolumeCapabilities ( ) == nil {
return nil , status . Error ( codes . InvalidArgument , "Volume Capabilities missing in request" )
}
2018-07-18 14:47:22 +00:00
// Need to check for already existing volume name, and if found
// check for the requested capacity and already allocated capacity
if exVol , err := getVolumeByName ( req . GetName ( ) ) ; err == nil {
// Since err is nil, it means the volume with the same name already exists
// need to check if the size of exisiting volume is the same as in new
// request
if exVol . VolSize >= int64 ( req . GetCapacityRange ( ) . GetRequiredBytes ( ) ) {
// exisiting volume is compatible with new request and should be reused.
// TODO (sbezverk) Do I need to make sure that RBD volume still exists?
return & csi . CreateVolumeResponse {
Volume : & csi . Volume {
2018-11-26 18:23:56 +00:00
VolumeId : exVol . VolID ,
2018-07-18 14:47:22 +00:00
CapacityBytes : int64 ( exVol . VolSize ) ,
2018-11-26 18:23:56 +00:00
VolumeContext : req . GetParameters ( ) ,
2018-07-18 14:47:22 +00:00
} ,
} , nil
}
return nil , status . Error ( codes . AlreadyExists , fmt . Sprintf ( "Volume with the same name: %s but with different size already exist" , req . GetName ( ) ) )
2018-01-09 18:57:14 +00:00
}
2018-07-18 14:47:22 +00:00
// Check for maximum available capacity
capacity := int64 ( req . GetCapacityRange ( ) . GetRequiredBytes ( ) )
if capacity >= maxStorageCapacity {
return nil , status . Errorf ( codes . OutOfRange , "Requested capacity %d exceeds maximum allowed %d" , capacity , maxStorageCapacity )
}
volumeID := uuid . NewUUID ( ) . String ( )
path := provisionRoot + volumeID
2018-01-09 18:57:14 +00:00
err := os . MkdirAll ( path , 0777 )
if err != nil {
glog . V ( 3 ) . Infof ( "failed to create volume: %v" , err )
return nil , err
}
2018-11-26 18:23:56 +00:00
if req . GetVolumeContentSource ( ) != nil {
contentSource := req . GetVolumeContentSource ( )
if contentSource . GetSnapshot ( ) != nil {
snapshotId := contentSource . GetSnapshot ( ) . GetSnapshotId ( )
snapshot , ok := hostPathVolumeSnapshots [ snapshotId ]
if ! ok {
return nil , status . Errorf ( codes . NotFound , "cannot find snapshot %v" , snapshotId )
}
if snapshot . ReadyToUse != true {
return nil , status . Errorf ( codes . Internal , "Snapshot %v is not yet ready to use." , snapshotId )
}
snapshotPath := snapshot . Path
args := [ ] string { "zxvf" , snapshotPath , "-C" , path }
executor := utilexec . New ( )
out , err := executor . Command ( "tar" , args ... ) . CombinedOutput ( )
if err != nil {
return nil , status . Error ( codes . Internal , fmt . Sprintf ( "failed pre-populate data for volume: %v: %s" , err , out ) )
}
}
}
2018-01-09 18:57:14 +00:00
glog . V ( 4 ) . Infof ( "create volume %s" , path )
2018-07-18 14:47:22 +00:00
hostPathVol := hostPathVolume { }
hostPathVol . VolName = req . GetName ( )
hostPathVol . VolID = volumeID
hostPathVol . VolSize = capacity
hostPathVol . VolPath = path
hostPathVolumes [ volumeID ] = hostPathVol
2018-01-09 18:57:14 +00:00
return & csi . CreateVolumeResponse {
2018-02-15 13:50:31 +00:00
Volume : & csi . Volume {
2018-11-26 18:23:56 +00:00
VolumeId : volumeID ,
2018-03-06 22:33:18 +00:00
CapacityBytes : req . GetCapacityRange ( ) . GetRequiredBytes ( ) ,
2018-11-26 18:23:56 +00:00
VolumeContext : req . GetParameters ( ) ,
2018-01-09 18:57:14 +00:00
} ,
} , nil
}
func ( cs * controllerServer ) DeleteVolume ( ctx context . Context , req * csi . DeleteVolumeRequest ) ( * csi . DeleteVolumeResponse , error ) {
2018-02-15 13:50:31 +00:00
// Check arguments
if len ( req . GetVolumeId ( ) ) == 0 {
return nil , status . Error ( codes . InvalidArgument , "Volume ID missing in request" )
}
2018-03-06 22:33:18 +00:00
if err := cs . Driver . ValidateControllerServiceRequest ( csi . ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME ) ; err != nil {
2018-01-09 18:57:14 +00:00
glog . V ( 3 ) . Infof ( "invalid delete volume req: %v" , req )
return nil , err
}
2018-07-18 14:47:22 +00:00
volumeID := req . VolumeId
glog . V ( 4 ) . Infof ( "deleting volume %s" , volumeID )
path := provisionRoot + volumeID
2018-01-09 18:57:14 +00:00
os . RemoveAll ( path )
2018-07-18 14:47:22 +00:00
delete ( hostPathVolumes , volumeID )
2018-01-09 18:57:14 +00:00
return & csi . DeleteVolumeResponse { } , nil
}
func ( cs * controllerServer ) ValidateVolumeCapabilities ( ctx context . Context , req * csi . ValidateVolumeCapabilitiesRequest ) ( * csi . ValidateVolumeCapabilitiesResponse , error ) {
2018-11-26 18:23:56 +00:00
return cs . DefaultControllerServer . ValidateVolumeCapabilities ( ctx , req )
}
2018-02-15 13:50:31 +00:00
2018-11-26 18:23:56 +00:00
// CreateSnapshot uses tar command to create snapshot for hostpath volume. The tar command can quickly create
// archives of entire directories. The host image must have "tar" binaries in /bin, /usr/sbin, or /usr/bin.
func ( cs * controllerServer ) CreateSnapshot ( ctx context . Context , req * csi . CreateSnapshotRequest ) ( * csi . CreateSnapshotResponse , error ) {
if err := cs . Driver . ValidateControllerServiceRequest ( csi . ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT ) ; err != nil {
glog . V ( 3 ) . Infof ( "invalid create snapshot req: %v" , req )
return nil , err
}
if len ( req . GetName ( ) ) == 0 {
return nil , status . Error ( codes . InvalidArgument , "Name missing in request" )
}
2018-02-15 13:50:31 +00:00
// Check arguments
2018-11-26 18:23:56 +00:00
if len ( req . GetSourceVolumeId ( ) ) == 0 {
return nil , status . Error ( codes . InvalidArgument , "SourceVolumeId missing in request" )
2018-02-15 13:50:31 +00:00
}
2018-11-26 18:23:56 +00:00
// Need to check for already existing snapshot name, and if found check for the
// requested sourceVolumeId and sourceVolumeId of snapshot that has been created.
if exSnap , err := getSnapshotByName ( req . GetName ( ) ) ; err == nil {
// Since err is nil, it means the snapshot with the same name already exists need
// to check if the sourceVolumeId of existing snapshot is the same as in new request.
if exSnap . VolID == req . GetSourceVolumeId ( ) {
// same snapshot has been created.
return & csi . CreateSnapshotResponse {
Snapshot : & csi . Snapshot {
SnapshotId : exSnap . Id ,
SourceVolumeId : exSnap . VolID ,
CreationTime : & exSnap . CreationTime ,
SizeBytes : exSnap . SizeBytes ,
ReadyToUse : exSnap . ReadyToUse ,
} ,
} , nil
}
return nil , status . Error ( codes . AlreadyExists , fmt . Sprintf ( "snapshot with the same name: %s but with different SourceVolumeId already exist" , req . GetName ( ) ) )
}
volumeID := req . GetSourceVolumeId ( )
hostPathVolume , ok := hostPathVolumes [ volumeID ]
if ! ok {
return nil , status . Error ( codes . Internal , "volumeID is not exist" )
}
snapshotID := uuid . NewUUID ( ) . String ( )
creationTime := ptypes . TimestampNow ( )
volPath := hostPathVolume . VolPath
file := snapshotRoot + snapshotID + ".tgz"
args := [ ] string { "czf" , file , "-C" , volPath , "." }
executor := utilexec . New ( )
out , err := executor . Command ( "tar" , args ... ) . CombinedOutput ( )
if err != nil {
return nil , status . Error ( codes . Internal , fmt . Sprintf ( "failed create snapshot: %v: %s" , err , out ) )
}
glog . V ( 4 ) . Infof ( "create volume snapshot %s" , file )
snapshot := hostPathSnapshot { }
snapshot . Name = req . GetName ( )
snapshot . Id = snapshotID
snapshot . VolID = volumeID
snapshot . Path = file
snapshot . CreationTime = * creationTime
snapshot . SizeBytes = hostPathVolume . VolSize
snapshot . ReadyToUse = true
hostPathVolumeSnapshots [ snapshotID ] = snapshot
return & csi . CreateSnapshotResponse {
Snapshot : & csi . Snapshot {
SnapshotId : snapshot . Id ,
SourceVolumeId : snapshot . VolID ,
CreationTime : & snapshot . CreationTime ,
SizeBytes : snapshot . SizeBytes ,
ReadyToUse : snapshot . ReadyToUse ,
} ,
} , nil
}
func ( cs * controllerServer ) DeleteSnapshot ( ctx context . Context , req * csi . DeleteSnapshotRequest ) ( * csi . DeleteSnapshotResponse , error ) {
// Check arguments
if len ( req . GetSnapshotId ( ) ) == 0 {
return nil , status . Error ( codes . InvalidArgument , "Snapshot ID missing in request" )
}
if err := cs . Driver . ValidateControllerServiceRequest ( csi . ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT ) ; err != nil {
glog . V ( 3 ) . Infof ( "invalid delete snapshot req: %v" , req )
return nil , err
}
snapshotID := req . GetSnapshotId ( )
glog . V ( 4 ) . Infof ( "deleting volume %s" , snapshotID )
path := snapshotRoot + snapshotID + ".tgz"
os . RemoveAll ( path )
delete ( hostPathVolumeSnapshots , snapshotID )
return & csi . DeleteSnapshotResponse { } , nil
}
func ( cs * controllerServer ) ListSnapshots ( ctx context . Context , req * csi . ListSnapshotsRequest ) ( * csi . ListSnapshotsResponse , error ) {
if err := cs . Driver . ValidateControllerServiceRequest ( csi . ControllerServiceCapability_RPC_LIST_SNAPSHOTS ) ; err != nil {
glog . V ( 3 ) . Infof ( "invalid list snapshot req: %v" , req )
return nil , err
}
// case 1: SnapshotId is not empty, return snapshots that match the snapshot id.
if len ( req . GetSnapshotId ( ) ) != 0 {
snapshotID := req . SnapshotId
if snapshot , ok := hostPathVolumeSnapshots [ snapshotID ] ; ok {
return convertSnapshot ( snapshot ) , nil
}
}
// case 2: SourceVolumeId is not empty, return snapshots that match the source volume id.
if len ( req . GetSourceVolumeId ( ) ) != 0 {
for _ , snapshot := range hostPathVolumeSnapshots {
if snapshot . VolID == req . SourceVolumeId {
return convertSnapshot ( snapshot ) , nil
}
}
2018-02-15 13:50:31 +00:00
}
2018-11-26 18:23:56 +00:00
var snapshots [ ] csi . Snapshot
// case 3: no parameter is set, so we return all the snapshots.
sortedKeys := make ( [ ] string , 0 )
for k := range hostPathVolumeSnapshots {
sortedKeys = append ( sortedKeys , k )
2018-07-18 14:47:22 +00:00
}
2018-11-26 18:23:56 +00:00
sort . Strings ( sortedKeys )
2018-02-15 13:50:31 +00:00
2018-11-26 18:23:56 +00:00
for _ , key := range sortedKeys {
snap := hostPathVolumeSnapshots [ key ]
snapshot := csi . Snapshot {
SnapshotId : snap . Id ,
SourceVolumeId : snap . VolID ,
CreationTime : & snap . CreationTime ,
SizeBytes : snap . SizeBytes ,
ReadyToUse : snap . ReadyToUse ,
2018-01-09 18:57:14 +00:00
}
2018-11-26 18:23:56 +00:00
snapshots = append ( snapshots , snapshot )
2018-01-09 18:57:14 +00:00
}
2018-11-26 18:23:56 +00:00
var (
ulenSnapshots = int32 ( len ( snapshots ) )
maxEntries = req . MaxEntries
startingToken int32
)
if v := req . StartingToken ; v != "" {
i , err := strconv . ParseUint ( v , 10 , 32 )
if err != nil {
return nil , status . Errorf (
codes . Aborted ,
"startingToken=%d !< int32=%d" ,
startingToken , math . MaxUint32 )
}
startingToken = int32 ( i )
}
if startingToken > ulenSnapshots {
return nil , status . Errorf (
codes . Aborted ,
"startingToken=%d > len(snapshots)=%d" ,
startingToken , ulenSnapshots )
}
// Discern the number of remaining entries.
rem := ulenSnapshots - startingToken
// If maxEntries is 0 or greater than the number of remaining entries then
// set maxEntries to the number of remaining entries.
if maxEntries == 0 || maxEntries > rem {
maxEntries = rem
}
var (
i int
j = startingToken
entries = make (
[ ] * csi . ListSnapshotsResponse_Entry ,
maxEntries )
)
for i = 0 ; i < len ( entries ) ; i ++ {
entries [ i ] = & csi . ListSnapshotsResponse_Entry {
Snapshot : & snapshots [ j ] ,
}
j ++
}
var nextToken string
if j < ulenSnapshots {
nextToken = fmt . Sprintf ( "%d" , j )
}
return & csi . ListSnapshotsResponse {
Entries : entries ,
NextToken : nextToken ,
} , nil
}
func convertSnapshot ( snap hostPathSnapshot ) * csi . ListSnapshotsResponse {
entries := [ ] * csi . ListSnapshotsResponse_Entry {
{
Snapshot : & csi . Snapshot {
SnapshotId : snap . Id ,
SourceVolumeId : snap . VolID ,
CreationTime : & snap . CreationTime ,
SizeBytes : snap . SizeBytes ,
ReadyToUse : snap . ReadyToUse ,
} ,
} ,
}
rsp := & csi . ListSnapshotsResponse {
Entries : entries ,
}
return rsp
2018-01-09 18:57:14 +00:00
}