Skip to content

Commit

Permalink
Add the ability to configure disk size and type for GCP clusters
Browse files Browse the repository at this point in the history
This change adds the ability for GCP clusters to have custom sized disks
and types.
  • Loading branch information
spew committed Apr 30, 2018
1 parent 79317f9 commit a383c56
Show file tree
Hide file tree
Showing 14 changed files with 316 additions and 42 deletions.
7 changes: 6 additions & 1 deletion cloud/google/cmd/gce-machine-controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ func main() {
glog.Fatalf("Could not create client for talking to the apiserver: %v", err)
}

actuator, err := google.NewMachineActuator(*kubeadmToken, client.ClusterV1alpha1().Machines(corev1.NamespaceDefault), *machineSetupConfigsPath)
params := google.MachineActuatorParams{
ConfigListPath: *machineSetupConfigsPath,
KubeadmToken: *kubeadmToken,
MachineClient: client.ClusterV1alpha1().Machines(corev1.NamespaceDefault),
}
actuator, err := google.NewMachineActuator(params)
if err != nil {
glog.Fatalf("Could not create Google machine actuator: %v", err)
}
Expand Down
11 changes: 11 additions & 0 deletions cloud/google/gceproviderconfig/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,15 @@ type GCEProviderConfig struct {

// The name of the OS to be installed on the machine.
OS string `json:"os"`
Image string `json:"image"`
Disks []Disk `json:"disks"`
}

type Disk struct {
InitializeParams DiskInitializeParams `json:"initializeParams"`
}

type DiskInitializeParams struct {
DiskSizeGb int64 `json:"diskSizeGb"`
DiskType string `json:"diskType"`
}
11 changes: 11 additions & 0 deletions cloud/google/gceproviderconfig/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,15 @@ type GCEProviderConfig struct {

// The name of the OS to be installed on the machine.
OS string `json:"os"`
Image string `json:"image"`
Disks []Disk `json:"disks"`
}

type Disk struct {
InitializeParams DiskInitializeParams `json:"initializeParams"`
}

type DiskInitializeParams struct {
DiskSizeGb int64 `json:"diskSizeGb"`
DiskType string `json:"diskType"`
}
76 changes: 52 additions & 24 deletions cloud/google/machineactuator.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,20 +86,20 @@ type GCEClient struct {
configWatch *machinesetup.ConfigWatch
}

type MachineActuatorParams struct {
ComputeService GCEClientComputeService
ConfigListPath string
KubeadmToken string
MachineClient client.MachineInterface
}

const (
gceTimeout = time.Minute * 10
gceWaitSleep = time.Second * 5
)

func NewMachineActuator(kubeadmToken string, machineClient client.MachineInterface, configListPath string) (*GCEClient, error) {
// The default GCP client expects the environment variable
// GOOGLE_APPLICATION_CREDENTIALS to point to a file with service credentials.
client, err := google.DefaultClient(context.TODO(), compute.ComputeScope)
if err != nil {
return nil, err
}

computeService, err := clients.NewComputeService(client)
func NewMachineActuator(params MachineActuatorParams) (*GCEClient, error) {
computeService, err := getOrCreateComputeService(params)
if err != nil {
return nil, err
}
Expand All @@ -124,8 +124,8 @@ func NewMachineActuator(kubeadmToken string, machineClient client.MachineInterfa

// TODO: get rid of empty string check when we switch to the new bootstrapping method.
var configWatch *machinesetup.ConfigWatch
if configListPath != "" {
configWatch, err = machinesetup.NewConfigWatch(configListPath)
if params.ConfigListPath != "" {
configWatch, err = machinesetup.NewConfigWatch(params.ConfigListPath)
if err != nil {
glog.Errorf("Error creating config watch: %v", err)
}
Expand All @@ -135,13 +135,13 @@ func NewMachineActuator(kubeadmToken string, machineClient client.MachineInterfa
computeService: computeService,
scheme: scheme,
codecFactory: codecFactory,
kubeadmToken: kubeadmToken,
kubeadmToken: params.KubeadmToken,
sshCreds: SshCreds{
privateKeyPath: privateKeyPath,
user: user,
},
machineClient: machineClient,
configWatch: configWatch,
machineClient: params.MachineClient,
}, nil
}

Expand Down Expand Up @@ -259,7 +259,6 @@ func (gce *GCEClient) Create(cluster *clusterv1.Cluster, machine *clusterv1.Mach
name := machine.ObjectMeta.Name
project := config.Project
zone := config.Zone
diskSize := int64(30)

if instance == nil {
labels := map[string]string{}
Expand All @@ -281,16 +280,7 @@ func (gce *GCEClient) Create(cluster *clusterv1.Cluster, machine *clusterv1.Mach
},
},
},
Disks: []*compute.AttachedDisk{
{
AutoDelete: true,
Boot: true,
InitializeParams: &compute.AttachedDiskInitializeParams{
SourceImage: imagePath,
DiskSizeGb: diskSize,
},
},
},
Disks: buildDisks(config, zone, imagePath, int64(30)),
Metadata: &compute.Metadata{
Items: metadataItems,
},
Expand Down Expand Up @@ -709,6 +699,27 @@ func (gce *GCEClient) getImagePath(img string) (imagePath string) {
return defaultImg
}

func buildDisks(config *gceconfig.GCEProviderConfig, zone string, imagePath string, minDiskSizeGb int64) []*compute.AttachedDisk {
var disks []*compute.AttachedDisk
for idx, disk := range config.Disks {
diskSizeGb := disk.InitializeParams.DiskSizeGb
if diskSizeGb < minDiskSizeGb {
diskSizeGb = minDiskSizeGb
}
d := compute.AttachedDisk{
AutoDelete: true,
Boot: idx == 0,
InitializeParams: &compute.AttachedDiskInitializeParams{
SourceImage: imagePath,
DiskSizeGb: diskSizeGb,
DiskType: fmt.Sprintf("zones/%s/diskTypes/%s", zone, disk.InitializeParams.DiskType),
},
}
disks = append(disks, &d)
}
return disks
}

// Just a temporary hack to grab a single range from the config.
func getSubnet(netRange clusterv1.NetworkRanges) string {
if len(netRange.CIDRBlocks) == 0 {
Expand All @@ -717,6 +728,23 @@ func getSubnet(netRange clusterv1.NetworkRanges) string {
return netRange.CIDRBlocks[0]
}

func getOrCreateComputeService(params MachineActuatorParams) (GCEClientComputeService, error) {
if params.ComputeService != nil {
return params.ComputeService, nil
}
// The default GCP client expects the environment variable
// GOOGLE_APPLICATION_CREDENTIALS to point to a file with service credentials.
client, err := google.DefaultClient(context.TODO(), compute.ComputeScope)
if err != nil {
return nil, err
}
computeService, err := clients.NewComputeService(client)
if err != nil {
return nil, err
}
return computeService, nil
}

// TODO: We need to change this when we create dedicated service account for apiserver/controller
// pod.
//
Expand Down
119 changes: 119 additions & 0 deletions cloud/google/machineactuator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package google_test

import (
"github.com/stretchr/testify/assert"
compute "google.golang.org/api/compute/v1"
"sigs.k8s.io/cluster-api/cloud/google"
"sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1"
"strings"
"testing"
)

type GCEClientComputeServiceMock struct {
mockImagesGet func(project string, image string) (*compute.Image, error)
mockImagesGetFromFamily func(project string, family string) (*compute.Image, error)
mockInstancesDelete func(project string, zone string, targetInstance string) (*compute.Operation, error)
mockInstancesGet func(project string, zone string, instance string) (*compute.Instance, error)
mockInstancesInsert func(project string, zone string, instance *compute.Instance) (*compute.Operation, error)
mockZoneOperationsGet func(project string, zone string, operation string) (*compute.Operation, error)
}

func (c *GCEClientComputeServiceMock) ImagesGet(project string, image string) (*compute.Image, error) {
if c.mockImagesGet == nil {
return nil, nil
}
return c.mockImagesGet(project, image)
}

func (c *GCEClientComputeServiceMock) ImagesGetFromFamily(project string, family string) (*compute.Image, error) {
if c.mockImagesGetFromFamily == nil {
return nil, nil
}
return c.mockImagesGetFromFamily(project, family)
}

func (c *GCEClientComputeServiceMock) InstancesDelete(project string, zone string, targetInstance string) (*compute.Operation, error) {
if c.mockInstancesDelete == nil {
return nil, nil
}
return c.mockInstancesDelete(project, zone, targetInstance)
}

func (c *GCEClientComputeServiceMock) InstancesGet(project string, zone string, instance string) (*compute.Instance, error) {
if c.mockInstancesGet == nil {
return nil, nil
}
return c.mockInstancesGet(project, zone, instance)
}

func (c *GCEClientComputeServiceMock) InstancesInsert(project string, zone string, instance *compute.Instance) (*compute.Operation, error) {
if c.mockInstancesInsert == nil {
return nil, nil
}
return c.mockInstancesInsert(project, zone, instance)
}

func (c *GCEClientComputeServiceMock) ZoneOperationsGet(project string, zone string, operation string) (*compute.Operation, error) {
if c.mockZoneOperationsGet == nil {
return nil, nil
}
return c.mockZoneOperationsGet(project, zone, operation)
}

func TestOneDisk(t *testing.T) {
receivedInstance, computeServiceMock := createInsertInstanceCapturingMock()
err := createCluster("testdata/cluster.yaml", "testdata/machine-one.yaml", computeServiceMock)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if receivedInstance == nil {
t.Error("expected a valid instance")
}
if len(receivedInstance.Disks) != 1 {
t.Errorf("invalid disk count: expected %v got %v", 1, len(receivedInstance.Disks))
}
checkDiskValues(t, receivedInstance.Disks[0], true, 17, "pd-ssd")
}

func TestTwoDisks(t *testing.T) {
receivedInstance, computeServiceMock := createInsertInstanceCapturingMock()
err := createCluster("testdata/cluster.yaml", "testdata/machine-two.yaml", computeServiceMock)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if receivedInstance == nil {
t.Error("expected a valid instance")
}
if len(receivedInstance.Disks) != 2 {
t.Errorf("invalid disk count: expected %v got %v", 2, len(receivedInstance.Disks))
}
checkDiskValues(t, receivedInstance.Disks[0], true, 12, "pd-ssd")
checkDiskValues(t, receivedInstance.Disks[1], false, 15, "pd-standard")
}

func checkDiskValues(t *testing.T, disk *compute.AttachedDisk, boot bool, sizeGb int64, diskType string) {
assert.Equal(t, boot, disk.Boot)
assert.Equal(t, sizeGb, disk.InitializeParams.DiskSizeGb)
assert.True(t, strings.Contains(disk.InitializeParams.DiskType, diskType))
}

func createCluster(clusterYamlFile string, machineYamlFile string, computeServiceMock *GCEClientComputeServiceMock) error {
cluster, _ := v1alpha1.ParseClusterYaml(clusterYamlFile)
machine, _ := v1alpha1.ParseMachineYaml(machineYamlFile)
params := google.MachineActuatorParams{ComputeService: computeServiceMock}
gce, _ := google.NewMachineActuator(params)
return gce.Create(cluster, machine)
}

func createInsertInstanceCapturingMock() (*compute.Instance, *GCEClientComputeServiceMock) {
var receivedInstance compute.Instance
computeServiceMock := GCEClientComputeServiceMock{
mockInstancesInsert: func(project string, zone string, instance *compute.Instance) (*compute.Operation, error) {
receivedInstance = *instance
return &compute.Operation{
Status: "DONE",
}, nil
},
}
return &receivedInstance, &computeServiceMock
}
15 changes: 15 additions & 0 deletions cloud/google/testdata/cluster.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: "cluster.k8s.io/v1alpha1"
kind: Cluster
metadata:
name: test10
spec:
clusterNetwork:
services:
cidrBlocks: ["10.96.0.0/12"]
pods:
cidrBlocks: ["192.168.0.0/16"]
serviceDomain: "cluster.local"
providerConfig:
value:
apiVersion: "gceproviderconfig/v1alpha1"
kind: "GCEProviderConfig"
27 changes: 27 additions & 0 deletions cloud/google/testdata/machine-one.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
apiVersion: "cluster.k8s.io/v1alpha1"
kind: Machine
metadata:
generateName: gce-master-
labels:
set: master
spec:
providerConfig:
value:
apiVersion: "gceproviderconfig/v1alpha1"
kind: "GCEProviderConfig"
project: "project-name-1000"
zone: "us-west1-c"
machineType: "n1-standard-2"
image: "projects/ubuntu-os-cloud/global/images/family/ubuntu-1604-lts"
disks:
- initializeParams:
diskSizeGb: 17
diskType: "pd-ssd"
versions:
kubelet: 1.8.3
controlPlane: 1.8.3
containerRuntime:
name: docker
version: 1.12.0
roles:
- Master
30 changes: 30 additions & 0 deletions cloud/google/testdata/machine-two.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
apiVersion: "cluster.k8s.io/v1alpha1"
kind: Machine
metadata:
generateName: gce-master-
labels:
set: master
spec:
providerConfig:
value:
apiVersion: "gceproviderconfig/v1alpha1"
kind: "GCEProviderConfig"
project: "project-name-1000"
zone: "us-west1-c"
machineType: "n1-standard-2"
image: "projects/ubuntu-os-cloud/global/images/family/ubuntu-1604-lts"
disks:
- initializeParams:
diskSizeGb: 12
diskType: "pd-ssd"
- initializeParams:
diskSizeGb: 15
diskType: "pd-standard"
versions:
kubelet: 1.8.3
controlPlane: 1.8.3
containerRuntime:
name: docker
version: 1.12.0
roles:
- Master
3 changes: 2 additions & 1 deletion gcp-deployer/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/golang/glog"
"github.com/spf13/cobra"
"sigs.k8s.io/cluster-api/gcp-deployer/deploy"
"sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1"
)

type CreateOptions struct {
Expand Down Expand Up @@ -59,7 +60,7 @@ var createCmd = &cobra.Command{
}

func RunCreate(co *CreateOptions) error {
cluster, err := parseClusterYaml(co.Cluster)
cluster, err := v1alpha1.ParseClusterYaml(co.Cluster)
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit a383c56

Please sign in to comment.