Skip to content

Commit

Permalink
Event for Cluster Upgrade Announcement (#9)
Browse files Browse the repository at this point in the history
* Event for Cluster Upgrade Announcement

* Event for Cluster Upgrade Announcement

* Event for Cluster Upgrade Announcement

* Event for Cluster Upgrade Announcement

* Event for Cluster Upgrade Announcement

* apply annotation

* rbac

* utc in timer

* update cluster annotation announcement before sending event

* detecting if out of office

* update circleci

Co-authored-by: Antonia von den Driesch <[email protected]>
  • Loading branch information
njuettner and anvddriesch authored Sep 10, 2021
1 parent 84f006e commit 2be5eea
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 9 deletions.
14 changes: 14 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,17 @@ workflows:
filters:
tags:
only: /^v.*/

- architect/push-to-app-collection:
context: "architect"
name: push-upgrade-schedule-operator-to-aws-app-collection
app_name: "upgrade-schedule-operator"
app_collection_repo: "aws-app-collection"
requires:
- push-upgrade-schedule-operator-to-app-catalog
filters:
# Only do this when a new tag is created.
branches:
ignore: /.*/
tags:
only: /^v.*/
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add helm manifest.
- Add Upgrade-Schedule-Operator.


[Unreleased]: https://github.com/giantswarm/upgrade-schedule-operator/tree/master
45 changes: 43 additions & 2 deletions controllers/cluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ import (
"github.com/giantswarm/apiextensions/v3/pkg/annotation"
"github.com/giantswarm/apiextensions/v3/pkg/label"
"github.com/go-logr/logr"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/record"
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
"sigs.k8s.io/cluster-api/util/annotations"
ctrl "sigs.k8s.io/controller-runtime"
Expand All @@ -38,6 +41,8 @@ type ClusterReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme

recorder record.EventRecorder
}

//+kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -102,9 +107,34 @@ func (r *ClusterReconciler) ReconcileUpgrade(ctx context.Context, cluster *clust
return ctrl.Result{}, err
}

// Send scheduled cluster upgrade announcement.
if _, exists := cluster.Annotations[ClusterUpgradeAnnouncement]; !exists {
if upgradeAnnouncementTimeReached(upgradeTime) {
cluster.Annotations[ClusterUpgradeAnnouncement] = "true"
err = r.Client.Update(ctx, cluster)
if err != nil {
log.Error(err, "Failed to set upgrade announcement annotation.")
return ctrl.Result{}, err
}
log.Info("Sending cluster upgrade announcement event.")

msg := fmt.Sprintf("The cluster %s/%s upgrade from release version %v to %v is scheduled to start in %v.",
cluster.Namespace,
cluster.Name,
getClusterReleaseVersionLabel(cluster),
getClusterUpgradeVersionAnnotation(cluster),
upgradeTime.Sub(time.Now().UTC()).Round(time.Minute),
)
if outOfOffice(upgradeTime) {
msg += "Please contact us via [email protected] in case of annormalies."
}
r.sendClusterUpgradeEvent(cluster, msg)
}
}

// Return if the scheduled upgrade time is not reached yet.
if !upgradeTimeReached(upgradeTime) {
log.Info(fmt.Sprintf("The scheduled update time is not reached yet. Cluster will be upgraded in %v at %v.", time.Until(upgradeTime).Round(time.Minute), upgradeTime))
log.Info(fmt.Sprintf("The scheduled update time is not reached yet. Cluster will be upgraded in %v at %v.", upgradeTime.Sub(time.Now().UTC()).Round(time.Minute), upgradeTime))
return timedRequeue(upgradeTime), nil
}

Expand All @@ -130,6 +160,7 @@ func (r *ClusterReconciler) ReconcileUpgrade(ctx context.Context, cluster *clust
cluster.Labels[label.ReleaseVersion] = getClusterUpgradeVersionAnnotation(cluster)
delete(cluster.Annotations, annotation.UpdateScheduleTargetTime)
delete(cluster.Annotations, annotation.UpdateScheduleTargetRelease)
delete(cluster.Annotations, ClusterUpgradeAnnouncement)
err = r.Client.Update(ctx, cluster)
if err != nil {
log.Error(err, "Failed to update Release version tag and remove scheduled upgrade annotations.")
Expand All @@ -142,7 +173,17 @@ func (r *ClusterReconciler) ReconcileUpgrade(ctx context.Context, cluster *clust

// SetupWithManager sets up the controller with the Manager.
func (r *ClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
err := ctrl.NewControllerManagedBy(mgr).
For(&clusterv1.Cluster{}).
Complete(r)
if err != nil {
return errors.Wrap(err, "failed setting up with a controller manager")
}

r.recorder = mgr.GetEventRecorderFor("cluster-controller")
return nil
}

func (r *ClusterReconciler) sendClusterUpgradeEvent(cluster *clusterv1.Cluster, message string) {
r.recorder.Eventf(cluster, corev1.EventTypeNormal, "ClusterUpgradeAnnouncement", message)
}
22 changes: 19 additions & 3 deletions controllers/utility.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

const ClusterUpgradeAnnouncement = "alpha.giantswarm.io/update-schedule-upgrade-announcement"

func defaultRequeue() reconcile.Result {
return ctrl.Result{
Requeue: true,
Expand All @@ -19,12 +21,12 @@ func defaultRequeue() reconcile.Result {
}

func timedRequeue(upgradeTime time.Time) reconcile.Result {
if time.Until(upgradeTime) > 5*time.Minute {
if upgradeTime.Sub(time.Now().UTC()) > 5*time.Minute {
return defaultRequeue()
}
return ctrl.Result{
Requeue: true,
RequeueAfter: time.Until(upgradeTime) + time.Second,
RequeueAfter: upgradeTime.Sub(time.Now().UTC()) + time.Second,
}
}

Expand All @@ -48,5 +50,19 @@ func upgradeApplied(targetVersion semver.Version, currentVersion semver.Version)
}

func upgradeTimeReached(upgradeTime time.Time) bool {
return upgradeTime.Before(time.Now())
return upgradeTime.Before(time.Now().UTC())
}

func upgradeAnnouncementTimeReached(upgradeTime time.Time) bool {
return upgradeTime.Add(-15 * time.Minute).Before(time.Now().UTC())
}

func outOfOffice(upgradeTime time.Time) bool {
if upgradeTime.Day() == 6 || upgradeTime.Day() == 7 {
return true
}
if upgradeTime.UTC().Hour() <= 7 && upgradeTime.UTC().Hour() >= 16 {
return true
}
return false
}
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ go 1.16

require (
github.com/blang/semver v3.5.1+incompatible
github.com/giantswarm/apiextensions/v3 v3.32.1-0.20210908083826-6fdda5406dde
github.com/giantswarm/apiextensions/v3 v3.33.0
github.com/go-logr/logr v0.4.0
github.com/pkg/errors v0.9.1
k8s.io/api v0.22.1
k8s.io/apimachinery v0.22.1
k8s.io/client-go v0.22.1
sigs.k8s.io/cluster-api v0.3.22
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/giantswarm/apiextensions/v3 v3.32.1-0.20210908083826-6fdda5406dde h1:G5doh5SYz7S8xc3WNDNm0bTDIOaY9t4yLaBJWGTkVJk=
github.com/giantswarm/apiextensions/v3 v3.32.1-0.20210908083826-6fdda5406dde/go.mod h1:T0/d4PKlGUPFIMqdStzj0JVuOk51rZNQVRnZHXdYVug=
github.com/giantswarm/apiextensions/v3 v3.33.0 h1:HJb98a4pT0aA2jIPEzwhNSqTouTEm0J51Lhk4cs3ad0=
github.com/giantswarm/apiextensions/v3 v3.33.0/go.mod h1:T0/d4PKlGUPFIMqdStzj0JVuOk51rZNQVRnZHXdYVug=
github.com/giantswarm/microerror v0.3.0/go.mod h1:g8oCEMFAoEs70riRRmj9+6eiz7SqNxYl+2OfxFh1po0=
github.com/giantswarm/to v0.3.0/go.mod h1:RTRtw+Dyk6YqoiNBOGLO981BqhibtVwogdaFIMO1y/A=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
Expand Down
8 changes: 7 additions & 1 deletion helm/upgrade-schedule-operator/templates/rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,13 @@ rules:
resources:
- events
verbs:
- create
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- coordination.k8s.io
resources:
Expand Down
48 changes: 48 additions & 0 deletions util/record/recorder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Package record implements recording functionality.
package record

import (
"strings"
"sync"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/record"
)

var (
initOnce sync.Once
defaultRecorder record.EventRecorder
)

func init() {
defaultRecorder = new(record.FakeRecorder)
}

// InitFromRecorder initializes the global default recorder. It can only be called once.
// Subsequent calls are considered noops.
func InitFromRecorder(recorder record.EventRecorder) {
initOnce.Do(func() {
defaultRecorder = recorder
})
}

// Event constructs an event from the given information and puts it in the queue for sending.
func Event(object runtime.Object, reason, message string) {
defaultRecorder.Event(object, corev1.EventTypeNormal, strings.Title(reason), message)
}

// Eventf is just like Event, but with Sprintf for the message field.
func Eventf(object runtime.Object, reason, message string, args ...interface{}) {
defaultRecorder.Eventf(object, corev1.EventTypeNormal, strings.Title(reason), message, args...)
}

// Warn constructs a warning event from the given information and puts it in the queue for sending.
func Warn(object runtime.Object, reason, message string) {
defaultRecorder.Event(object, corev1.EventTypeWarning, strings.Title(reason), message)
}

// Warnf is just like Warn, but with Sprintf for the message field.
func Warnf(object runtime.Object, reason, message string, args ...interface{}) {
defaultRecorder.Eventf(object, corev1.EventTypeWarning, strings.Title(reason), message, args...)
}

0 comments on commit 2be5eea

Please sign in to comment.