Files
monok8s/clitools/pkg/controller/osupgrade/progress.go

266 lines
6.8 KiB
Go

package osupgrade
import (
"context"
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog/v2"
monov1alpha1 "example.com/monok8s/pkg/apis/monok8s/v1alpha1"
"example.com/monok8s/pkg/buildinfo"
"example.com/monok8s/pkg/kube"
)
var (
unstructuredConverter = runtime.DefaultUnstructuredConverter
osup_gvr = schema.GroupVersionResource{
Group: monov1alpha1.Group,
Version: monov1alpha1.Version,
Resource: "osupgradeprogresses",
}
)
func ensureProgressHeartbeat(
ctx context.Context,
clients *kube.Clients,
namespace string,
nodeName string,
osu *monov1alpha1.OSUpgrade,
) (*monov1alpha1.OSUpgradeProgress, error) {
name := fmt.Sprintf("%s-%s", osu.Name, nodeName)
now := metav1.Now()
currentVersion := buildinfo.KubeVersion
targetVersion := ""
if osu.Status != nil {
targetVersion = osu.Status.ResolvedVersion
}
progress := &monov1alpha1.OSUpgradeProgress{
TypeMeta: metav1.TypeMeta{
APIVersion: monov1alpha1.APIVersion,
Kind: "OSUpgradeProgress",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: monov1alpha1.OSUpgradeProgressSpec{
NodeName: nodeName,
SourceRef: monov1alpha1.OSUpgradeSourceRef{
Name: osu.Name,
},
},
Status: &monov1alpha1.OSUpgradeProgressStatus{
CurrentVersion: currentVersion,
TargetVersion: targetVersion,
Phase: monov1alpha1.OSUpgradeProgressPhasePending,
LastUpdatedAt: &now,
Message: "acknowledged",
},
}
created, err := createProgress(ctx, clients, osup_gvr, progress)
if err == nil {
klog.InfoS("created osupgradeprogress", "name", created.Name, "namespace", created.Namespace)
return created, nil
}
if !apierrors.IsAlreadyExists(err) {
return nil, fmt.Errorf("create OSUpgradeProgress %s/%s: %w", namespace, name, err)
}
existing, err := getProgress(ctx, clients, osup_gvr, namespace, name)
if err != nil {
return nil, fmt.Errorf("get existing OSUpgradeProgress %s/%s: %w", namespace, name, err)
}
// Spec should remain aligned with the source and node.
existing.Spec.NodeName = nodeName
existing.Spec.SourceRef.Name = osu.Name
if existing, err = updateProgressSpec(ctx, clients, osup_gvr, existing); err != nil {
return nil, fmt.Errorf("update OSUpgradeProgress spec %s/%s: %w", namespace, name, err)
}
if existing.Status == nil {
existing.Status = &monov1alpha1.OSUpgradeProgressStatus{}
}
existing.Status.CurrentVersion = currentVersion
existing.Status.TargetVersion = targetVersion
existing.Status.LastUpdatedAt = &now
// Only set phase/message if they are still empty, so later real state machine
// updates are not clobbered by the heartbeat.
if existing.Status.Phase == "" {
existing.Status.Phase = monov1alpha1.OSUpgradeProgressPhasePending
}
if existing, err = updateProgressStatus(ctx, clients, osup_gvr, existing); err != nil {
return nil, fmt.Errorf("update OSUpgradeProgress status %s/%s: %w", namespace, name, err)
}
klog.InfoS("updated osupgradeprogress", "name", existing.Name, "namespace", existing.Namespace)
return existing, nil
}
func createProgress(
ctx context.Context,
clients *kube.Clients,
gvr schema.GroupVersionResource,
progress *monov1alpha1.OSUpgradeProgress,
) (*monov1alpha1.OSUpgradeProgress, error) {
obj, err := toUnstructured(progress)
if err != nil {
return nil, err
}
created, err := clients.Dynamic.
Resource(gvr).
Namespace(progress.Namespace).
Create(ctx, obj, metav1.CreateOptions{})
if err != nil {
return nil, err
}
return fromUnstructuredProgress(created)
}
func getProgress(
ctx context.Context,
clients *kube.Clients,
gvr schema.GroupVersionResource,
namespace, name string,
) (*monov1alpha1.OSUpgradeProgress, error) {
got, err := clients.Dynamic.
Resource(gvr).
Namespace(namespace).
Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, err
}
return fromUnstructuredProgress(got)
}
func updateProgressSpec(
ctx context.Context,
clients *kube.Clients,
gvr schema.GroupVersionResource,
progress *monov1alpha1.OSUpgradeProgress,
) (*monov1alpha1.OSUpgradeProgress, error) {
obj, err := toUnstructured(progress)
if err != nil {
return nil, err
}
updated, err := clients.Dynamic.
Resource(gvr).
Namespace(progress.Namespace).
Update(ctx, obj, metav1.UpdateOptions{})
if err != nil {
return nil, err
}
return fromUnstructuredProgress(updated)
}
func updateProgressStatus(
ctx context.Context,
clients *kube.Clients,
gvr schema.GroupVersionResource,
progress *monov1alpha1.OSUpgradeProgress,
) (*monov1alpha1.OSUpgradeProgress, error) {
obj, err := toUnstructured(progress)
if err != nil {
return nil, err
}
updated, err := clients.Dynamic.
Resource(gvr).
Namespace(progress.Namespace).
UpdateStatus(ctx, obj, metav1.UpdateOptions{})
if err != nil {
return nil, err
}
return fromUnstructuredProgress(updated)
}
func failProgress(
ctx context.Context,
clients *kube.Clients,
osup *monov1alpha1.OSUpgradeProgress,
action string,
cause error,
) error {
now := metav1.Now()
if osup.Status == nil {
osup.Status = &monov1alpha1.OSUpgradeProgressStatus{}
}
osup.Status.LastUpdatedAt = &now
osup.Status.Message = fmt.Sprintf("%s: %v", action, cause)
osup.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseFailed
if _, err := updateProgressStatus(ctx, clients, osup_gvr, osup); err != nil {
klog.ErrorS(err, "failed to update osupgradeprogress status after error",
"action", action,
"name", osup.Name,
"namespace", osup.Namespace,
)
}
return fmt.Errorf("%s: %w", action, cause)
}
func markProgressCompleted(
ctx context.Context,
clients *kube.Clients,
osup *monov1alpha1.OSUpgradeProgress,
message string,
) error {
now := metav1.Now()
if osup.Status == nil {
osup.Status = &monov1alpha1.OSUpgradeProgressStatus{}
}
osup.Status.Phase = monov1alpha1.OSUpgradeProgressPhaseCompleted
osup.Status.Message = message
osup.Status.LastUpdatedAt = &now
osup.Status.CompletedAt = &now
_, err := updateProgressStatus(ctx, clients, osup_gvr, osup)
if err != nil {
return fmt.Errorf("mark progress completed: %w", err)
}
return nil
}
func toUnstructured(progress *monov1alpha1.OSUpgradeProgress) (*unstructured.Unstructured, error) {
m, err := unstructuredConverter.ToUnstructured(progress)
if err != nil {
return nil, fmt.Errorf("convert OSUpgradeProgress to unstructured: %w", err)
}
return &unstructured.Unstructured{Object: m}, nil
}
func fromUnstructuredProgress(obj *unstructured.Unstructured) (*monov1alpha1.OSUpgradeProgress, error) {
var progress monov1alpha1.OSUpgradeProgress
if err := unstructuredConverter.FromUnstructured(obj.Object, &progress); err != nil {
return nil, fmt.Errorf("convert unstructured to OSUpgradeProgress: %w", err)
}
return &progress, nil
}