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
2 changes: 1 addition & 1 deletion cluster/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ type ClusterSpec struct {
// - compose the DNS name of multi-cluster services.
//
// +optional
// +kubebuilder:validation:Maxlength=128000
// +kubebuilder:validation:MaxLength=128000
ID string `json:"id,omitempty"`

// SyncMode describes how a cluster syncs resources from karmada control plane.
Expand Down
45 changes: 45 additions & 0 deletions cluster/v1alpha1/cluster_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,52 @@ limitations under the License.

package v1alpha1

import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)

// String returns a well-formatted string for the Cluster object.
func (c *Cluster) String() string {
return c.Name
}

// APIEnablementStatus is the status of the specific API on the cluster.
type APIEnablementStatus string

const (
// APIEnabled means the cluster supports the specified API.
APIEnabled APIEnablementStatus = "APIEnabled"
// APIDisabled means the cluster does not support the specified API.
APIDisabled APIEnablementStatus = "APIDisabled"
// APIUnknown means it is unknown whether the cluster supports the specified API.
APIUnknown APIEnablementStatus = "APIUnknown"
)

// APIEnablement checks if the target API (or CRD) referenced by gvk has been installed in the cluster.
// The check takes the CompleteAPIEnablements condition into account. If the CompleteAPIEnablements condition indicates
// the current APIEnablements is Partial, it returns APIEnabled if the gvk is found in the list; otherwise, the status is considered APIUnknown.
// This means that when the APIEnablements is Partial and the gvk is not present, we cannot definitively say the API is disabled.
func (c *Cluster) APIEnablement(gvk schema.GroupVersionKind) APIEnablementStatus {
targetGroupVersion := gvk.GroupVersion().String()
for _, apiEnablement := range c.Status.APIEnablements {
if apiEnablement.GroupVersion != targetGroupVersion {
continue
}
for _, resource := range apiEnablement.Resources {
if resource.Kind != gvk.Kind {
continue
}
return APIEnabled
}
}

// If we have the complete APIEnablements list for the cluster,
// we can confidently determine that the API is disabled if it was not found above.
if meta.IsStatusConditionPresentAndEqual(c.Status.Conditions, ClusterConditionCompleteAPIEnablements, metav1.ConditionTrue) {
return APIDisabled
}

return APIUnknown
}
194 changes: 194 additions & 0 deletions cluster/v1alpha1/cluster_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)

func TestString(t *testing.T) {
Expand Down Expand Up @@ -64,3 +65,196 @@ func TestString(t *testing.T) {
})
}
}

func TestAPIEnablement(t *testing.T) {
tests := []struct {
name string
cluster *Cluster
gvk schema.GroupVersionKind
expected APIEnablementStatus
}{
{
name: "API enabled - exact match found",
cluster: &Cluster{
Status: ClusterStatus{
APIEnablements: []APIEnablement{
{
GroupVersion: "apps/v1",
Resources: []APIResource{
{Name: "deployments", Kind: "Deployment"},
{Name: "replicasets", Kind: "ReplicaSet"},
},
},
},
Conditions: []metav1.Condition{
{
Type: ClusterConditionCompleteAPIEnablements,
Status: metav1.ConditionTrue,
},
},
},
},
gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
expected: APIEnabled,
},
{
name: "API disabled - not found in complete list",
cluster: &Cluster{
Status: ClusterStatus{
APIEnablements: []APIEnablement{
{
GroupVersion: "apps/v1",
Resources: []APIResource{
{Name: "deployments", Kind: "Deployment"},
},
},
},
Conditions: []metav1.Condition{
{
Type: ClusterConditionCompleteAPIEnablements,
Status: metav1.ConditionTrue,
},
},
},
},
gvk: schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "Job"},
expected: APIDisabled,
},
{
name: "API unknown - not found in partial list",
cluster: &Cluster{
Status: ClusterStatus{
APIEnablements: []APIEnablement{
{
GroupVersion: "apps/v1",
Resources: []APIResource{
{Name: "deployments", Kind: "Deployment"},
},
},
},
Conditions: []metav1.Condition{
{
Type: ClusterConditionCompleteAPIEnablements,
Status: metav1.ConditionFalse,
},
},
},
},
gvk: schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "Job"},
expected: APIUnknown,
},
{
name: "API unknown - no CompleteAPIEnablements condition",
cluster: &Cluster{
Status: ClusterStatus{
APIEnablements: []APIEnablement{
{
GroupVersion: "apps/v1",
Resources: []APIResource{
{Name: "deployments", Kind: "Deployment"},
},
},
},
Conditions: []metav1.Condition{},
},
},
gvk: schema.GroupVersionKind{Group: "batch", Version: "v1", Kind: "Job"},
expected: APIUnknown,
},
{
name: "API enabled - found in core group",
cluster: &Cluster{
Status: ClusterStatus{
APIEnablements: []APIEnablement{
{
GroupVersion: "v1",
Resources: []APIResource{
{Name: "pods", Kind: "Pod"},
{Name: "services", Kind: "Service"},
},
},
},
Conditions: []metav1.Condition{
{
Type: ClusterConditionCompleteAPIEnablements,
Status: metav1.ConditionTrue,
},
},
},
},
gvk: schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"},
expected: APIEnabled,
},
{
name: "API disabled - wrong kind in same group version",
cluster: &Cluster{
Status: ClusterStatus{
APIEnablements: []APIEnablement{
{
GroupVersion: "apps/v1",
Resources: []APIResource{
{Name: "deployments", Kind: "Deployment"},
},
},
},
Conditions: []metav1.Condition{
{
Type: ClusterConditionCompleteAPIEnablements,
Status: metav1.ConditionTrue,
},
},
},
},
gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"},
expected: APIDisabled,
},
{
name: "API disabled - empty APIEnablements with complete condition",
cluster: &Cluster{
Status: ClusterStatus{
APIEnablements: []APIEnablement{},
Conditions: []metav1.Condition{
{
Type: ClusterConditionCompleteAPIEnablements,
Status: metav1.ConditionTrue,
},
},
},
},
gvk: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
expected: APIDisabled,
},
{
name: "API enabled - custom resource found",
cluster: &Cluster{
Status: ClusterStatus{
APIEnablements: []APIEnablement{
{
GroupVersion: "example.com/v1alpha1",
Resources: []APIResource{
{Name: "customresources", Kind: "CustomResource"},
},
},
},
Conditions: []metav1.Condition{
{
Type: ClusterConditionCompleteAPIEnablements,
Status: metav1.ConditionTrue,
},
},
},
},
gvk: schema.GroupVersionKind{Group: "example.com", Version: "v1alpha1", Kind: "CustomResource"},
expected: APIEnabled,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.cluster.APIEnablement(tt.gvk)
if result != tt.expected {
t.Errorf("APIEnablement() = %v, want %v", result, tt.expected)
}
})
}
}
2 changes: 1 addition & 1 deletion cluster/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ type ClusterSpec struct {
// - compose the DNS name of multi-cluster services.
//
// +optional
// +kubebuilder:validation:Maxlength=128000
// +kubebuilder:validation:MaxLength=128000
ID string `json:"id,omitempty"`

// SyncMode describes how a cluster syncs resources from karmada control plane.
Expand Down
10 changes: 10 additions & 0 deletions config/v1alpha1/interpretercontext_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ type ResourceInterpreterResponse struct {
// +optional
Replicas *int32 `json:"replicas,omitempty"`

// Components represents the requirements of multiple pod templates of the referencing resource.
// It is designed to support workloads that consist of multiple pod templates,
// such as distributed training jobs (e.g., PyTorch, TensorFlow) and big data workloads (e.g., FlinkDeployment),
// where each workload is composed of more than one pod template. It is also capable of representing
// single-component workloads, such as Deployment.
//
// Required if InterpreterOperation is InterpreterOperationInterpretComponent.
// +optional
Components []workv1alpha2.Component `json:"components,omitempty"`

// Dependencies represents the reference of dependencies object.
// Required if InterpreterOperation is InterpreterOperationInterpretDependency.
// +optional
Expand Down
56 changes: 56 additions & 0 deletions config/v1alpha1/resourceinterpretercustomization_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ type CustomizationRules struct {
// +optional
ReplicaResource *ReplicaResourceRequirement `json:"replicaResource,omitempty"`

// ComponentResource describes the rules for Karmada to discover the resource requirements
// for multiple components from the given object.
// This is designed for CRDs with multiple components (e.g., FlinkDeployment), but
// can also be used for single-component resources like Deployment.
// If implemented, the controller will use this to obtain per-component replica and resource
// requirements, and will not call ReplicaResource.
// If not implemented, the controller will fall back to ReplicaResource for backward compatibility.
// This will only be used when the feature gate 'MultiplePodTemplatesScheduling' is enabled.
// +optional
ComponentResource *ComponentResourceRequirement `json:"componentResource,omitempty"`

// ReplicaRevision describes the rules for Karmada to revise the resource's replica.
// It would be useful for those CRD resources that declare workload types like
// Deployment.
Expand Down Expand Up @@ -203,6 +214,51 @@ type ReplicaResourceRequirement struct {
LuaScript string `json:"luaScript"`
}

// ComponentResourceRequirement holds the scripts for extracting the desired replica count
// and resource requirements for each component within a resource. This is particularly useful for
// resources that define multiple components (such as CRDs with multiple pod templates), but can also
// be used for single-component resources.
type ComponentResourceRequirement struct {
// LuaScript holds the Lua script that is used to extract the desired replica count and resource
// requirements for each component of the resource.
//
// The script should implement a function as follows:
//
// ```
// luaScript: >
// function GetComponents(desiredObj)
// local components = {}
//
// local jobManagerComponent = {
// name = "jobmanager",
// replicas = desiredObj.spec.jobManager.replicas
// }
// table.insert(components, jobManagerComponent)
//
// local taskManagerComponent = {
// name = "taskmanager",
// replicas = desiredObj.spec.taskManager.replicas
// }
// table.insert(components, taskManagerComponent)
//
// return components
// end
// ```
//
// The content of the LuaScript needs to be a whole function including both
// declaration and implementation.
//
// The parameters will be supplied by the system:
// - desiredObj: the object represents the configuration to be applied
// to the member cluster.
//
// The function expects one return value:
// - components: the resource requirements for each component.
// The returned value will be set into a ResourceBinding or ClusterResourceBinding.
// +required
LuaScript string `json:"luaScript"`
}

// ReplicaRevision holds the scripts for revising the desired replicas.
type ReplicaRevision struct {
// LuaScript holds the Lua script that is used to revise replicas in the desired specification.
Expand Down
8 changes: 8 additions & 0 deletions config/v1alpha1/resourceinterpreterwebhook_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ const (
// Only necessary for those resource types that have replica declaration, like Deployment or similar custom resources.
InterpreterOperationInterpretReplica InterpreterOperation = "InterpretReplica"

// InterpreterOperationInterpretComponent indicates that karmada wants to figure out
// resource requirements for multiple components from a given object.
// This operation is designed for CRDs with multiple components (e.g., FlinkDeployment),
// but can also be used for single-component resources.
// If an interpreter supports this operation, 'InterpretReplica' will not be called.
// This operation is only used when the feature gate 'MultiplePodTemplatesScheduling' is enabled.
InterpreterOperationInterpretComponent InterpreterOperation = "InterpretComponent"

// InterpreterOperationReviseReplica indicates that karmada request webhook to modify the replica.
InterpreterOperationReviseReplica InterpreterOperation = "ReviseReplica"

Expand Down
Loading