Skip to content

Commit 812763e

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 812763e

File tree

2 files changed

+154
-1
lines changed

2 files changed

+154
-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: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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+
klog.Infof("VersionController: getting deployment %s in namespace %s ...", c.controllerDeploymentName, c.commonClient.ControlPlaneNamespace)
84+
deployment, err := c.commonClient.ControlPlaneKubeClient.AppsV1().Deployments(c.commonClient.ControlPlaneNamespace).Get(context.TODO(), c.controllerDeploymentName, metav1.GetOptions{})
85+
if err != nil {
86+
return fmt.Errorf("could not get Deployment %s in Namespace %s: %w", c.controllerDeploymentName, c.commonClient.ControlPlaneNamespace, err)
87+
}
88+
89+
var desiredVersion string
90+
var actualVersion string
91+
for k, v := range deployment.Annotations {
92+
if k == "release.openshift.io/desired-version" {
93+
klog.Infof("VersionController: desiredVersion found: %s", v)
94+
desiredVersion = v
95+
} else if k == "release.openshift.io/version" {
96+
klog.Infof("VersionController: actualVersion found: %s", v)
97+
actualVersion = v
98+
}
99+
}
100+
101+
// This operator adds "release.openshift.io/desired-version" annotation with its version for sure;
102+
// if the version from Annotations is not the same as operator version, we look at some outdated
103+
// deployment generated by another (previous) operator instance.
104+
if desiredVersion != status.VersionForOperatorFromEnv() {
105+
klog.Infof("VersionController: desiredVersion mismatch: \"%s\" vs \"%s\"", desiredVersion, status.VersionForOperatorFromEnv())
106+
return nil
107+
}
108+
109+
// If versions from "release.openshift.io/version" and "release.openshift.io/desired-version"
110+
// annotations are equal, we have already populated these annotations before, do nothing now.
111+
if actualVersion == desiredVersion {
112+
klog.Infof("VersionController: version \"%s\" is already populated, nothing to do", desiredVersion)
113+
return nil
114+
}
115+
116+
if hasFinishedProgressing(deployment) {
117+
klog.Infof("VersionController: deployment update is completed")
118+
updatedDeployment := setVersionAnnotation(deployment, desiredVersion)
119+
err := c.updateDeployment(ctx, updatedDeployment)
120+
if err != nil {
121+
return err
122+
}
123+
klog.Infof("VersionController: deployment updated with version %s", desiredVersion)
124+
} else {
125+
klog.Infof("VersionController: deployment update is in progress ...")
126+
}
127+
return nil
128+
}
129+
130+
func (c *VersionController) updateDeployment(ctx context.Context, deployment *appsv1.Deployment) error {
131+
_, err := c.commonClient.ControlPlaneKubeClient.AppsV1().Deployments(c.commonClient.ControlPlaneNamespace).Update(ctx, deployment, metav1.UpdateOptions{})
132+
if err != nil {
133+
klog.Errorf("error updating deployment object %s in namespace %s: %v", deployment.Name, c.commonClient.ControlPlaneNamespace, err)
134+
return err
135+
}
136+
return nil
137+
}
138+
139+
func setVersionAnnotation(deployment *appsv1.Deployment, version string) *appsv1.Deployment {
140+
// Create a deep copy of the PersistentVolume to avoid modifying the cached object
141+
deploymentCopy := deployment.DeepCopy()
142+
143+
// Ensure the deployment has an annotations map
144+
if deploymentCopy.Annotations == nil {
145+
deploymentCopy.Annotations = make(map[string]string)
146+
}
147+
148+
// Set or update the tag hash annotation
149+
deploymentCopy.Annotations["release.openshift.io/version"] = version
150+
151+
return deploymentCopy
152+
}

0 commit comments

Comments
 (0)