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

[improvement] Add env var toggle for gzip compression of cloud-init user data #531

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"flag"
"fmt"
"os"
"strconv"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -84,6 +85,9 @@
linodeDNSURL = os.Getenv("LINODE_DNS_URL")
linodeDNSCA = os.Getenv("LINODE_DNS_CA")

// Feature flags
gzipCompressionEnabled = os.Getenv("GZIP_COMPRESSION_ENABLED")

Check warning on line 90 in cmd/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/main.go#L88-L90

Added lines #L88 - L90 were not covered by tests
machineWatchFilter string
clusterWatchFilter string
objectStorageBucketWatchFilter string
Expand Down Expand Up @@ -185,11 +189,17 @@
os.Exit(1)
}

useGzip, err := strconv.ParseBool(gzipCompressionEnabled)
if err != nil {
setupLog.Error(err, "proceeding without gzip compression for cloud-init data")
}

Check warning on line 195 in cmd/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/main.go#L192-L195

Added lines #L192 - L195 were not covered by tests

if err = (&controller.LinodeMachineReconciler{
Client: mgr.GetClient(),
Recorder: mgr.GetEventRecorderFor("LinodeMachineReconciler"),
WatchFilterValue: machineWatchFilter,
LinodeClientConfig: linodeClientConfig,
Client: mgr.GetClient(),
Recorder: mgr.GetEventRecorderFor("LinodeMachineReconciler"),
WatchFilterValue: machineWatchFilter,
LinodeClientConfig: linodeClientConfig,
GzipCompressionEnabled: useGzip,

Check warning on line 202 in cmd/main.go

View check run for this annotation

Codecov / codecov/patch

cmd/main.go#L198-L202

Added lines #L198 - L202 were not covered by tests
}).SetupWithManager(mgr, crcontroller.Options{MaxConcurrentReconciles: linodeMachineConcurrency}); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "LinodeMachine")
os.Exit(1)
Expand Down
4 changes: 3 additions & 1 deletion controller/linodemachine_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ type LinodeMachineReconciler struct {
LinodeClientConfig scope.ClientConfig
WatchFilterValue string
ReconcileTimeout time.Duration
// Feature flags
GzipCompressionEnabled bool
}

// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=linodemachines,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -361,7 +363,7 @@ func (r *LinodeMachineReconciler) reconcilePreflightMetadataSupportConfigure(ctx

func (r *LinodeMachineReconciler) reconcilePreflightCreate(ctx context.Context, logger logr.Logger, machineScope *scope.MachineScope) (ctrl.Result, error) {
// get the bootstrap data for the Linode instance and set it for create config
createOpts, err := newCreateConfig(ctx, machineScope, logger)
createOpts, err := newCreateConfig(ctx, machineScope, r.GzipCompressionEnabled, logger)
if err != nil {
logger.Error(err, "Failed to create Linode machine InstanceCreateOptions")
return retryIfTransient(err)
Expand Down
35 changes: 30 additions & 5 deletions controller/linodemachine_controller_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import (
"bytes"
"compress/gzip"
"context"
b64 "encoding/base64"
"encoding/gob"
Expand Down Expand Up @@ -97,7 +98,7 @@
}
}

func newCreateConfig(ctx context.Context, machineScope *scope.MachineScope, logger logr.Logger) (*linodego.InstanceCreateOptions, error) {
func newCreateConfig(ctx context.Context, machineScope *scope.MachineScope, gzipCompressionEnabled bool, logger logr.Logger) (*linodego.InstanceCreateOptions, error) {
var err error

createConfig := linodeMachineSpecToInstanceCreateConfig(machineScope.LinodeMachine.Spec)
Expand All @@ -110,7 +111,7 @@
}

createConfig.Booted = util.Pointer(false)
if err := setUserData(ctx, machineScope, createConfig, logger); err != nil {
if err := setUserData(ctx, machineScope, createConfig, gzipCompressionEnabled, logger); err != nil {
return nil, err
}

Expand Down Expand Up @@ -455,16 +456,40 @@
return &createConfig
}

func setUserData(ctx context.Context, machineScope *scope.MachineScope, createConfig *linodego.InstanceCreateOptions, logger logr.Logger) error {
func compressUserData(bootstrapData []byte) ([]byte, error) {
var userDataBuff bytes.Buffer
var err error
gz := gzip.NewWriter(&userDataBuff)
defer func(gz *gzip.Writer) {
err = gz.Close()
}(gz)
if _, err := gz.Write(bootstrapData); err != nil {
return nil, err
}
err = gz.Close()
return userDataBuff.Bytes(), err

Check warning on line 470 in controller/linodemachine_controller_helpers.go

View check run for this annotation

Codecov / codecov/patch

controller/linodemachine_controller_helpers.go#L459-L470

Added lines #L459 - L470 were not covered by tests
}

func setUserData(ctx context.Context, machineScope *scope.MachineScope, createConfig *linodego.InstanceCreateOptions, gzipCompressionEnabled bool, logger logr.Logger) error {
bootstrapData, err := machineScope.GetBootstrapData(ctx)
if err != nil {
logger.Error(err, "Failed to get bootstrap data")

return err
}

userData := bootstrapData
//nolint:nestif // this is a temp flag until cloud-init is updated in cloud-init compatible images
if machineScope.LinodeMachine.Status.CloudinitMetadataSupport {
bootstrapSize := len(bootstrapData)
if gzipCompressionEnabled {
userData, err = compressUserData(bootstrapData)
if err != nil {
logger.Error(err, "failed to compress bootstrap data")

return err
}

Check warning on line 490 in controller/linodemachine_controller_helpers.go

View check run for this annotation

Codecov / codecov/patch

controller/linodemachine_controller_helpers.go#L485-L490

Added lines #L485 - L490 were not covered by tests
}
bootstrapSize := len(userData)
if bootstrapSize > maxBootstrapDataBytesCloudInit {
err = errors.New("bootstrap data too large")
logger.Error(err, "decoded bootstrap data exceeds size limit",
Expand All @@ -475,7 +500,7 @@
return err
}
createConfig.Metadata = &linodego.InstanceMetadataOptions{
UserData: b64.StdEncoding.EncodeToString(bootstrapData),
UserData: b64.StdEncoding.EncodeToString(userData),
}
} else {
logger.Info("using StackScripts for bootstrapping")
Expand Down
22 changes: 19 additions & 3 deletions controller/linodemachine_controller_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package controller

import (
"bytes"
"compress/gzip"
"context"
"crypto/rand"
b64 "encoding/base64"
"encoding/gob"
"fmt"
Expand Down Expand Up @@ -79,6 +81,20 @@ func TestLinodeMachineSpecToCreateInstanceConfig(t *testing.T) {
func TestSetUserData(t *testing.T) {
t.Parallel()

userData := []byte("test-data")
if gzipCompressionFlag {
var userDataBuff bytes.Buffer
gz := gzip.NewWriter(&userDataBuff)
_, err = gz.Write([]byte("test-data"))
err = gz.Close()
require.NoError(t, err, "Failed to compress bootstrap data")
userData = userDataBuff.Bytes()
}

largeData := make([]byte, maxBootstrapDataBytesCloudInit*10)
_, err = rand.Read(largeData)
require.NoError(t, err, "Failed to create bootstrap data")

tests := []struct {
name string
machineScope *scope.MachineScope
Expand Down Expand Up @@ -107,7 +123,7 @@ func TestSetUserData(t *testing.T) {
}},
createConfig: &linodego.InstanceCreateOptions{},
wantConfig: &linodego.InstanceCreateOptions{Metadata: &linodego.InstanceMetadataOptions{
UserData: b64.StdEncoding.EncodeToString([]byte("test-data")),
UserData: b64.StdEncoding.EncodeToString(userData),
}},
expects: func(mockClient *mock.MockLinodeClient, kMock *mock.MockK8sClient) {
kMock.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj *corev1.Secret, opts ...client.GetOption) error {
Expand Down Expand Up @@ -184,7 +200,7 @@ func TestSetUserData(t *testing.T) {
kMock.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj *corev1.Secret, opts ...client.GetOption) error {
cred := corev1.Secret{
Data: map[string][]byte{
"value": make([]byte, maxBootstrapDataBytesCloudInit+1),
"value": largeData,
},
}
*obj = cred
Expand Down Expand Up @@ -304,7 +320,7 @@ func TestSetUserData(t *testing.T) {
testcase.expects(mockClient, mockK8sClient)
logger := logr.Logger{}

err := setUserData(context.Background(), testcase.machineScope, testcase.createConfig, logger)
err := setUserData(context.Background(), testcase.machineScope, testcase.createConfig, gzipCompressionFlag, logger)
if testcase.expectedError != nil {
assert.ErrorContains(t, err, testcase.expectedError.Error())
} else {
Expand Down
11 changes: 7 additions & 4 deletions controller/linodemachine_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ import (
. "github.com/onsi/gomega"
)

const defaultNamespace = "default"
const (
defaultNamespace = "default"
gzipCompressionFlag = false
)

var _ = Describe("create", Label("machine", "create"), func() {
var machine clusterv1.Machine
Expand Down Expand Up @@ -1708,7 +1711,7 @@ var _ = Describe("machine in PlacementGroup", Label("machine", "placementGroup")
Expect(err).NotTo(HaveOccurred())
mScope.PatchHelper = patchHelper

createOpts, err := newCreateConfig(ctx, &mScope, logger)
createOpts, err := newCreateConfig(ctx, &mScope, gzipCompressionFlag, logger)
Expect(err).NotTo(HaveOccurred())
Expect(createOpts).NotTo(BeNil())
Expect(createOpts.PlacementGroup.ID).To(Equal(1))
Expand Down Expand Up @@ -1882,7 +1885,7 @@ var _ = Describe("machine in VPC", Label("machine", "VPC"), Ordered, func() {
Expect(err).NotTo(HaveOccurred())
mScope.PatchHelper = patchHelper

createOpts, err := newCreateConfig(ctx, &mScope, logger)
createOpts, err := newCreateConfig(ctx, &mScope, gzipCompressionFlag, logger)
Expect(err).NotTo(HaveOccurred())
Expect(createOpts).NotTo(BeNil())
Expect(createOpts.Interfaces).To(Equal([]linodego.InstanceConfigInterfaceCreateOptions{
Expand Down Expand Up @@ -1960,7 +1963,7 @@ var _ = Describe("machine in VPC", Label("machine", "VPC"), Ordered, func() {
Expect(err).NotTo(HaveOccurred())
mScope.PatchHelper = patchHelper

createOpts, err := newCreateConfig(ctx, &mScope, logger)
createOpts, err := newCreateConfig(ctx, &mScope, gzipCompressionFlag, logger)
Expect(err).NotTo(HaveOccurred())
Expect(createOpts).NotTo(BeNil())
Expect(createOpts.Interfaces).To(Equal([]linodego.InstanceConfigInterfaceCreateOptions{
Expand Down
Loading