Skip to content

Commit

Permalink
kubevirt-console-plugin: [release-1.12] Use two replicas in case of h…
Browse files Browse the repository at this point in the history
…ighly available cluster (#3035)

In case the cluster has an HighlyAvailable infrastructure, the kubevirt-console-plugin and kubevirt-console-proxy should be highly available as well,
in order to avoid losing the Virtualization plugin when the hosting node is down. By running these pods with two replicas and PodAntiAffinity rule, we
can ensure the plugin will remain available when a node is down.

Signed-off-by: Oren Cohen <ocohen@redhat.com>
  • Loading branch information
orenc1 committed Aug 13, 2024
1 parent 3d74306 commit 2fe5af2
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 3 deletions.
40 changes: 37 additions & 3 deletions controllers/operands/kubevirtConsolePlugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ func NewKvUIProxyDeployment(hc *hcov1beta1.HyperConverged) *appsv1.Deployment {
func getKvUIDeployment(hc *hcov1beta1.HyperConverged, deploymentName string, image string,
servingCertName string, servingCertPath string, port int32, componentName hcoutil.AppComponent) *appsv1.Deployment {
labels := getLabels(hc, componentName)
infrastructureHighlyAvailable := hcoutil.GetClusterInfo().IsInfrastructureHighlyAvailable()
var replicas int32
if infrastructureHighlyAvailable {
replicas = int32(2)
} else {
replicas = int32(1)
}

affinity := getPodAntiAffinity(labels[hcoutil.AppLabelComponent], infrastructureHighlyAvailable)

deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -113,7 +122,7 @@ func getKvUIDeployment(hc *hcov1beta1.HyperConverged, deploymentName string, ima
Namespace: hc.Namespace,
},
Spec: appsv1.DeploymentSpec{
Replicas: ptr.To(int32(1)),
Replicas: ptr.To(replicas),
Selector: &metav1.LabelSelector{
MatchLabels: labels,
},
Expand Down Expand Up @@ -181,7 +190,7 @@ func getKvUIDeployment(hc *hcov1beta1.HyperConverged, deploymentName string, ima
if hc.Spec.Infra.NodePlacement.Affinity != nil {
deployment.Spec.Template.Spec.Affinity = hc.Spec.Infra.NodePlacement.Affinity.DeepCopy()
} else {
deployment.Spec.Template.Spec.Affinity = nil
deployment.Spec.Template.Spec.Affinity = affinity
}

if hc.Spec.Infra.NodePlacement.Tolerations != nil {
Expand All @@ -192,12 +201,37 @@ func getKvUIDeployment(hc *hcov1beta1.HyperConverged, deploymentName string, ima
}
} else {
deployment.Spec.Template.Spec.NodeSelector = nil
deployment.Spec.Template.Spec.Affinity = nil
deployment.Spec.Template.Spec.Affinity = affinity
deployment.Spec.Template.Spec.Tolerations = nil
}
return deployment
}

func getPodAntiAffinity(componentLabel string, infrastructureHighlyAvailable bool) *corev1.Affinity {
if infrastructureHighlyAvailable {
return &corev1.Affinity{
PodAntiAffinity: &corev1.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: hcoutil.AppLabelComponent,
Operator: metav1.LabelSelectorOpIn,
Values: []string{componentLabel},
},
},
},
TopologyKey: corev1.LabelHostname,
},
},
},
}
}

return nil
}

func NewKvUIPluginSvc(hc *hcov1beta1.HyperConverged) *corev1.Service {
servicePorts := []corev1.ServicePort{
{
Expand Down
109 changes: 109 additions & 0 deletions controllers/operands/kubevirtConsolePlugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,94 @@ var _ = Describe("Kubevirt Console Plugin", func() {
Entry("plugin deployment", hcoutil.AppComponentUIPlugin, NewKvUIPluginDeployment, newKvUIPluginDeploymentHandler),
Entry("proxy deployment", hcoutil.AppComponentUIProxy, NewKvUIProxyDeployment, newKvUIProxyDeploymentHandler),
)

DescribeTable("apply PodAntiAffinity and two replicas if HighlyAvailable", func(ctx context.Context, appComponent hcoutil.AppComponent,
deploymentManifestor func(converged *hcov1beta1.HyperConverged) *appsv1.Deployment, handlerFunc GetHandler) {

originalGetClusterInfo := hcoutil.GetClusterInfo
hcoutil.GetClusterInfo = func() hcoutil.ClusterInfo {
return &commontestutils.ClusterInfoMock{}
}

defer func() {
hcoutil.GetClusterInfo = originalGetClusterInfo
}()

existingResource := deploymentManifestor(hco)

hco.Spec.Infra.NodePlacement = nil
existingResource.Spec.Template.Spec.Affinity = nil
existingResource.Spec.Replicas = ptr.To(int32(1))

cl := commontestutils.InitClient([]client.Object{hco, existingResource})
handlers, err := handlerFunc(logger, cl, commontestutils.GetScheme(), hco)

Expect(err).ToNot(HaveOccurred())
res := handlers[0].ensure(req)
Expect(res.Created).To(BeFalse())
Expect(res.Updated).To(BeTrue())
Expect(res.Overwritten).To(BeFalse())
Expect(res.UpgradeDone).To(BeFalse())
Expect(res.Err).ToNot(HaveOccurred())

foundResource := &appsv1.Deployment{}
Expect(
cl.Get(ctx,
types.NamespacedName{Name: existingResource.Name, Namespace: existingResource.Namespace},
foundResource),
).To(Succeed())

Expect(existingResource.Spec.Template.Spec.Affinity).To(BeNil())
Expect(*existingResource.Spec.Replicas).To(Equal(int32(1)))

expectedAffinity := expectedPodAntiAffinity(appComponent)
Expect(foundResource.Spec.Template.Spec.Affinity).To(BeEquivalentTo(expectedAffinity))
Expect(*foundResource.Spec.Replicas).To(Equal(int32(2)))
},
Entry("plugin deployment", hcoutil.AppComponentUIPlugin, NewKvUIPluginDeployment, newKvUIPluginDeploymentHandler),
Entry("proxy deployment", hcoutil.AppComponentUIProxy, NewKvUIProxyDeployment, newKvUIProxyDeploymentHandler),
)

DescribeTable("use one replica on SNO", func(ctx context.Context, appComponent hcoutil.AppComponent,
deploymentManifestor func(converged *hcov1beta1.HyperConverged) *appsv1.Deployment, handlerFunc GetHandler) {

originalGetClusterInfo := hcoutil.GetClusterInfo
hcoutil.GetClusterInfo = func() hcoutil.ClusterInfo {
return &commontestutils.ClusterInfoSNOMock{}
}

defer func() {
hcoutil.GetClusterInfo = originalGetClusterInfo
}()

existingResource := deploymentManifestor(hco)
existingResource.Spec.Replicas = ptr.To(int32(3))

cl := commontestutils.InitClient([]client.Object{hco, existingResource})
handlers, err := handlerFunc(logger, cl, commontestutils.GetScheme(), hco)

Expect(err).ToNot(HaveOccurred())
res := handlers[0].ensure(req)
Expect(res.Created).To(BeFalse())
Expect(res.Updated).To(BeTrue())
Expect(res.Overwritten).To(BeFalse())
Expect(res.UpgradeDone).To(BeFalse())
Expect(res.Err).ToNot(HaveOccurred())

foundResource := &appsv1.Deployment{}
Expect(
cl.Get(ctx,
types.NamespacedName{Name: existingResource.Name, Namespace: existingResource.Namespace},
foundResource),
).To(Succeed())

Expect(existingResource.Spec.Template.Spec.Affinity).To(BeNil())
Expect(foundResource.Spec.Template.Spec.Affinity).To(BeNil())
Expect(*foundResource.Spec.Replicas).To(Equal(int32(1)))
},
Entry("plugin deployment", hcoutil.AppComponentUIPlugin, NewKvUIPluginDeployment, newKvUIPluginDeploymentHandler),
Entry("proxy deployment", hcoutil.AppComponentUIProxy, NewKvUIProxyDeployment, newKvUIProxyDeploymentHandler),
)
})
})

Expand Down Expand Up @@ -797,3 +885,24 @@ var _ = Describe("Kubevirt Console Plugin", func() {
})

})

func expectedPodAntiAffinity(appComponent hcoutil.AppComponent) *v1.Affinity {
return &v1.Affinity{
PodAntiAffinity: &v1.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: hcoutil.AppLabelComponent,
Operator: metav1.LabelSelectorOpIn,
Values: []string{string(appComponent)},
},
},
},
TopologyKey: v1.LabelHostname,
},
},
},
}
}
44 changes: 44 additions & 0 deletions tests/func-tests/console_plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,50 @@ var _ = Describe("kubevirt console plugin", Label(tests.OpenshiftLabel), func()
WithPolling(100 * time.Millisecond).
Should(Succeed())
})

It("console-plugin and apiserver-proxy Deployments should have 2 replicas in Highly Available clusters", Label(tests.HighlyAvailableClusterLabel), func(ctx context.Context) {
Eventually(func(g Gomega, ctx context.Context) {
consoleUIDeployment, err := cli.AppsV1().Deployments(flags.KubeVirtInstallNamespace).Get(ctx, string(hcoutil.AppComponentUIPlugin), metav1.GetOptions{})
g.Expect(err).ToNot(HaveOccurred())

g.Expect(consoleUIDeployment.Spec.Replicas).To(HaveValue(Equal(int32(2))))
}).WithTimeout(1 * time.Minute).
WithPolling(100 * time.Millisecond).
WithContext(ctx).
Should(Succeed())

Eventually(func(g Gomega, ctx context.Context) {
proxyUIDeployment, err := cli.AppsV1().Deployments(flags.KubeVirtInstallNamespace).Get(ctx, string(hcoutil.AppComponentUIProxy), metav1.GetOptions{})
g.Expect(err).ToNot(HaveOccurred())

g.Expect(proxyUIDeployment.Spec.Replicas).To(HaveValue(Equal(int32(2))))
}).WithTimeout(1 * time.Minute).
WithPolling(100 * time.Millisecond).
WithContext(ctx).
Should(Succeed())
})

It("console-plugin and apiserver-proxy Deployments should have 1 replica in single node clusters", Label(tests.SingleNodeLabel), func(ctx context.Context) {
Eventually(func(g Gomega, ctx context.Context) {
consoleUIDeployment, err := cli.AppsV1().Deployments(flags.KubeVirtInstallNamespace).Get(ctx, string(hcoutil.AppComponentUIPlugin), metav1.GetOptions{})
g.Expect(err).ToNot(HaveOccurred())

g.Expect(consoleUIDeployment.Spec.Replicas).To(HaveValue(Equal(int32(1))))
}).WithTimeout(1 * time.Minute).
WithPolling(100 * time.Millisecond).
WithContext(ctx).
Should(Succeed())

Eventually(func(g Gomega, ctx context.Context) {
proxyUIDeployment, err := cli.AppsV1().Deployments(flags.KubeVirtInstallNamespace).Get(ctx, string(hcoutil.AppComponentUIProxy), metav1.GetOptions{})
g.Expect(err).ToNot(HaveOccurred())

g.Expect(proxyUIDeployment.Spec.Replicas).To(HaveValue(Equal(int32(1))))
}).WithTimeout(1 * time.Minute).
WithPolling(100 * time.Millisecond).
WithContext(ctx).
Should(Succeed())
})
})

func executeCommandOnPod(ctx context.Context, cli kubecli.KubevirtClient, pod *v1.Pod, command string) (string, string, error) {
Expand Down

0 comments on commit 2fe5af2

Please sign in to comment.