mirror of
https://github.com/kubevirt/containerized-data-importer.git
synced 2025-06-03 06:30:22 +00:00
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:
parent
9a5f11e1b2
commit
a5f17e4f8a
@ -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",
|
||||
|
@ -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{
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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{
|
||||
{
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
|
@ -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{
|
||||
{
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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{}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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]))
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user