Skip to content

Commit d36115a

Browse files
authored
Merge pull request #76 from lpabon/pick49
Bugfix: PV should use capacity bytes returned by plugin, not PVC bytes
2 parents 00ca3b3 + d3baedd commit d36115a

File tree

2 files changed

+122
-2
lines changed

2 files changed

+122
-2
lines changed

pkg/controller/controller.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import (
2323
"strings"
2424
"time"
2525

26+
"k8s.io/apimachinery/pkg/api/resource"
27+
_ "k8s.io/apimachinery/pkg/util/json"
28+
2629
"github.com/golang/glog"
2730

2831
"github.com/kubernetes-incubator/external-storage/lib/controller"
@@ -289,7 +292,6 @@ func (p *csiProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis
289292
},
290293
CapacityRange: &csi.CapacityRange{
291294
RequiredBytes: int64(volSizeBytes),
292-
LimitBytes: int64(volSizeBytes),
293295
},
294296
}
295297
rep := &csi.CreateVolumeResponse{}
@@ -336,6 +338,28 @@ func (p *csiProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis
336338
for k, v := range rep.Volume.Attributes {
337339
volumeAttributes[k] = v
338340
}
341+
respCap := rep.GetVolume().GetCapacityBytes()
342+
if respCap < volSizeBytes {
343+
capErr := fmt.Errorf("created volume capacity %v less than requested capacity %v", respCap, volSizeBytes)
344+
delReq := &csi.DeleteVolumeRequest{
345+
VolumeId: rep.GetVolume().GetId(),
346+
}
347+
if options.Parameters != nil {
348+
credentials, err := getCredentialsFromParameters(p.client, options.Parameters)
349+
if err != nil {
350+
return nil, err
351+
}
352+
delReq.ControllerDeleteSecrets = credentials
353+
}
354+
ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
355+
defer cancel()
356+
_, err := p.csiClient.DeleteVolume(ctx, delReq)
357+
if err != nil {
358+
capErr = fmt.Errorf("%v. Cleanup of volume %s failed, volume is orphaned: %v", capErr, share, err)
359+
}
360+
return nil, capErr
361+
}
362+
repBytesString := fmt.Sprintf("%v", respCap)
339363
pv := &v1.PersistentVolume{
340364
ObjectMeta: metav1.ObjectMeta{
341365
Name: share,
@@ -344,7 +368,7 @@ func (p *csiProvisioner) Provision(options controller.VolumeOptions) (*v1.Persis
344368
PersistentVolumeReclaimPolicy: options.PersistentVolumeReclaimPolicy,
345369
AccessModes: options.PVC.Spec.AccessModes,
346370
Capacity: v1.ResourceList{
347-
v1.ResourceName(v1.ResourceStorage): options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)],
371+
v1.ResourceName(v1.ResourceStorage): resource.MustParse(repBytesString),
348372
},
349373
// TODO wait for CSI VolumeSource API
350374
PersistentVolumeSource: v1.PersistentVolumeSource{

pkg/controller/controller_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,18 @@ package controller
1818

1919
import (
2020
"fmt"
21+
"strconv"
2122
"testing"
2223
"time"
2324

2425
csi "github.com/container-storage-interface/spec/lib/go/csi/v0"
2526
"github.com/golang/mock/gomock"
2627
"github.com/kubernetes-csi/csi-test/driver"
28+
"github.com/kubernetes-incubator/external-storage/lib/controller"
2729
"google.golang.org/grpc"
30+
"k8s.io/api/core/v1"
31+
"k8s.io/apimachinery/pkg/api/resource"
32+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2833
)
2934

3035
const (
@@ -384,3 +389,94 @@ func TestGetDriverName(t *testing.T) {
384389
}
385390
}
386391
}
392+
393+
func TestCreateDriverReturnsInvalidCapacityDuringProvision(t *testing.T) {
394+
// Set up mocks
395+
var requestedBytes int64 = 100
396+
mockController, driver, identityServer, controllerServer, csiConn, err := createMockServer(t)
397+
if err != nil {
398+
t.Fatal(err)
399+
}
400+
defer mockController.Finish()
401+
defer driver.Stop()
402+
403+
csiProvisioner := NewCSIProvisioner(nil, driver.Address(), 5*time.Second, "test-provisioner", "test", 5, csiConn.conn)
404+
405+
// Requested PVC with requestedBytes storage
406+
opts := controller.VolumeOptions{
407+
PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete,
408+
PVName: "test-name",
409+
PVC: createFakePVC(requestedBytes),
410+
Parameters: map[string]string{},
411+
}
412+
413+
// Drivers CreateVolume response with lower capacity bytes than request
414+
out := &csi.CreateVolumeResponse{
415+
Volume: &csi.Volume{
416+
CapacityBytes: requestedBytes - 1,
417+
Id: "test-volume-id",
418+
},
419+
}
420+
421+
// Set up Mocks
422+
provisionMockServerSetupExpectations(identityServer, controllerServer)
423+
controllerServer.EXPECT().CreateVolume(gomock.Any(), gomock.Any()).Return(out, nil).Times(1)
424+
// Since capacity returned by driver is invalid, we expect the provision call to clean up the volume
425+
controllerServer.EXPECT().DeleteVolume(gomock.Any(), &csi.DeleteVolumeRequest{
426+
VolumeId: "test-volume-id",
427+
}).Return(&csi.DeleteVolumeResponse{}, nil).Times(1)
428+
429+
// Call provision
430+
_, err = csiProvisioner.Provision(opts)
431+
if err == nil {
432+
t.Errorf("Provision did not cause an error when one was expected")
433+
return
434+
}
435+
t.Logf("Provision encountered an error: %v, expected: create volume capacity less than requested capacity", err)
436+
}
437+
438+
func provisionMockServerSetupExpectations(identityServer *driver.MockIdentityServer, controllerServer *driver.MockControllerServer) {
439+
identityServer.EXPECT().GetPluginCapabilities(gomock.Any(), gomock.Any()).Return(&csi.GetPluginCapabilitiesResponse{
440+
Capabilities: []*csi.PluginCapability{
441+
&csi.PluginCapability{
442+
Type: &csi.PluginCapability_Service_{
443+
Service: &csi.PluginCapability_Service{
444+
Type: csi.PluginCapability_Service_CONTROLLER_SERVICE,
445+
},
446+
},
447+
},
448+
},
449+
}, nil).Times(1)
450+
controllerServer.EXPECT().ControllerGetCapabilities(gomock.Any(), gomock.Any()).Return(&csi.ControllerGetCapabilitiesResponse{
451+
Capabilities: []*csi.ControllerServiceCapability{
452+
&csi.ControllerServiceCapability{
453+
Type: &csi.ControllerServiceCapability_Rpc{
454+
Rpc: &csi.ControllerServiceCapability_RPC{
455+
Type: csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
456+
},
457+
},
458+
},
459+
},
460+
}, nil).Times(1)
461+
identityServer.EXPECT().GetPluginInfo(gomock.Any(), gomock.Any()).Return(&csi.GetPluginInfoResponse{
462+
Name: "test-driver",
463+
VendorVersion: "test-vendor",
464+
}, nil).Times(1)
465+
}
466+
467+
// Minimal PVC required for tests to function
468+
func createFakePVC(requestBytes int64) *v1.PersistentVolumeClaim {
469+
return &v1.PersistentVolumeClaim{
470+
ObjectMeta: metav1.ObjectMeta{
471+
UID: "testid",
472+
},
473+
Spec: v1.PersistentVolumeClaimSpec{
474+
Selector: nil, // Provisioner doesn't support selector
475+
Resources: v1.ResourceRequirements{
476+
Requests: v1.ResourceList{
477+
v1.ResourceName(v1.ResourceStorage): resource.MustParse(strconv.FormatInt(requestBytes, 10)),
478+
},
479+
},
480+
},
481+
}
482+
}

0 commit comments

Comments
 (0)