Add replicas for cdi infra (#2933)

* Add deployment replicas set for cdi infra

Signed-off-by: zhuanlan <zhuanlan_yewu@cmss.chinamobile.com>

* Add podAntiAffinity for cdi deployment

Signed-off-by: zhuanlan <zhuanlan_yewu@cmss.chinamobile.com>

* Fix linter problem

Signed-off-by: zhuanlan <zhuanlan_yewu@cmss.chinamobile.com>

* add e2e tests modify the new replica count fields and verify the results

Signed-off-by: zhuanlan <zhuanlan_yewu@cmss.chinamobile.com>

---------

Signed-off-by: zhuanlan <zhuanlan_yewu@cmss.chinamobile.com>
This commit is contained in:
Longchuanzheng 2023-12-18 11:01:08 +08:00 committed by GitHub
parent 9a5f11e1b2
commit a5f17e4f8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 446 additions and 60 deletions

View File

@ -5056,9 +5056,9 @@
]
},
"infra": {
"description": "Rules on which nodes CDI infrastructure pods will be scheduled",
"description": "Selectors and tolerations that should apply to cdi infrastructure components",
"default": {},
"$ref": "#/definitions/api.NodePlacement"
"$ref": "#/definitions/v1beta1.ComponentConfig"
},
"priorityClass": {
"description": "PriorityClass of the CDI control plane",
@ -5118,6 +5118,47 @@
}
}
},
"v1beta1.ComponentConfig": {
"description": "ComponentConfig defines the scheduling and replicas configuration for CDI components",
"type": "object",
"properties": {
"affinity": {
"description": "affinity enables pod affinity/anti-affinity placement expanding the types of constraints that can be expressed with nodeSelector. affinity is going to be applied to the relevant kind of pods in parallel with nodeSelector See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity",
"$ref": "#/definitions/v1.Affinity"
},
"apiServerReplicas": {
"description": "ApiserverReplicas set Replicas for cdi-apiserver",
"type": "integer",
"format": "int32"
},
"deploymentReplicas": {
"description": "DeploymentReplicas set Replicas for cdi-deployment",
"type": "integer",
"format": "int32"
},
"nodeSelector": {
"description": "nodeSelector is the node selector applied to the relevant kind of pods It specifies a map of key-value pairs: for the pod to be eligible to run on a node, the node must have each of the indicated key-value pairs as labels (it can have additional labels as well). See https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector",
"type": "object",
"additionalProperties": {
"type": "string",
"default": ""
}
},
"tolerations": {
"description": "tolerations is a list of tolerations applied to the relevant kind of pods See https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ for more info. These are additional tolerations other than default ones.",
"type": "array",
"items": {
"default": {},
"$ref": "#/definitions/v1.Toleration"
}
},
"uploadProxyReplicas": {
"description": "UploadproxyReplicas set Replicas for cdi-uploadproxy",
"type": "integer",
"format": "int32"
}
}
},
"v1beta1.DataImportCron": {
"description": "DataImportCron defines a cron job for recurring polling/importing disk images as PVCs into a golden image namespace",
"type": "object",

View File

@ -534,6 +534,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1.CDIStatus": schema_pkg_apis_core_v1beta1_CDIStatus(ref),
"kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1.CertConfig": schema_pkg_apis_core_v1beta1_CertConfig(ref),
"kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1.ClaimPropertySet": schema_pkg_apis_core_v1beta1_ClaimPropertySet(ref),
"kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1.ComponentConfig": schema_pkg_apis_core_v1beta1_ComponentConfig(ref),
"kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1.ConditionState": schema_pkg_apis_core_v1beta1_ConditionState(ref),
"kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1.DataImportCron": schema_pkg_apis_core_v1beta1_DataImportCron(ref),
"kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1.DataImportCronCondition": schema_pkg_apis_core_v1beta1_DataImportCronCondition(ref),
@ -25243,9 +25244,9 @@ func schema_pkg_apis_core_v1beta1_CDISpec(ref common.ReferenceCallback) common.O
},
"infra": {
SchemaProps: spec.SchemaProps{
Description: "Rules on which nodes CDI infrastructure pods will be scheduled",
Description: "Selectors and tolerations that should apply to cdi infrastructure components",
Default: map[string]interface{}{},
Ref: ref("kubevirt.io/controller-lifecycle-operator-sdk/api.NodePlacement"),
Ref: ref("kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1.ComponentConfig"),
},
},
"workload": {
@ -25285,7 +25286,7 @@ func schema_pkg_apis_core_v1beta1_CDISpec(ref common.ReferenceCallback) common.O
},
},
Dependencies: []string{
"kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1.CDICertConfig", "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1.CDIConfigSpec", "kubevirt.io/controller-lifecycle-operator-sdk/api.NodePlacement"},
"kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1.CDICertConfig", "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1.CDIConfigSpec", "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1.ComponentConfig", "kubevirt.io/controller-lifecycle-operator-sdk/api.NodePlacement"},
}
}
@ -25408,6 +25409,78 @@ func schema_pkg_apis_core_v1beta1_ClaimPropertySet(ref common.ReferenceCallback)
}
}
func schema_pkg_apis_core_v1beta1_ComponentConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "ComponentConfig defines the scheduling and replicas configuration for CDI components",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"nodeSelector": {
SchemaProps: spec.SchemaProps{
Description: "nodeSelector is the node selector applied to the relevant kind of pods It specifies a map of key-value pairs: for the pod to be eligible to run on a node, the node must have each of the indicated key-value pairs as labels (it can have additional labels as well). See https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
"affinity": {
SchemaProps: spec.SchemaProps{
Description: "affinity enables pod affinity/anti-affinity placement expanding the types of constraints that can be expressed with nodeSelector. affinity is going to be applied to the relevant kind of pods in parallel with nodeSelector See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity",
Ref: ref("k8s.io/api/core/v1.Affinity"),
},
},
"tolerations": {
SchemaProps: spec.SchemaProps{
Description: "tolerations is a list of tolerations applied to the relevant kind of pods See https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ for more info. These are additional tolerations other than default ones.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/api/core/v1.Toleration"),
},
},
},
},
},
"deploymentReplicas": {
SchemaProps: spec.SchemaProps{
Description: "DeploymentReplicas set Replicas for cdi-deployment",
Type: []string{"integer"},
Format: "int32",
},
},
"apiServerReplicas": {
SchemaProps: spec.SchemaProps{
Description: "ApiserverReplicas set Replicas for cdi-apiserver",
Type: []string{"integer"},
Format: "int32",
},
},
"uploadProxyReplicas": {
SchemaProps: spec.SchemaProps{
Description: "UploadproxyReplicas set Replicas for cdi-uploadproxy",
Type: []string{"integer"},
Format: "int32",
},
},
},
},
},
Dependencies: []string{
"k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Toleration"},
}
}
func schema_pkg_apis_core_v1beta1_ConditionState(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{

View File

@ -21,6 +21,8 @@ const (
CDIComponentLabel = "cdi.kubevirt.io"
// CDIControllerName is the CDI controller name
CDIControllerName = "cdi-controller"
// CDIOperatorName is the CDI operator name
CDIOperatorName = "cdi-operator"
// AppKubernetesPartOfLabel is the Kubernetes recommended part-of label
AppKubernetesPartOfLabel = "app.kubernetes.io/part-of"

View File

@ -104,7 +104,17 @@ func (r *ReconcileCDI) getNamespacedArgs(cr *cdiv1.CDI) *cdinamespaced.FactoryAr
// Any error we cannot determine if priority class exists.
result.PriorityClassName = ""
}
result.InfraNodePlacement = &cr.Spec.Infra
result.InfraNodePlacement = &cr.Spec.Infra.NodePlacement
if cr.Spec.Infra.DeploymentReplicas != nil {
result.ControllerReplicas = *cr.Spec.Infra.DeploymentReplicas
}
if cr.Spec.Infra.APIServerReplicas != nil {
result.APIServerReplicas = *cr.Spec.Infra.APIServerReplicas
}
if cr.Spec.Infra.UploadProxyReplicas != nil {
result.UploadProxyReplicas = *cr.Spec.Infra.UploadProxyReplicas
}
}
return &result

View File

@ -345,8 +345,8 @@ spec:
- Never
type: string
infra:
description: Rules on which nodes CDI infrastructure pods will be
scheduled
description: Selectors and tolerations that should apply to cdi infrastructure
components
properties:
affinity:
description: affinity enables pod affinity/anti-affinity placement
@ -1219,6 +1219,14 @@ spec:
type: array
type: object
type: object
apiServerReplicas:
description: ApiserverReplicas set Replicas for cdi-apiserver
format: int32
type: integer
deploymentReplicas:
description: DeploymentReplicas set Replicas for cdi-deployment
format: int32
type: integer
nodeSelector:
additionalProperties:
type: string
@ -1272,6 +1280,10 @@ spec:
type: string
type: object
type: array
uploadProxyReplicas:
description: UploadproxyReplicas set Replicas for cdi-uploadproxy
format: int32
type: integer
type: object
priorityClass:
description: PriorityClass of the CDI control plane
@ -2585,8 +2597,8 @@ spec:
- Never
type: string
infra:
description: Rules on which nodes CDI infrastructure pods will be
scheduled
description: Selectors and tolerations that should apply to cdi infrastructure
components
properties:
affinity:
description: affinity enables pod affinity/anti-affinity placement
@ -3459,6 +3471,14 @@ spec:
type: array
type: object
type: object
apiServerReplicas:
description: ApiserverReplicas set Replicas for cdi-apiserver
format: int32
type: integer
deploymentReplicas:
description: DeploymentReplicas set Replicas for cdi-deployment
format: int32
type: integer
nodeSelector:
additionalProperties:
type: string
@ -3512,6 +3532,10 @@ spec:
type: string
type: object
type: array
uploadProxyReplicas:
description: UploadproxyReplicas set Replicas for cdi-uploadproxy
format: int32
type: integer
type: object
priorityClass:
description: PriorityClass of the CDI control plane

View File

@ -47,7 +47,7 @@ func createAPIServerResources(args *FactoryArgs) []client.Object {
createAPIServerRoleBinding(),
createAPIServerRole(),
createAPIServerService(),
createAPIServerDeployment(args.APIServerImage, args.Verbosity, args.PullPolicy, args.ImagePullSecrets, args.PriorityClassName, args.InfraNodePlacement),
createAPIServerDeployment(args.APIServerImage, args.Verbosity, args.PullPolicy, args.ImagePullSecrets, args.PriorityClassName, args.InfraNodePlacement, args.APIServerReplicas),
}
}
@ -98,12 +98,15 @@ func createAPIServerService() *corev1.Service {
return service
}
func createAPIServerDeployment(image, verbosity, pullPolicy string, imagePullSecrets []corev1.LocalObjectReference, priorityClassName string, infraNodePlacement *sdkapi.NodePlacement) *appsv1.Deployment {
func createAPIServerDeployment(image, verbosity, pullPolicy string, imagePullSecrets []corev1.LocalObjectReference, priorityClassName string, infraNodePlacement *sdkapi.NodePlacement, replicas int32) *appsv1.Deployment {
defaultMode := corev1.ConfigMapVolumeSourceDefaultMode
deployment := utils.CreateDeployment(apiServerRessouceName, cdiLabel, apiServerRessouceName, apiServerRessouceName, imagePullSecrets, 1, infraNodePlacement)
if priorityClassName != "" {
deployment.Spec.Template.Spec.PriorityClassName = priorityClassName
}
if replicas > 1 {
deployment.Spec.Replicas = &replicas
}
container := utils.CreateContainer(apiServerRessouceName, image, verbosity, pullPolicy)
container.Env = []corev1.EnvVar{
{

View File

@ -51,7 +51,8 @@ func createControllerResources(args *FactoryArgs) []client.Object {
args.PullPolicy,
args.ImagePullSecrets,
args.PriorityClassName,
args.InfraNodePlacement),
args.InfraNodePlacement,
args.ControllerReplicas),
createPrometheusService(),
}
}
@ -172,12 +173,15 @@ func createControllerServiceAccount() *corev1.ServiceAccount {
return utils.ResourceBuilder.CreateServiceAccount(common.ControllerServiceAccountName)
}
func createControllerDeployment(controllerImage, importerImage, clonerImage, uploadServerImage, verbosity, pullPolicy string, imagePullSecrets []corev1.LocalObjectReference, priorityClassName string, infraNodePlacement *sdkapi.NodePlacement) *appsv1.Deployment {
func createControllerDeployment(controllerImage, importerImage, clonerImage, uploadServerImage, verbosity, pullPolicy string, imagePullSecrets []corev1.LocalObjectReference, priorityClassName string, infraNodePlacement *sdkapi.NodePlacement, replicas int32) *appsv1.Deployment {
defaultMode := corev1.ConfigMapVolumeSourceDefaultMode
deployment := utils.CreateDeployment(controllerResourceName, "app", "containerized-data-importer", common.ControllerServiceAccountName, imagePullSecrets, int32(1), infraNodePlacement)
if priorityClassName != "" {
deployment.Spec.Template.Spec.PriorityClassName = priorityClassName
}
if replicas > 1 {
deployment.Spec.Replicas = &replicas
}
container := utils.CreateContainer("cdi-controller", controllerImage, verbosity, pullPolicy)
container.Ports = []corev1.ContainerPort{
{
@ -187,6 +191,8 @@ func createControllerDeployment(controllerImage, importerImage, clonerImage, upl
},
}
labels := util.MergeLabels(deployment.Spec.Template.GetLabels(), map[string]string{common.PrometheusLabelKey: common.PrometheusLabelValue})
//Add label for pod affinity
labels = util.AppendLabels(labels, map[string]string{cdiLabel: controllerResourceName})
deployment.SetLabels(labels)
deployment.Spec.Template.SetLabels(labels)
container.Env = []corev1.EnvVar{

View File

@ -45,6 +45,9 @@ type FactoryArgs struct {
PriorityClassName string
Namespace string
InfraNodePlacement *sdkapi.NodePlacement
ControllerReplicas int32
APIServerReplicas int32
UploadProxyReplicas int32
}
type factoryFunc func(*FactoryArgs) []client.Object

View File

@ -39,7 +39,7 @@ func createUploadProxyResources(args *FactoryArgs) []client.Object {
createUploadProxyService(),
createUploadProxyRoleBinding(),
createUploadProxyRole(),
createUploadProxyDeployment(args.UploadProxyImage, args.Verbosity, args.PullPolicy, args.ImagePullSecrets, args.PriorityClassName, args.InfraNodePlacement),
createUploadProxyDeployment(args.UploadProxyImage, args.Verbosity, args.PullPolicy, args.ImagePullSecrets, args.PriorityClassName, args.InfraNodePlacement, args.UploadProxyReplicas),
}
}
@ -87,12 +87,15 @@ func createUploadProxyRole() *rbacv1.Role {
return utils.ResourceBuilder.CreateRole(uploadProxyResourceName, getUploadProxyNamespacedRules())
}
func createUploadProxyDeployment(image, verbosity, pullPolicy string, imagePullSecrets []corev1.LocalObjectReference, priorityClassName string, infraNodePlacement *sdkapi.NodePlacement) *appsv1.Deployment {
func createUploadProxyDeployment(image, verbosity, pullPolicy string, imagePullSecrets []corev1.LocalObjectReference, priorityClassName string, infraNodePlacement *sdkapi.NodePlacement, replicas int32) *appsv1.Deployment {
defaultMode := corev1.ConfigMapVolumeSourceDefaultMode
deployment := utils.CreateDeployment(uploadProxyResourceName, cdiLabel, uploadProxyResourceName, uploadProxyResourceName, imagePullSecrets, int32(1), infraNodePlacement)
if priorityClassName != "" {
deployment.Spec.Template.Spec.PriorityClassName = priorityClassName
}
if replicas > 1 {
deployment.Spec.Replicas = &replicas
}
container := utils.CreateContainer(uploadProxyResourceName, image, verbosity, pullPolicy)
container.Env = []corev1.EnvVar{
{

View File

@ -10,6 +10,7 @@ go_library(
"//pkg/util:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
"//vendor/kubevirt.io/controller-lifecycle-operator-sdk/api:go_default_library",
"//vendor/kubevirt.io/controller-lifecycle-operator-sdk/pkg/sdk/resources:go_default_library",

View File

@ -19,6 +19,7 @@ package utils
import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
"kubevirt.io/containerized-data-importer/pkg/common"
@ -94,7 +95,12 @@ func CreateDeployment(name, matchKey, matchValue, serviceAccountName string, ima
},
ImagePullSecrets: imagePullSecrets,
}
deployment := ResourceBuilder.CreateDeployment(name, "", matchKey, matchValue, serviceAccountName, replicas, podSpec, infraNodePlacement)
inpCopy := infraNodePlacement.DeepCopy()
if inpCopy == nil {
inpCopy = &sdkapi.NodePlacement{}
}
inpCopy.Affinity = AddPodPreferredDuringSchedulingIgnoredDuringExecution(name, inpCopy.Affinity)
deployment := ResourceBuilder.CreateDeployment(name, "", matchKey, matchValue, serviceAccountName, replicas, podSpec, inpCopy)
return deployment
}
@ -113,10 +119,66 @@ func CreateOperatorDeployment(name, namespace, matchKey, matchValue, serviceAcco
Operator: corev1.TolerationOpExists,
},
},
Affinity: &corev1.Affinity{
PodAffinity: &corev1.PodAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{
{
Weight: int32(1),
PodAffinityTerm: corev1.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "cdi.kubevirt.io",
Operator: metav1.LabelSelectorOpIn,
Values: []string{name}},
},
},
TopologyKey: "kubernetes.io/hostname",
},
},
},
},
},
}
deployment := ResourceBuilder.CreateOperatorDeployment(name, namespace, matchKey, matchValue, serviceAccount, numReplicas, podSpec)
labels := util.MergeLabels(deployment.Spec.Template.GetLabels(), map[string]string{common.PrometheusLabelKey: common.PrometheusLabelValue})
labels := util.MergeLabels(deployment.Spec.Template.GetLabels(), map[string]string{common.PrometheusLabelKey: common.PrometheusLabelValue, common.CDIComponentLabel: common.CDIOperatorName})
deployment.SetLabels(labels)
deployment.Spec.Template.SetLabels(labels)
return deployment
}
// AddPodPreferredDuringSchedulingIgnoredDuringExecution to affinity
func AddPodPreferredDuringSchedulingIgnoredDuringExecution(name string, affinity *corev1.Affinity) *corev1.Affinity {
var affinityCopy *corev1.Affinity
preferredDuringSchedulingIgnoredDuringExecution := corev1.WeightedPodAffinityTerm{
Weight: int32(1),
PodAffinityTerm: corev1.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "cdi.kubevirt.io",
Operator: metav1.LabelSelectorOpIn,
Values: []string{name}},
},
},
TopologyKey: "kubernetes.io/hostname",
},
}
if affinity != nil && affinity.PodAntiAffinity != nil {
affinityCopy = affinity.DeepCopy()
affinityCopy.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append(affinityCopy.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, preferredDuringSchedulingIgnoredDuringExecution)
} else if affinity != nil {
affinityCopy = affinity.DeepCopy()
affinityCopy.PodAntiAffinity = &corev1.PodAntiAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{preferredDuringSchedulingIgnoredDuringExecution},
}
} else {
affinityCopy = &corev1.Affinity{
PodAntiAffinity: &corev1.PodAntiAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{preferredDuringSchedulingIgnoredDuringExecution},
},
}
}
return affinityCopy
}

View File

@ -337,6 +337,19 @@ func MergeLabels(src, dest map[string]string) map[string]string {
return dest
}
// AppendLabels append dest labels to source (update if key has existed)
func AppendLabels(src, dest map[string]string) map[string]string {
if src == nil {
src = map[string]string{}
}
for k, v := range dest {
src[k] = v
}
return src
}
// GetRecommendedInstallerLabelsFromCr returns the recommended labels to set on CDI resources
func GetRecommendedInstallerLabelsFromCr(cr *cdiv1.CDI) map[string]string {
labels := map[string]string{}

View File

@ -824,8 +824,8 @@ type CDISpec struct {
// +kubebuilder:validation:Enum=RemoveWorkloads;BlockUninstallIfWorkloadsExist
// CDIUninstallStrategy defines the state to leave CDI on uninstall
UninstallStrategy *CDIUninstallStrategy `json:"uninstallStrategy,omitempty"`
// Rules on which nodes CDI infrastructure pods will be scheduled
Infra sdkapi.NodePlacement `json:"infra,omitempty"`
// Selectors and tolerations that should apply to cdi infrastructure components
Infra ComponentConfig `json:"infra,omitempty"`
// Restrict on which nodes CDI workload pods will be scheduled
Workloads sdkapi.NodePlacement `json:"workload,omitempty"`
// Clone strategy override: should we use a host-assisted copy even if snapshots are available?
@ -839,6 +839,18 @@ type CDISpec struct {
PriorityClass *CDIPriorityClass `json:"priorityClass,omitempty"`
}
// ComponentConfig defines the scheduling and replicas configuration for CDI components
type ComponentConfig struct {
// NodePlacement describes scheduling configuration for specific CDI components
sdkapi.NodePlacement `json:",inline"`
// DeploymentReplicas set Replicas for cdi-deployment
DeploymentReplicas *int32 `json:"deploymentReplicas,omitempty"`
// ApiserverReplicas set Replicas for cdi-apiserver
APIServerReplicas *int32 `json:"apiServerReplicas,omitempty"`
// UploadproxyReplicas set Replicas for cdi-uploadproxy
UploadProxyReplicas *int32 `json:"uploadProxyReplicas,omitempty"`
}
// CDIPriorityClass defines the priority class of the CDI control plane.
type CDIPriorityClass string

View File

@ -428,7 +428,7 @@ func (CDISpec) SwaggerDoc() map[string]string {
"": "CDISpec defines our specification for the CDI installation",
"imagePullPolicy": "+kubebuilder:validation:Enum=Always;IfNotPresent;Never\nPullPolicy describes a policy for if/when to pull a container image",
"uninstallStrategy": "+kubebuilder:validation:Enum=RemoveWorkloads;BlockUninstallIfWorkloadsExist\nCDIUninstallStrategy defines the state to leave CDI on uninstall",
"infra": "Rules on which nodes CDI infrastructure pods will be scheduled",
"infra": "Selectors and tolerations that should apply to cdi infrastructure components",
"workload": "Restrict on which nodes CDI workload pods will be scheduled",
"cloneStrategyOverride": "Clone strategy override: should we use a host-assisted copy even if snapshots are available?\n+kubebuilder:validation:Enum=\"copy\";\"snapshot\";\"csi-clone\"",
"config": "CDIConfig at CDI level",
@ -437,6 +437,15 @@ func (CDISpec) SwaggerDoc() map[string]string {
}
}
func (ComponentConfig) SwaggerDoc() map[string]string {
return map[string]string{
"": "ComponentConfig defines the scheduling and replicas configuration for CDI components",
"deploymentReplicas": "DeploymentReplicas set Replicas for cdi-deployment",
"apiServerReplicas": "ApiserverReplicas set Replicas for cdi-apiserver",
"uploadProxyReplicas": "UploadproxyReplicas set Replicas for cdi-uploadproxy",
}
}
func (CDIStatus) SwaggerDoc() map[string]string {
return map[string]string{
"": "CDIStatus defines the status of the installation",

View File

@ -405,6 +405,38 @@ func (in *ClaimPropertySet) DeepCopy() *ClaimPropertySet {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ComponentConfig) DeepCopyInto(out *ComponentConfig) {
*out = *in
in.NodePlacement.DeepCopyInto(&out.NodePlacement)
if in.DeploymentReplicas != nil {
in, out := &in.DeploymentReplicas, &out.DeploymentReplicas
*out = new(int32)
**out = **in
}
if in.APIServerReplicas != nil {
in, out := &in.APIServerReplicas, &out.APIServerReplicas
*out = new(int32)
**out = **in
}
if in.UploadProxyReplicas != nil {
in, out := &in.UploadProxyReplicas, &out.UploadProxyReplicas
*out = new(int32)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentConfig.
func (in *ComponentConfig) DeepCopy() *ComponentConfig {
if in == nil {
return nil
}
out := new(ComponentConfig)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConditionState) DeepCopyInto(out *ConditionState) {
*out = *in

View File

@ -2,8 +2,6 @@ package framework
import (
"context"
"fmt"
"reflect"
"runtime"
sdkapi "kubevirt.io/controller-lifecycle-operator-sdk/api"
@ -13,9 +11,12 @@ import (
)
var (
nodeSelectorTestValue = map[string]string{"kubernetes.io/arch": runtime.GOARCH}
tolerationsTestValue = []v1.Toleration{{Key: "test", Value: "123"}}
affinityTestValue = &v1.Affinity{}
//NodeSelectorTestValue is nodeSelector value for test
NodeSelectorTestValue = map[string]string{"kubernetes.io/arch": runtime.GOARCH}
//TolerationsTestValue is tolerations value for test
TolerationsTestValue = []v1.Toleration{{Key: "test", Value: "123"}}
//AffinityTestValue is affinity value for test
AffinityTestValue = &v1.Affinity{}
)
// TestNodePlacementValues returns a pre-defined set of node placement values for testing purposes.
@ -31,7 +32,7 @@ func (f *Framework) TestNodePlacementValues() sdkapi.NodePlacement {
}
}
affinityTestValue = &v1.Affinity{
AffinityTestValue = &v1.Affinity{
NodeAffinity: &v1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{
@ -46,31 +47,8 @@ func (f *Framework) TestNodePlacementValues() sdkapi.NodePlacement {
}
return sdkapi.NodePlacement{
NodeSelector: nodeSelectorTestValue,
Affinity: affinityTestValue,
Tolerations: tolerationsTestValue,
NodeSelector: NodeSelectorTestValue,
Affinity: AffinityTestValue,
Tolerations: TolerationsTestValue,
}
}
// PodSpecHasTestNodePlacementValues compares if the pod spec has the set of node placement values defined for testing purposes
func (f *Framework) PodSpecHasTestNodePlacementValues(podSpec v1.PodSpec) bool {
if !reflect.DeepEqual(podSpec.NodeSelector, nodeSelectorTestValue) {
fmt.Printf("mismatched nodeSelectors, podSpec:\n%v\nExpected:\n%v\n", podSpec.NodeSelector, nodeSelectorTestValue)
return false
}
if !reflect.DeepEqual(podSpec.Affinity, affinityTestValue) {
fmt.Printf("mismatched affinity, podSpec:\n%v\nExpected:\n%v\n", *podSpec.Affinity, affinityTestValue)
return false
}
foundMatchingTolerations := false
for _, toleration := range podSpec.Tolerations {
if toleration == tolerationsTestValue[0] {
foundMatchingTolerations = true
}
}
if !foundMatchingTolerations {
fmt.Printf("no matching tolerations found. podSpec:\n%v\nExpected:\n%v\n", podSpec.Tolerations, tolerationsTestValue)
return false
}
return true
}

View File

@ -437,8 +437,11 @@ var _ = Describe("[rfe_id:4784][crit:high] Importer respects node placement", Se
Expect(err).NotTo(HaveOccurred(), "Unable to get importer pod")
By("Verify the import pod has nodeSelector")
match := f.PodSpecHasTestNodePlacementValues(importer.Spec)
Expect(match).To(BeTrue(), fmt.Sprintf("node placement in pod spec\n%v\n differs from node placement values in CDI CR\n%v\n", importer.Spec, cr.Spec.Workloads))
Expect(importer.Spec.NodeSelector).To(Equal(framework.NodeSelectorTestValue))
By("Verify the import pod has affinity")
Expect(importer.Spec.Affinity).To(Equal(framework.AffinityTestValue))
By("Verify the import pod has tolerations")
Expect(importer.Spec.Tolerations).To(ContainElement(framework.TolerationsTestValue[0]))
})
})

View File

@ -104,7 +104,7 @@ var _ = Describe("[Destructive] Monitoring Tests", Serial, func() {
},
Spec: cr.Spec,
}
cdi.Spec.Infra.NodeSelector = map[string]string{"wrong": "wrong"}
cdi.Spec.Infra.NodePlacement.NodeSelector = map[string]string{"wrong": "wrong"}
_, err := f.CdiClient.CdiV1beta1().CDIs().Create(context.TODO(), cdi, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())

View File

@ -328,7 +328,7 @@ var _ = Describe("ALL Operator tests", func() {
Operator: corev1.TolerationOpExists,
}
if !tolerationExists(cr.Spec.Infra.Tolerations, criticalAddonsToleration) {
if !tolerationExists(cr.Spec.Infra.NodePlacement.Tolerations, criticalAddonsToleration) {
Skip("Unexpected CDI CR (not from cdi-cr.yaml), doesn't tolerate CriticalAddonsOnly")
}
@ -626,7 +626,9 @@ var _ = Describe("ALL Operator tests", func() {
It("[test_id:4782] Should install CDI infrastructure pods with node placement", func() {
By("Creating modified CDI CR, with infra nodePlacement")
localSpec := restoreCdiCr.Spec.DeepCopy()
localSpec.Infra = f.TestNodePlacementValues()
nodePlacement := f.TestNodePlacementValues()
localSpec.Infra.NodePlacement = nodePlacement
tempCdiCr := &cdiv1.CDI{
ObjectMeta: metav1.ObjectMeta{
@ -641,9 +643,14 @@ var _ = Describe("ALL Operator tests", func() {
for _, deploymentName := range []string{"cdi-apiserver", "cdi-deployment", "cdi-uploadproxy"} {
deployment, err := f.K8sClient.AppsV1().Deployments(f.CdiInstallNs).Get(context.TODO(), deploymentName, metav1.GetOptions{})
Expect(err).ToNot(HaveOccurred())
By("Verify the deployment has nodeSelector")
Expect(deployment.Spec.Template.Spec.NodeSelector).To(Equal(framework.NodeSelectorTestValue))
match := f.PodSpecHasTestNodePlacementValues(deployment.Spec.Template.Spec)
Expect(match).To(BeTrue(), fmt.Sprintf("node placement in pod spec\n%v\n differs from node placement values in CDI CR\n%v\n", deployment.Spec.Template.Spec, localSpec.Infra))
By("Verify the deployment has affinity")
checkAntiAffinity(deploymentName, deployment.Spec.Template.Spec.Affinity)
By("Verify the deployment has tolerations")
Expect(deployment.Spec.Template.Spec.Tolerations).To(ContainElement(framework.TolerationsTestValue[0]))
}
})
})
@ -804,6 +811,74 @@ var _ = Describe("ALL Operator tests", func() {
return scc.Priority
}, 2*time.Minute, 1*time.Second).Should(BeNil())
})
It("[test_id:4785] Should update infra pod number when modify the replica in CDI CR", func() {
By("Modify the replica separately")
cdi := getCDI(f)
apiserverTmpReplica := int32(2)
deploymentTmpReplica := int32(3)
uploadproxyTmpReplica := int32(4)
cdi.Spec.Infra.APIServerReplicas = &apiserverTmpReplica
cdi.Spec.Infra.DeploymentReplicas = &deploymentTmpReplica
cdi.Spec.Infra.UploadProxyReplicas = &uploadproxyTmpReplica
_, err := f.CdiClient.CdiV1beta1().CDIs().Update(context.TODO(), cdi, metav1.UpdateOptions{})
Expect(err).ToNot(HaveOccurred())
Eventually(func() bool {
for _, deploymentName := range []string{"cdi-apiserver", "cdi-deployment", "cdi-uploadproxy"} {
depl, err := f.K8sClient.AppsV1().Deployments(f.CdiInstallNs).Get(context.TODO(), deploymentName, metav1.GetOptions{})
Expect(err).ToNot(HaveOccurred())
if err != nil || *depl.Spec.Replicas == 1 {
return false
}
}
By("Replicas in deployments update complete")
return true
}, 5*time.Minute, 1*time.Second).Should(BeTrue())
By("Verify the replica of cdi-apiserver")
Eventually(func() bool {
return getPodNumByPrefix(f, "cdi-apiserver") == 2
}, 5*time.Minute, 1*time.Second).Should(BeTrue())
By("Verify the replica of cdi-deployment")
Eventually(func() bool {
return getPodNumByPrefix(f, "cdi-deployment") == 3
}, 5*time.Minute, 1*time.Second).Should(BeTrue())
By("Verify the replica of cdi-uploadproxy")
Eventually(func() bool {
return getPodNumByPrefix(f, "cdi-uploadproxy") == 4
}, 5*time.Minute, 1*time.Second).Should(BeTrue())
By("Reset replica for CDI CR")
cdi = getCDI(f)
cdi.Spec.Infra.APIServerReplicas = nil
cdi.Spec.Infra.DeploymentReplicas = nil
cdi.Spec.Infra.UploadProxyReplicas = nil
_, err = f.CdiClient.CdiV1beta1().CDIs().Update(context.TODO(), cdi, metav1.UpdateOptions{})
Expect(err).ToNot(HaveOccurred())
By("Replica should be 1 when replica dosen't set in CDI CR")
Eventually(func() bool {
for _, deploymentName := range []string{"cdi-apiserver", "cdi-deployment", "cdi-uploadproxy"} {
depl, err := f.K8sClient.AppsV1().Deployments(f.CdiInstallNs).Get(context.TODO(), deploymentName, metav1.GetOptions{})
Expect(err).ToNot(HaveOccurred())
_, err = utils.FindPodByPrefix(f.K8sClient, f.CdiInstallNs, deploymentName, common.CDIComponentLabel+"="+deploymentName)
if err != nil || *depl.Spec.Replicas != 1 {
return false
}
}
return true
}, 5*time.Minute, 1*time.Second).Should(BeTrue())
})
})
var _ = Describe("Operator cert config tests", func() {
@ -1260,8 +1335,44 @@ func checkLogForRegEx(regEx *regexp.Regexp, log string) bool {
return len(matches) >= 1
}
func checkAntiAffinity(name string, deploymentAffinity *corev1.Affinity) {
affinityTampleValue := &corev1.PodAntiAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{
{
Weight: int32(1),
PodAffinityTerm: corev1.PodAffinityTerm{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "cdi.kubevirt.io",
Operator: metav1.LabelSelectorOpIn,
Values: []string{name}},
},
},
TopologyKey: "kubernetes.io/hostname",
},
},
},
}
affCopy := framework.AffinityTestValue.DeepCopy()
affCopy.PodAntiAffinity = affinityTampleValue
Expect(reflect.DeepEqual(deploymentAffinity, affCopy)).To(BeTrue())
}
func getLog(f *framework.Framework, name string) string {
log, err := f.RunKubectlCommand("logs", "--since=0", name, "-n", f.CdiInstallNs)
Expect(err).ToNot(HaveOccurred())
return log
}
func getPodNumByPrefix(f *framework.Framework, deploymentName string) int {
labelSelector := metav1.LabelSelector{MatchLabels: map[string]string{common.CDIComponentLabel: deploymentName}}
podList, err := f.K8sClient.CoreV1().Pods(f.CdiInstallNs).List(context.TODO(), metav1.ListOptions{
LabelSelector: labels.Set(labelSelector.MatchLabels).String(),
})
Expect(err).ToNot(HaveOccurred(), "failed listing deployment pods")
return len(podList.Items)
}