Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic provisioner for GCE PD #18621

Merged
merged 1 commit into from
Dec 17, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions cmd/kube-controller-manager/app/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
// Volume plugins
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
"k8s.io/kubernetes/pkg/cloudprovider/providers/openstack"
"k8s.io/kubernetes/pkg/util/io"
"k8s.io/kubernetes/pkg/volume"
Expand Down Expand Up @@ -91,8 +92,8 @@ func NewVolumeProvisioner(cloud cloudprovider.Interface, flags VolumeConfigFlags
return getProvisionablePluginFromVolumePlugins(host_path.ProbeVolumePlugins(volume.VolumeConfig{}))
case cloud != nil && aws.ProviderName == cloud.ProviderName():
return getProvisionablePluginFromVolumePlugins(aws_ebs.ProbeVolumePlugins())
// case cloud != nil && gce.ProviderName == cloud.ProviderName():
// return getProvisionablePluginFromVolumePlugins(gce_pd.ProbeVolumePlugins())
case cloud != nil && gce.ProviderName == cloud.ProviderName():
return getProvisionablePluginFromVolumePlugins(gce_pd.ProbeVolumePlugins())
case cloud != nil && openstack.ProviderName == cloud.ProviderName():
return getProvisionablePluginFromVolumePlugins(cinder.ProbeVolumePlugins())
}
Expand Down
105 changes: 105 additions & 0 deletions pkg/volume/gce_pd/gce_pd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ limitations under the License.
package gce_pd

import (
"fmt"
"os"
"path"
"strconv"

"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/exec"
Expand All @@ -41,6 +43,8 @@ type gcePersistentDiskPlugin struct {

var _ volume.VolumePlugin = &gcePersistentDiskPlugin{}
var _ volume.PersistentVolumePlugin = &gcePersistentDiskPlugin{}
var _ volume.DeletableVolumePlugin = &gcePersistentDiskPlugin{}
var _ volume.ProvisionableVolumePlugin = &gcePersistentDiskPlugin{}

const (
gcePersistentDiskPluginName = "kubernetes.io/gce-pd"
Expand Down Expand Up @@ -122,12 +126,50 @@ func (plugin *gcePersistentDiskPlugin) newCleanerInternal(volName string, podUID
}}, nil
}

func (plugin *gcePersistentDiskPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
return plugin.newDeleterInternal(spec, &GCEDiskUtil{})
}

func (plugin *gcePersistentDiskPlugin) newDeleterInternal(spec *volume.Spec, manager pdManager) (volume.Deleter, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the helper function needed? Why not just stick this logic in NewDeleter? Ditto with NewProvisioner.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used in testing to inject fake pdManager.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack.

if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.GCEPersistentDisk == nil {
return nil, fmt.Errorf("spec.PersistentVolumeSource.GCEPersistentDisk is nil")
}
return &gcePersistentDiskDeleter{
gcePersistentDisk: &gcePersistentDisk{
volName: spec.Name(),
pdName: spec.PersistentVolume.Spec.GCEPersistentDisk.PDName,
manager: manager,
plugin: plugin,
}}, nil
}

func (plugin *gcePersistentDiskPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
if len(options.AccessModes) == 0 {
options.AccessModes = plugin.GetAccessModes()
}
return plugin.newProvisionerInternal(options, &GCEDiskUtil{})
}

func (plugin *gcePersistentDiskPlugin) newProvisionerInternal(options volume.VolumeOptions, manager pdManager) (volume.Provisioner, error) {
return &gcePersistentDiskProvisioner{
gcePersistentDisk: &gcePersistentDisk{
manager: manager,
plugin: plugin,
},
options: options,
}, nil
}

// Abstract interface to PD operations.
type pdManager interface {
// Attaches the disk to the kubelet's host machine.
AttachAndMountDisk(b *gcePersistentDiskBuilder, globalPDPath string) error
// Detaches the disk from the kubelet's host machine.
DetachDisk(c *gcePersistentDiskCleaner) error
// Creates a volume
CreateVolume(provisioner *gcePersistentDiskProvisioner) (volumeID string, volumeSizeGB int, err error)
// Deletes a volume
DeleteVolume(deleter *gcePersistentDiskDeleter) error
}

// gcePersistentDisk volumes are disk resources provided by Google Compute Engine
Expand Down Expand Up @@ -301,3 +343,66 @@ func (c *gcePersistentDiskCleaner) TearDownAt(dir string) error {
}
return nil
}

type gcePersistentDiskDeleter struct {
*gcePersistentDisk
}

var _ volume.Deleter = &gcePersistentDiskDeleter{}

func (d *gcePersistentDiskDeleter) GetPath() string {
name := gcePersistentDiskPluginName
return d.plugin.host.GetPodVolumeDir(d.podUID, util.EscapeQualifiedNameForDisk(name), d.volName)
}

func (d *gcePersistentDiskDeleter) Delete() error {
return d.manager.DeleteVolume(d)
}

type gcePersistentDiskProvisioner struct {
*gcePersistentDisk
options volume.VolumeOptions
}

var _ volume.Provisioner = &gcePersistentDiskProvisioner{}

func (c *gcePersistentDiskProvisioner) Provision(pv *api.PersistentVolume) error {
volumeID, sizeGB, err := c.manager.CreateVolume(c)
if err != nil {
return err
}
pv.Spec.PersistentVolumeSource.GCEPersistentDisk.PDName = volumeID
pv.Spec.Capacity = api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think GCE API return GB not GiB

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it does not. All disk sizes are in GiB, just the documentation (and GUI and basically everything) says it's GB.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

}
return nil
}

func (c *gcePersistentDiskProvisioner) NewPersistentVolumeTemplate() (*api.PersistentVolume, error) {
// Provide dummy api.PersistentVolume.Spec, it will be filled in
// gcePersistentDiskProvisioner.Provision()
return &api.PersistentVolume{
ObjectMeta: api.ObjectMeta{
GenerateName: "pv-gce-",
Labels: map[string]string{},
Annotations: map[string]string{
"kubernetes.io/createdby": "gce-pd-dynamic-provisioner",
},
},
Spec: api.PersistentVolumeSpec{
PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy,
AccessModes: c.options.AccessModes,
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): c.options.Capacity,
},
PersistentVolumeSource: api.PersistentVolumeSource{
GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
PDName: "dummy",
FSType: "ext4",
Partition: 0,
ReadOnly: false,
},
},
},
}, nil
}
54 changes: 54 additions & 0 deletions pkg/volume/gce_pd/gce_pd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ limitations under the License.
package gce_pd

import (
"fmt"
"io/ioutil"
"os"
"path"
"testing"

"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/client/unversioned/testclient"
"k8s.io/kubernetes/pkg/types"
Expand Down Expand Up @@ -111,6 +113,17 @@ func (fake *fakePDManager) DetachDisk(c *gcePersistentDiskCleaner) error {
return nil
}

func (fake *fakePDManager) CreateVolume(c *gcePersistentDiskProvisioner) (volumeID string, volumeSizeGB int, err error) {
return "test-gce-volume-name", 100, nil
}

func (fake *fakePDManager) DeleteVolume(cd *gcePersistentDiskDeleter) error {
if cd.pdName != "test-gce-volume-name" {
return fmt.Errorf("Deleter got unexpected volume name: %s", cd.pdName)
}
return nil
}

func TestPlugin(t *testing.T) {
tmpDir, err := ioutil.TempDir(os.TempDir(), "gcepdTest")
if err != nil {
Expand Down Expand Up @@ -190,6 +203,47 @@ func TestPlugin(t *testing.T) {
if !fakeManager.detachCalled {
t.Errorf("Detach watch not called")
}

// Test Provisioner
cap := resource.MustParse("100Mi")
options := volume.VolumeOptions{
Capacity: cap,
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
},
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete,
}
provisioner, err := plug.(*gcePersistentDiskPlugin).newProvisionerInternal(options, &fakePDManager{})
persistentSpec, err := provisioner.NewPersistentVolumeTemplate()
if err != nil {
t.Errorf("NewPersistentVolumeTemplate() failed: %v", err)
}

// get 2nd Provisioner - persistent volume controller will do the same
provisioner, err = plug.(*gcePersistentDiskPlugin).newProvisionerInternal(options, &fakePDManager{})
err = provisioner.Provision(persistentSpec)
if err != nil {
t.Errorf("Provision() failed: %v", err)
}

if persistentSpec.Spec.PersistentVolumeSource.GCEPersistentDisk.PDName != "test-gce-volume-name" {
t.Errorf("Provision() returned unexpected volume ID: %s", persistentSpec.Spec.PersistentVolumeSource.GCEPersistentDisk.PDName)
}
cap = persistentSpec.Spec.Capacity[api.ResourceStorage]
size := cap.Value()
if size != 100*1024*1024*1024 {
t.Errorf("Provision() returned unexpected volume size: %v", size)
}

// Test Deleter
volSpec := &volume.Spec{
PersistentVolume: persistentSpec,
}
deleter, err := plug.(*gcePersistentDiskPlugin).newDeleterInternal(volSpec, &fakePDManager{})
err = deleter.Delete()
if err != nil {
t.Errorf("Deleter() failed: %v", err)
}
}

func TestPersistentClaimReadOnlyFlag(t *testing.T) {
Expand Down
34 changes: 34 additions & 0 deletions pkg/volume/gce_pd/gce_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"k8s.io/kubernetes/pkg/util/exec"
"k8s.io/kubernetes/pkg/util/keymutex"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/volume"
)

const (
Expand Down Expand Up @@ -112,6 +113,39 @@ func (util *GCEDiskUtil) DetachDisk(c *gcePersistentDiskCleaner) error {
return nil
}

func (util *GCEDiskUtil) DeleteVolume(d *gcePersistentDiskDeleter) error {
cloud, err := getCloudProvider()
if err != nil {
return err
}

if err = cloud.DeleteDisk(d.pdName); err != nil {
glog.V(2).Infof("Error deleting GCE PD volume %s: %v", d.pdName, err)
return err
}
glog.V(2).Infof("Successfully deleted GCE PD volume %s", d.pdName)
return nil
}

func (gceutil *GCEDiskUtil) CreateVolume(c *gcePersistentDiskProvisioner) (volumeID string, volumeSizeGB int, err error) {
cloud, err := getCloudProvider()
if err != nil {
return "", 0, err
}

name := fmt.Sprintf("kube-dynamic-%s", util.NewUUID())
requestBytes := c.options.Capacity.Value()
// GCE works with gigabytes, convert to GiB with rounding up
requestGB := volume.RoundUpSize(requestBytes, 1024*1024*1024)
err = cloud.CreateDisk(name, int64(requestGB))
if err != nil {
glog.V(2).Infof("Error creating GCE PD volume: %v", err)
return "", 0, err
}
glog.V(2).Infof("Successfully created GCE PD volume %s", name)
return name, int(requestGB), nil
}

// Attaches the specified persistent disk device to node, verifies that it is attached, and retries if it fails.
func attachDiskAndVerify(b *gcePersistentDiskBuilder, sdBeforeSet sets.String) (string, error) {
devicePaths := getDiskByIdPaths(b.gcePersistentDisk)
Expand Down