Skip to content

Commit 1289852

Browse files
committed
hypershift: Add version annotation to controller Deployments
New annotation `release.openshift.io/version` signals that rollout of Deployment is complete (see https://issues.redhat.com/browse/STOR-2523 for details). This commit implements new controller (VersionController) which reconciles version annotation if controller deplyment is done.
1 parent 307ef19 commit 1289852

File tree

2 files changed

+153
-1
lines changed

2 files changed

+153
-1
lines changed

pkg/driver/aws-ebs/aws_ebs.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ func GetAWSEBSOperatorControllerConfig(ctx context.Context, flavour generator.Cl
204204

205205
if flavour == generator.FlavourHyperShift {
206206
volumeTagController := NewEBSVolumeTagsController(cfg.GetControllerName("EBSVolumeTagsController"), c, c.EventRecorder)
207-
cfg.ExtraControlPlaneControllers = append(cfg.ExtraControlPlaneControllers, volumeTagController)
207+
versionController := operator.NewVersionController(cfg.GetControllerName("EBSVesrsionController"), "aws-ebs-csi-driver-controller", c, c.EventRecorder)
208+
cfg.ExtraControlPlaneControllers = append(cfg.ExtraControlPlaneControllers, volumeTagController, versionController)
208209
cfg.DeploymentInformers = append(cfg.DeploymentInformers, c.KubeInformers.InformersFor("").Core().V1().PersistentVolumes().Informer())
209210
cfg.DeploymentInformers = append(cfg.DeploymentInformers, c.KubeInformers.InformersFor(awsEBSSecretNamespace).Core().V1().Secrets().Informer())
210211
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package operator
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"k8s.io/klog/v2"
9+
10+
operatorapi "github.com/openshift/api/operator/v1"
11+
"github.com/openshift/csi-operator/pkg/clients"
12+
"github.com/openshift/library-go/pkg/controller/factory"
13+
"github.com/openshift/library-go/pkg/operator/events"
14+
"github.com/openshift/library-go/pkg/operator/status"
15+
appsv1 "k8s.io/api/apps/v1"
16+
corev1 "k8s.io/api/core/v1"
17+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18+
)
19+
20+
const (
21+
defaultReSyncPeriod = 10 * time.Minute
22+
)
23+
24+
type VersionController struct {
25+
name string
26+
controllerDeploymentName string
27+
commonClient *clients.Clients
28+
eventRecorder events.Recorder
29+
}
30+
31+
func NewVersionController(
32+
name string,
33+
controllerDeploymentName string,
34+
commonClient *clients.Clients,
35+
eventRecorder events.Recorder) factory.Controller {
36+
37+
c := &VersionController{
38+
name: name,
39+
controllerDeploymentName: controllerDeploymentName,
40+
commonClient: commonClient,
41+
eventRecorder: eventRecorder,
42+
}
43+
return factory.New().WithSync(
44+
c.Sync,
45+
).WithInformers(
46+
c.commonClient.ControlPlaneKubeInformers.InformersFor(c.commonClient.ControlPlaneNamespace).Apps().V1().Deployments().Informer(),
47+
).ResyncEvery(
48+
defaultReSyncPeriod,
49+
).ToController(
50+
name,
51+
eventRecorder,
52+
)
53+
}
54+
55+
func hasFinishedProgressing(deployment *appsv1.Deployment) bool {
56+
if deployment.Status.ObservedGeneration != deployment.Generation {
57+
// The Deployment controller did not act on the Deployment spec change yet.
58+
// Any condition in the status may be stale.
59+
return false
60+
}
61+
// Deployment whose rollout is complete gets Progressing condition with Reason NewReplicaSetAvailable condition.
62+
// https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#complete-deployment
63+
for _, cond := range deployment.Status.Conditions {
64+
if cond.Type == appsv1.DeploymentProgressing {
65+
return cond.Status == corev1.ConditionTrue && cond.Reason == "NewReplicaSetAvailable"
66+
}
67+
}
68+
return false
69+
}
70+
71+
func (c *VersionController) Sync(ctx context.Context, syncCtx factory.SyncContext) error {
72+
klog.Infof("VersionController sync started")
73+
defer klog.Infof("VersionController sync finished")
74+
75+
opSpec, _, _, err := c.commonClient.OperatorClient.GetOperatorState()
76+
if err != nil {
77+
return err
78+
}
79+
if opSpec.ManagementState != operatorapi.Managed {
80+
return nil
81+
}
82+
83+
deployment, err := c.commonClient.ControlPlaneKubeInformers.InformersFor(c.commonClient.ControlPlaneNamespace).Apps().V1().Deployments().Lister().Deployments(c.commonClient.ControlPlaneNamespace).Get(c.controllerDeploymentName)
84+
if err != nil {
85+
return fmt.Errorf("could not get Deployment %s in Namespace %s: %w", c.controllerDeploymentName, c.commonClient.ControlPlaneNamespace, err)
86+
}
87+
88+
var desiredVersion string
89+
var actualVersion string
90+
for k, v := range deployment.Annotations {
91+
if k == "release.openshift.io/desired-version" {
92+
klog.Infof("VersionController: desiredVersion found: %s", v)
93+
desiredVersion = v
94+
} else if k == "release.openshift.io/version" {
95+
klog.Infof("VersionController: actualVersion found: %s", v)
96+
actualVersion = v
97+
}
98+
}
99+
100+
// This operator adds "release.openshift.io/desired-version" annotation with its version for sure;
101+
// if the version from Annotations is not the same as operator version, we look at some outdated
102+
// deployment generated by another (previous) operator instance.
103+
if desiredVersion != status.VersionForOperatorFromEnv() {
104+
klog.Infof("VersionController: desiredVersion mismatch: \"%s\" vs \"%s\"", desiredVersion, status.VersionForOperatorFromEnv())
105+
return nil
106+
}
107+
108+
// If versions from "release.openshift.io/version" and "release.openshift.io/desired-version"
109+
// annotations are equal, we have already populated these annotations before, do nothing now.
110+
if actualVersion == desiredVersion {
111+
klog.Infof("VersionController: version \"%s\" is already populated, nothing to do", desiredVersion)
112+
return nil
113+
}
114+
115+
if hasFinishedProgressing(deployment) {
116+
klog.Infof("VersionController: deployment update is completed")
117+
updatedDeployment := setVersionAnnotation(deployment, desiredVersion)
118+
err := c.updateDeployment(ctx, updatedDeployment)
119+
if err != nil {
120+
return err
121+
}
122+
klog.Infof("VersionController: deployment updated with version %s", desiredVersion)
123+
} else {
124+
klog.Infof("VersionController: deployment update is in progress ...")
125+
}
126+
return nil
127+
}
128+
129+
func (c *VersionController) updateDeployment(ctx context.Context, deployment *appsv1.Deployment) error {
130+
_, err := c.commonClient.ControlPlaneKubeClient.AppsV1().Deployments(c.commonClient.ControlPlaneNamespace).Update(ctx, deployment, metav1.UpdateOptions{})
131+
if err != nil {
132+
klog.Errorf("error updating deployment object %s in namespace %s: %v", deployment.Name, c.commonClient.ControlPlaneNamespace, err)
133+
return err
134+
}
135+
return nil
136+
}
137+
138+
func setVersionAnnotation(deployment *appsv1.Deployment, version string) *appsv1.Deployment {
139+
// Create a deep copy of the PersistentVolume to avoid modifying the cached object
140+
deploymentCopy := deployment.DeepCopy()
141+
142+
// Ensure the deployment has an annotations map
143+
if deploymentCopy.Annotations == nil {
144+
deploymentCopy.Annotations = make(map[string]string)
145+
}
146+
147+
// Set or update the tag hash annotation
148+
deploymentCopy.Annotations["release.openshift.io/version"] = version
149+
150+
return deploymentCopy
151+
}

0 commit comments

Comments
 (0)