Skip to content
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
150 changes: 150 additions & 0 deletions apis/kubefleet.dev/placement/v1alpha1/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
Copyright 2026 The KubeFleet Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

// Verify the implementation of the accessor interfaces for the placement policy,
// placement resource snapshot, and placement binding resources.
var _ PlacementPolicyAccessor = &PlacementPolicy{}
var _ PlacementPolicyAccessor = &ClusterPlacementPolicy{}
var _ PlacementResourceSnapshotAccessor = &PlacementResourceSnapshot{}
var _ PlacementResourceSnapshotAccessor = &ClusterPlacementResourceSnapshot{}
var _ PlacementBindingAccessor = &PlacementBinding{}
var _ PlacementBindingAccessor = &ClusterPlacementBinding{}

// PlacementPolicyAccessor provides unified access to the spec and status of placement policy resources,
// namespace-scoped and cluster-scoped.
//
// +kubebuilder:object:generate=false
type PlacementPolicyAccessor interface {
client.Object

GetSpec() *PlacementPolicySpec
GetStatus() *PlacementPolicyStatus

SetSpec(PlacementPolicySpec)
SetStatus(PlacementPolicyStatus)
}

func (p *PlacementPolicy) GetSpec() *PlacementPolicySpec {
return &p.Spec
}

func (p *PlacementPolicy) GetStatus() *PlacementPolicyStatus {
return &p.Status
}

func (p *PlacementPolicy) SetSpec(spec PlacementPolicySpec) {
p.Spec = spec
}

func (p *PlacementPolicy) SetStatus(status PlacementPolicyStatus) {
p.Status = status
}

func (p *ClusterPlacementPolicy) GetSpec() *PlacementPolicySpec {
return &p.Spec
}

func (p *ClusterPlacementPolicy) GetStatus() *PlacementPolicyStatus {
return &p.Status
}

func (p *ClusterPlacementPolicy) SetSpec(spec PlacementPolicySpec) {
p.Spec = spec
}

func (p *ClusterPlacementPolicy) SetStatus(status PlacementPolicyStatus) {
p.Status = status
}

// PlacementResourceSnapshotAccessor provides unified access to the spec of placement resource snapshot resources,
// namespace-scoped and cluster-scoped.
//
// +kubebuilder:object:generate=false
type PlacementResourceSnapshotAccessor interface {
client.Object

GetSpec() *PlacementResourceSnapshotSpec

SetSpec(PlacementResourceSnapshotSpec)
}

func (p *PlacementResourceSnapshot) GetSpec() *PlacementResourceSnapshotSpec {
return &p.Spec
}

func (p *PlacementResourceSnapshot) SetSpec(spec PlacementResourceSnapshotSpec) {
p.Spec = spec
}

func (p *ClusterPlacementResourceSnapshot) GetSpec() *PlacementResourceSnapshotSpec {
return &p.Spec
}

func (p *ClusterPlacementResourceSnapshot) SetSpec(spec PlacementResourceSnapshotSpec) {
p.Spec = spec
}

// PlacementBindingAccessor provides unified access to the spec and status of placement binding resources,
// namespace-scoped and cluster-scoped.
//
// +kubebuilder:object:generate=false
type PlacementBindingAccessor interface {
client.Object

GetSpec() *PlacementBindingSpec
GetStatus() *PlacementBindingStatus

SetSpec(PlacementBindingSpec)
SetStatus(PlacementBindingStatus)
}

func (p *PlacementBinding) GetSpec() *PlacementBindingSpec {
return &p.Spec
}

func (p *PlacementBinding) GetStatus() *PlacementBindingStatus {
return &p.Status
}

func (p *PlacementBinding) SetSpec(spec PlacementBindingSpec) {
p.Spec = spec
}

func (p *PlacementBinding) SetStatus(status PlacementBindingStatus) {
p.Status = status
}

func (p *ClusterPlacementBinding) GetSpec() *PlacementBindingSpec {
return &p.Spec
}

func (p *ClusterPlacementBinding) GetStatus() *PlacementBindingStatus {
return &p.Status
}

func (p *ClusterPlacementBinding) SetSpec(spec PlacementBindingSpec) {
p.Spec = spec
}

func (p *ClusterPlacementBinding) SetStatus(status PlacementBindingStatus) {
p.Status = status
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,50 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)

const (
// When users create a placement policy to place resources across member clusters, KubeFleet will capture the
// resources selected by the placement policy at a specific point in time in the form of placement resource
// snapshots. This enables KubeFleet to roll out resources to member clusters in a consistent manner.
//
// As resources change over time, there might be a time series of placement resource snapshots associated
// with a placement policy. KubeFleet assigns these snapshots with a monotonically increasing index based on
// their creation timestamp, starting from 0 with a step of 1.
//
// Due to sizing limitations in Kubernetes, when there are too many resources being selected at a time, or
// when some resources are too large, KubeFleet will capture them using multiple placement resource snapshots.
// These snapshots share the same index (as they are snapshots from the same point in time), and KubeFleet
// will further assign them each with a sub-index to tell them apart, also starting from 0 with a step of 1.
// The snapshot of the sub-index 0 is considered the primary snapshot of the same index.

// PlacementResourceSnapshotOwnedByLabelKey is a label key that denotes the owner placement policy of
// a placement resource snapshot. Its value is the name of the owner placement policy.
//
// This label is set on all placement resource snapshots.
PlacementResourceSnapshotOwnedByLabelKey = "placement.kubefleet.dev/placement-resource-snapshot-owned-by"
// PlacementResourceSnapshotIndexLabelKey is a label key that denotes the index of a placement resource snapshot.
// Its value is the index integer formatted as a string.
//
// This label is set on all placement resource snapshots.
PlacementResourceSnapshotIndexLabelKey = "placement.kubefleet.dev/placement-resource-snapshot-index"
// PlacementResourceSnapshotSubIndexLabelKey is a label key that denotes the sub-index of a placement resource snapshot.
// Its value is the sub-index integer formatted as a string.
//
// This label is set on all placement resource snapshots.
PlacementResourceSnapshotSubIndexLabelKey = "placement.kubefleet.dev/placement-resource-snapshot-sub-index"
// SubIndexedPlacementResourceSnapshotCountLabelKey is a label key that denotes the total number of sub-indexed
// placement resource snapshots associated with the same index. Its value is the count integer
// formatted as a string.
//
// This label is set only on resource placement snapshots with the sub-index of 0.
SubIndexedPlacementResourceSnapshotCountLabelKey = "placement.kubefleet.dev/sub-indexed-placement-resource-snapshot-count"

// PlacementResourceSnapshotContentsHashAnnotationKey is an annotation key that denotes the hash of the contents
// of a placement resource snapshot. Its value is the hash string.
//
// This annotation is set on all placement resource snapshots.
PlacementResourceSnapshotContentsHashAnnotationKey = "placement.kubefleet.dev/placement-resource-snapshot-contents-hash"
)

// PlacementResourceSnapshot is the KubeFleet API that captures the resources selected by a placement policy
// as seen on the hub cluster at a specific point in time. It is referenced by other KubeFleet APIs
// to enable consistent rollouts of resources across multiple member clusters in the fleet.
Expand Down
19 changes: 19 additions & 0 deletions pkg/utils/informer/informermanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ import (
ctrlcache "sigs.k8s.io/controller-runtime/pkg/cache"
)

// Note (chenyu1): many methods in this utility, such as IsInformerSynced and Lister, will implicitly create an informer
// for the queried resource if one does not exist already. This might have side effects as such informers
// will not start until the manager's Start() method is called, provided that such resources have support
// for LIST/WATCH ops. Normally this is fine as the resource watcher is configured to periodically register
// all applicable resources in the informer manager, but the gaps between the synchronization might lead to
// unexpected behaviors (hopefully temporary). For newer code that needs to integrate with the informer manager,
// consider calling IsInformerSet first to check if an informer has been set up, before calling other methods.

// InformerManager manages dynamic shared informer for all resources, include Kubernetes resource and
// custom resources defined by CustomResourceDefinition.
type Manager interface {
Expand All @@ -42,6 +50,9 @@ type Manager interface {
// IsInformerSynced checks if the resource's informer is synced.
IsInformerSynced(resource schema.GroupVersionResource) bool

// IsInformerSet returns if an informer has been set up for the given resource.
IsInformerSet(gvk schema.GroupVersionKind) bool

Comment on lines +53 to +55
// Start will run all informers, the informers will keep running until the channel closed.
// It is intended to be called after create new informer(s), and it's safe to call multi times.
Start()
Expand Down Expand Up @@ -153,6 +164,14 @@ func (s *informerManagerImpl) IsInformerSynced(resource schema.GroupVersionResou
return s.informerFactory.ForResource(resource).Informer().HasSynced()
}

func (s *informerManagerImpl) IsInformerSet(gvk schema.GroupVersionKind) bool {
s.resourcesLock.RLock()
defer s.resourcesLock.RUnlock()

_, ok := s.apiResources[gvk]
return ok
}

func (s *informerManagerImpl) Lister(resource schema.GroupVersionResource) cache.GenericLister {
return s.informerFactory.ForResource(resource).Lister()
}
Expand Down
Loading
Loading