Skip to content
Draft
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
8 changes: 1 addition & 7 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,9 @@ rules:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- list
- watch
- apiGroups:
Expand Down
5 changes: 3 additions & 2 deletions controllers/workloads/instance_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type InstanceReconciler struct {
// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=core,resources=pods/status,verbs=get
// +kubebuilder:rbac:groups=core,resources=pods/finalizers,verbs=update
// +kubebuilder:rbac:groups=core,resources=nodes,verbs=get

// +kubebuilder:rbac:groups=core,resources=persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=core,resources=persistentvolumeclaims/status,verbs=get
Expand Down Expand Up @@ -94,11 +95,11 @@ func (r *InstanceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
Do(instance.NewFixMetaReconciler()).
Do(instance.NewDeletionReconciler(r.Client)).
Do(instance.NewRevisionUpdateReconciler()).
Do(instance.NewStatusReconciler()).
Do(instance.NewStatusReconciler(r.Client)).
// Do(instance.NewRevisionUpdateReconciler()).
Do(instance.NewAssistantObjectReconciler()).
Do(instance.NewAlignmentReconciler()).
Do(instance.NewUpdateReconciler()).
Do(instance.NewUpdateReconciler(r.Client)).
Commit()
}

Expand Down
5 changes: 3 additions & 2 deletions controllers/workloads/instanceset_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type InstanceSetReconciler struct {
// +kubebuilder:rbac:groups=core,resources=pods/status,verbs=get
// +kubebuilder:rbac:groups=core,resources=pods/finalizers,verbs=update
// +kubebuilder:rbac:groups=core,resources=pods/resize,verbs=update
// +kubebuilder:rbac:groups=core,resources=nodes,verbs=get

// +kubebuilder:rbac:groups=core,resources=persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=core,resources=persistentvolumeclaims/status,verbs=get
Expand Down Expand Up @@ -83,11 +84,11 @@ func (r *InstanceSetReconciler) Reconcile(ctx context.Context, req ctrl.Request)
Do(instanceset.NewFixMetaReconciler()).
Do(instanceset.NewDeletionReconciler()).
Do(instanceset.NewValidationReconciler()).
Do(instanceset.NewStatusReconciler()).
Do(instanceset.NewStatusReconciler(r.Client)).
Do(instanceset.NewRevisionUpdateReconciler()).
Do(instanceset.NewAssistantObjectReconciler()).
Do(instanceset.NewReplicasAlignmentReconciler()).
Do(instanceset.NewUpdateReconciler()).
Do(instanceset.NewUpdateReconciler(r.Client)).
Commit()

// TODO(free6om): handle error based on ErrorCode (after defined)
Expand Down
8 changes: 1 addition & 7 deletions deploy/helm/config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,9 @@ rules:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- list
- watch
- apiGroups:
Expand Down
6 changes: 6 additions & 0 deletions deploy/helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,12 @@ affinity:
# registryConfig:
# defaultRegistry: apecloud-registry.cn-zhangjiakou.cr.aliyuncs.com
# defaultNamespace: apecloud
# # Optional offline proof for runtimes that report an OCI/Docker index digest
# # instead of the platform manifest digest pinned in a PodSpec (or vice versa).
# # indexBase64 must contain the exact raw index bytes whose digest is indexDigest.
# imageIndexProofs:
# - indexDigest: sha256:<index-digest>
# indexBase64: <base64-exact-raw-index-bytes>
registryConfig: {}

# Add extra pod labels to KubeBlocks Deployment
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ require (
github.com/magiconair/properties v1.8.7
github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.36.3
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.19.0
github.com/sethvargo/go-password v0.2.0
Expand Down Expand Up @@ -115,8 +117,6 @@ require (
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
Expand Down
155 changes: 155 additions & 0 deletions pkg/controller/instance/image_index_proof_reconciler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
Copyright (C) 2022-2026 ApeCloud Co., Ltd

This file is part of KubeBlocks project

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package instance

import (
"context"
"encoding/base64"
"errors"
"strings"
"testing"

digest "github.com/opencontainers/go-digest"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

workloads "github.com/apecloud/kubeblocks/apis/workloads/v1"
"github.com/apecloud/kubeblocks/pkg/constant"
"github.com/apecloud/kubeblocks/pkg/controller/kubebuilderx"
intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
viper "github.com/apecloud/kubeblocks/pkg/viperx"
)

const instanceProofChild = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

type instanceProofReader struct {
node *corev1.Node
err error
}

func (r *instanceProofReader) Get(_ context.Context, _ client.ObjectKey, obj client.Object, _ ...client.GetOption) error {
if r.err != nil {
return r.err
}
r.node.DeepCopyInto(obj.(*corev1.Node))
return nil
}

func (r *instanceProofReader) List(context.Context, client.ObjectList, ...client.ListOption) error {
return errors.New("unexpected List")
}

func instanceProofConfig() (string, map[string]any) {
raw := []byte(`{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"` + instanceProofChild + `","size":100,"platform":{"architecture":"amd64","os":"linux"}}]}`)
parent := digest.FromBytes(raw).String()
return parent, map[string]any{
"imageIndexProofs": []map[string]any{{
"indexDigest": parent,
"indexBase64": base64.StdEncoding.EncodeToString(raw),
}},
}
}

func instanceProofPod(parent string) *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "demo", Namespace: "default"},
Spec: corev1.PodSpec{
NodeName: "node-1",
Containers: []corev1.Container{{
Name: "kbagent",
Image: "example/tools@" + instanceProofChild,
}},
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
Conditions: []corev1.PodCondition{{
Type: corev1.PodReady,
Status: corev1.ConditionTrue,
LastTransitionTime: metav1.Now(),
}},
ContainerStatuses: []corev1.ContainerStatus{{
Name: "kbagent",
Image: "example/tools:tag",
ImageID: "example/tools@" + parent,
}},
},
}
}

func TestImageIndexProofReconcilerPropagation(t *testing.T) {
oldRegistries := viper.Get(constant.CfgRegistries)
parent, config := instanceProofConfig()
viper.Set(constant.CfgRegistries, config)
if err := intctrlutil.LoadRegistryConfig(); err != nil {
t.Fatalf("LoadRegistryConfig() error = %v", err)
}
defer func() {
viper.Set(constant.CfgRegistries, oldRegistries)
if err := intctrlutil.LoadRegistryConfig(); err != nil {
t.Errorf("restore LoadRegistryConfig() error = %v", err)
}
}()

newTree := func() (*kubebuilderx.ObjectTree, *workloads.Instance, *corev1.Pod) {
inst := &workloads.Instance{ObjectMeta: metav1.ObjectMeta{Name: "demo", Namespace: "default"}}
pod := instanceProofPod(parent)
tree := kubebuilderx.NewObjectTree()
tree.Context = context.Background()
tree.SetRoot(inst)
if err := tree.Add(pod); err != nil {
t.Fatalf("tree.Add() error = %v", err)
}
return tree, inst, pod
}

t.Run("status returns transient Node errors", func(t *testing.T) {
tree, _, _ := newTree()
_, err := NewStatusReconciler(&instanceProofReader{err: errors.New("temporary Node read")}).Reconcile(tree)
if err == nil || !strings.Contains(err.Error(), "temporary Node read") {
t.Fatalf("status Reconcile() error = %v, want temporary Node read", err)
}
})

t.Run("update gate returns transient Node errors", func(t *testing.T) {
tree, inst, pod := newTree()
matched, retry, err := (&updateReconciler{reader: &instanceProofReader{
err: errors.New("temporary Node read"),
}}).isPodCanBeUpdated(tree, inst, pod)
if matched || retry || err == nil || !strings.Contains(err.Error(), "temporary Node read") {
t.Fatalf("isPodCanBeUpdated() = (%v, %v, %v), want (false, false, temporary error)", matched, retry, err)
}
})

t.Run("update gate accepts the exact Node platform relation", func(t *testing.T) {
tree, inst, pod := newTree()
reader := &instanceProofReader{node: &corev1.Node{
ObjectMeta: metav1.ObjectMeta{Name: "node-1"},
Status: corev1.NodeStatus{NodeInfo: corev1.NodeSystemInfo{
OperatingSystem: "linux",
Architecture: "amd64",
}},
}}
matched, retry, err := (&updateReconciler{reader: reader}).isPodCanBeUpdated(tree, inst, pod)
if err != nil || !matched || retry {
t.Fatalf("isPodCanBeUpdated() = (%v, %v, %v), want (true, false, nil)", matched, retry, err)
}
})
}
19 changes: 15 additions & 4 deletions pkg/controller/instance/reconciler_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

workloads "github.com/apecloud/kubeblocks/apis/workloads/v1"
"github.com/apecloud/kubeblocks/pkg/constant"
Expand All @@ -35,11 +36,17 @@ import (
intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
)

func NewStatusReconciler() kubebuilderx.Reconciler {
return &statusReconciler{}
func NewStatusReconciler(readers ...client.Reader) kubebuilderx.Reconciler {
var reader client.Reader
if len(readers) > 0 {
reader = readers[0]
}
return &statusReconciler{reader: reader}
}

type statusReconciler struct{}
type statusReconciler struct {
reader client.Reader
}

var _ kubebuilderx.Reconciler = &statusReconciler{}

Expand Down Expand Up @@ -68,7 +75,11 @@ func (r *statusReconciler) Reconcile(tree *kubebuilderx.ObjectTree) (kubebuilder
if isCreated(pod) {
notReadyName = pod.Name
}
if isImageMatched(pod) && intctrlutil.IsPodReady(pod) {
imageMatched, err := isImageMatched(tree.Context, r.reader, pod)
if err != nil {
return kubebuilderx.Continue, err
}
if imageMatched && intctrlutil.IsPodReady(pod) {
ready = true
notReadyName = ""
if intctrlutil.IsPodAvailable(pod, inst.Spec.MinReadySeconds) {
Expand Down
Loading
Loading