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

Save entry list compressed in extension state #416

Merged
merged 2 commits into from
Jan 16, 2025
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ sast-report: $(GOSEC)
@./hack/sast.sh --gosec-report true

.PHONY: test
test:
test: $(GINKGO)
@bash $(GARDENER_HACK_DIR)/test.sh ./cmd/... ./pkg/...

.PHONY: test-cov
Expand All @@ -135,8 +135,12 @@ test-cov:
test-clean:
@bash $(GARDENER_HACK_DIR)/test-cover-clean.sh

.PHONY: test-integration-lifecycle
test-integration-lifecycle: $(REPORT_COLLECTOR) $(SETUP_ENVTEST)
@bash $(GARDENER_HACK_DIR)/test-integration.sh ./test/integration/lifecycle/...

.PHONY: verify
verify: check format test sast
verify: check format test test-integration-lifecycle sast

.PHONY: verify-extended
verify-extended: check-generate check format test-cov test-clean sast-report
verify-extended: check-generate check format test-cov test-clean test-integration-lifecycle sast-report
marc1404 marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.23.0

require (
github.com/ahmetb/gen-crd-api-reference-docs v0.3.1-0.20241014194617-ffc4efda75d4
github.com/andybalholm/brotli v1.1.1
github.com/gardener/external-dns-management v0.22.2
github.com/gardener/gardener v1.110.0
github.com/go-logr/logr v1.4.2
Expand All @@ -29,7 +30,6 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.1 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
Expand Down
19 changes: 0 additions & 19 deletions pkg/apis/helper/scheme.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,18 @@
package helper

import (
"fmt"

extapi "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"

api "github.com/gardener/gardener-extension-shoot-dns-service/pkg/apis"
"github.com/gardener/gardener-extension-shoot-dns-service/pkg/apis/install"
)

var (
// Scheme is a scheme with the types relevant for vSphere actuators.
Scheme *runtime.Scheme

decoder runtime.Decoder
)

func init() {
Scheme = runtime.NewScheme()
utilruntime.Must(install.AddToScheme(Scheme))

decoder = serializer.NewCodecFactory(Scheme).UniversalDecoder()
}

func GetExtensionState(ext *extapi.Extension) (*api.DNSState, error) {
state := &api.DNSState{}
if ext.Status.State != nil && ext.Status.State.Raw != nil {
if _, _, err := decoder.Decode(ext.Status.State.Raw, nil, state); err != nil {
return state, fmt.Errorf("could not decode extension state: %w", err)
}
}
return state, nil
}
70 changes: 70 additions & 0 deletions pkg/controller/common/compressedstate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
//
// SPDX-License-Identifier: Apache-2.0

package common

import (
"bytes"
"encoding/json"
"fmt"

"github.com/andybalholm/brotli"
)

type compressedEntriesState struct {
CompressedState []byte `json:"compressedState"`
}

// CompressEntriesState compresses the entries state data.
func CompressEntriesState(state []byte) ([]byte, error) {
if len(state) == 0 || string(state) == "{}" {
return nil, nil
}

var stateCompressed bytes.Buffer
writer := brotli.NewWriter(&stateCompressed)
defer writer.Close()

if _, err := writer.Write(state); err != nil {
return nil, fmt.Errorf("failed writing entries state data for compression: %w", err)
}

// Close ensures any unwritten data is flushed. Without this, the `stateCompressed`
// buffer would not contain any data. Hence, we have to call it explicitly here after writing, in addition to the
// 'defer' call above.
if err := writer.Close(); err != nil {
return nil, fmt.Errorf("failed closing the brotli writer after compressing the entries state data: %w", err)
}

return json.Marshal(&compressedEntriesState{CompressedState: stateCompressed.Bytes()})
}

// LooksLikeCompressedEntriesState checks if the given state data has the string compressedState in the first 20 bytes.
func LooksLikeCompressedEntriesState(state []byte) bool {
if len(state) < len("compressedState") {
return false
}

return bytes.Contains(state[:min(20, len(state))], []byte("compressedState"))
}

// DecompressEntriesState decompresses the entries state data.
func DecompressEntriesState(stateCompressed []byte) ([]byte, error) {
if len(stateCompressed) == 0 {
return nil, nil
}

var entriesState compressedEntriesState
if err := json.Unmarshal(stateCompressed, &entriesState); err != nil {
return nil, fmt.Errorf("failed unmarshalling JSON to compressed entries state structure: %w", err)
}

reader := brotli.NewReader(bytes.NewReader(entriesState.CompressedState))
var state bytes.Buffer
if _, err := state.ReadFrom(reader); err != nil {
return nil, fmt.Errorf("failed reading machine state data for decompression: %w", err)
}

return state.Bytes(), nil
}
34 changes: 34 additions & 0 deletions pkg/controller/common/compressedstate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and Gardener contributors
//
// SPDX-License-Identifier: Apache-2.0

package common

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("CompressedState", func() {
It("should compress and decompress", func() {
state := []byte(`{"entries":[{"name":"entry1","spec":{}},{"name":"entry2","spec":{}}]}`)
data, err := CompressEntriesState(state)
Expect(err).To(BeNil())
Expect(data).NotTo(BeNil())

state2, err := DecompressEntriesState(data)
Expect(err).To(BeNil())
Expect(state2).NotTo(BeNil())
Expect(state).To(Equal(state2))
})

It("should recognise compressed state data by heuristic", func() {
state := []byte(`{"entries":[{"name":"entry1","spec":{}},{"name":"entry2","spec":{}}]}`)
data, err := CompressEntriesState(state)
Expect(err).To(BeNil())
Expect(data).NotTo(BeNil())

Expect(LooksLikeCompressedEntriesState(data)).To(BeTrue())
Expect(LooksLikeCompressedEntriesState(state)).To(BeFalse())
})
})
59 changes: 40 additions & 19 deletions pkg/controller/common/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import (

dnsapi "github.com/gardener/external-dns-management/pkg/apis/dns/v1alpha1"
"github.com/gardener/gardener/pkg/apis/extensions/v1alpha1"
extapi "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/sets"
"sigs.k8s.io/controller-runtime/pkg/client"

Expand All @@ -23,6 +25,32 @@ import (
"github.com/gardener/gardener-extension-shoot-dns-service/pkg/service"
)

var (
decoder runtime.Decoder
)

func init() {
decoder = serializer.NewCodecFactory(helper.Scheme).UniversalDecoder()
}

func GetExtensionState(ext *extapi.Extension) (*apis.DNSState, error) {
state := &apis.DNSState{}
if ext.Status.State != nil && ext.Status.State.Raw != nil {
data := ext.Status.State.Raw
if LooksLikeCompressedEntriesState(data) {
var err error
data, err = DecompressEntriesState(data)
if err != nil {
return state, fmt.Errorf("could not decompress extension state: %w", err)
}
}
if _, _, err := decoder.Decode(data, nil, state); err != nil {
return state, fmt.Errorf("could not decode extension state: %w", err)
}
}
return state, nil
}

////////////////////////////////////////////////////////////////////////////////
// state update handling

Expand Down Expand Up @@ -51,7 +79,7 @@ func NewStateHandler(ctx context.Context, env *Env, ext *v1alpha1.Extension, ref
elem: elem,
helper: NewShootDNSEntriesHelper(ctx, env.Client(), ext),
}
handler.state, err = helper.GetExtensionState(ext)
handler.state, err = GetExtensionState(ext)
if err != nil || refresh {
if err != nil {
handler.modified = true
Expand Down Expand Up @@ -93,17 +121,6 @@ func (s *StateHandler) Refresh() (bool, error) {
if err != nil {
return false, err
}
/*
list = append(list, dnsapi.DNSEntry{
ObjectMeta: metav1.ObjectMeta{
Name: "DUMMY",
},
Spec: dnsapi.DNSEntrySpec{
DNSName: "bla.blub.de",
Targets: []string{"8.8.8.8"},
},
})
*/
return s.EnsureEntries(list), nil
}

Expand Down Expand Up @@ -178,25 +195,29 @@ func (s *StateHandler) Update(reason string) error {
wire.Kind = wireapi.DNSStateKind
err := helper.Scheme.Convert(s.state, wire, nil)
if err != nil {
s.Infof("state conversion failed: %s", err)
s.Error(err, "state conversion failed")
return err
}
if s.ext.Status.State == nil {
s.ext.Status.State = &runtime.RawExtension{}
}
s.ext.Status.State.Raw, err = json.Marshal(wire)
data, err := json.Marshal(wire)
if err != nil {
s.Error(err, "marshalling failed")
return err
}
s.ext.Status.State.Raw, err = CompressEntriesState(data)
if err != nil {
s.Info("marshalling failed", "error", err)
s.Error(err, "compressing failed")
return err
}
s.ext.Status.State.Object = nil
err = s.client.Status().Update(s.ctx, s.ext)
if err != nil {
s.Info("update failed", "error", err)
} else {
s.modified = false
s.Error(err, "update failed")
return err
}
return err
s.modified = false
}
return nil
}
17 changes: 17 additions & 0 deletions test/integration/lifecycle/lifecycle_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
//
// SPDX-License-Identifier: Apache-2.0

package lifecycle_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestLifecycle(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Lifecycle Suite")
}
Loading
Loading