Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions api/v1alpha3/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions api/v1alpha4/condition_consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,34 @@ const (
// from making any further remediations.
TooManyUnhealthyReason = "TooManyUnhealthy"
)

// These are valid conditions of a MachineDeployment.
const (
// MachineDeploymentReady reports an aggregate of current status of the machines controlled by the MachineDeployment.
MachineDeploymentReadyCondition ConditionType = "MachineDeploymentReady"
// Available means the MachineDeployment is available, ie. at least the minimum available
// machines required are up and running for at least minReadySeconds.
MachineDeploymentAvailable ConditionType = "MachineDeploymentAvailable"
// Progressing means the MachineDeployment is progressing. Progress for a MachineDeployment is
// considered when a new machine set is created or adopted, and when new machine scale
// up or old machine scale down. Progress is not estimated for paused deployments or
// when progressDeadlineSeconds is not specified.
MachineDeploymentProgressing ConditionType = "MachineDeploymentProgressing"

// Reasons for deployment conditions
//
// Progressing:
// NewMSAvailableReason is added in a deployment when its newest machine set is made available
NewMSAvailableReason = "NewMachineSetAvailable"
// TimedOutReason is added in a deployment when its newest machine set fails to show any progress
// within the given deadline (progressDeadlineSeconds).
TimedOutReason = "ProgressDeadlineExceeded"
// MachineSetUpdatedReason is added in a deployment when one of its machine sets is updated as part
// of the rollout process.
MachineSetUpdatedReason = "MachineSetUpdated"
//
// Available:

// MinimumMachinesAvailable is added in a deployment when it has its minimum machines required available.
MinimumMachinesAvailable = "MinimumMachinesAvailable"
)
12 changes: 12 additions & 0 deletions api/v1alpha4/machinedeployment_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ type MachineDeploymentStatus struct {
// Phase represents the current phase of a MachineDeployment (ScalingUp, ScalingDown, Running, Failed, or Unknown).
// +optional
Phase string `json:"phase,omitempty"`

// Conditions defines current service state of the MachineDeployment.
// +optional
Conditions Conditions `json:"conditions,omitempty"`
}

// ANCHOR_END: MachineDeploymentStatus
Expand Down Expand Up @@ -280,3 +284,11 @@ type MachineDeploymentList struct {
func init() {
SchemeBuilder.Register(&MachineDeployment{}, &MachineDeploymentList{})
}

func (in *MachineDeployment) GetConditions() Conditions {
return in.Status.Conditions
}

func (in *MachineDeployment) SetConditions(conditions Conditions) {
in.Status.Conditions = conditions
}
9 changes: 8 additions & 1 deletion api/v1alpha4/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 29 additions & 0 deletions config/crd/bases/cluster.x-k8s.io_machinedeployments.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,35 @@ spec:
description: Total number of available machines (ready for at least minReadySeconds) targeted by this deployment.
format: int32
type: integer
conditions:
description: Conditions defines current service state of the MachineDeployment.
items:
description: Condition defines an observation of a Cluster API resource operational state.
properties:
lastTransitionTime:
description: Last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: A human readable message indicating details about the transition. This field may be empty.
type: string
reason:
description: The reason for the condition's last transition in CamelCase. The specific API may choose whether or not this field is considered a guaranteed API. This field may not be empty.
type: string
severity:
description: Severity provides an explicit classification of Reason code, so the users or machines can immediately understand the current situation and act accordingly. The Severity field MUST be set only when Status=False.
type: string
status:
description: Status of the condition, one of True, False, Unknown.
type: string
type:
description: Type of condition in CamelCase or in foo.example.com/CamelCase. Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important.
type: string
required:
- status
- type
type: object
type: array
observedGeneration:
description: The generation observed by the deployment controller.
format: int64
Expand Down
18 changes: 18 additions & 0 deletions controllers/machinedeployment_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,24 @@ var _ = Describe("MachineDeployment Reconciler", func() {
return len(machineSets.Items)
}, timeout*5).Should(BeEquivalentTo(0))

By("Verifying MachineDeployment has correct Conditions")
Eventually(func() bool {
key := client.ObjectKey{Name: deployment.Name, Namespace: deployment.Namespace}
if err := testEnv.Get(ctx, key, deployment); err != nil {
return false
}
var availableCond, progressingCond bool
for _, c := range deployment.Status.Conditions {
if c.Type == clusterv1.MachineDeploymentAvailable && c.Status == corev1.ConditionTrue && c.Reason == clusterv1.MinimumMachinesAvailable {
availableCond = true
}
if c.Type == clusterv1.MachineDeploymentProgressing && c.Status == corev1.ConditionTrue && c.Reason == clusterv1.NewMSAvailableReason {
progressingCond = true
}
}
return availableCond && progressingCond
}, timeout).Should(BeTrue())

// Validate that the controller set the cluster name label in selector.
Expect(deployment.Status.Selector).To(ContainSubstring(testCluster.Name))
})
Expand Down
4 changes: 2 additions & 2 deletions controllers/machinedeployment_rolling.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (r *MachineDeploymentReconciler) rolloutRolling(ctx context.Context, d *clu
return err
}

if err := r.syncDeploymentStatus(allMSs, newMS, d); err != nil {
if err := r.syncDeploymentStatus(ctx, allMSs, newMS, d); err != nil {
return err
}

Expand All @@ -57,7 +57,7 @@ func (r *MachineDeploymentReconciler) rolloutRolling(ctx context.Context, d *clu
return err
}

if err := r.syncDeploymentStatus(allMSs, newMS, d); err != nil {
if err := r.syncDeploymentStatus(ctx, allMSs, newMS, d); err != nil {
return err
}

Expand Down
128 changes: 118 additions & 10 deletions controllers/machinedeployment_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha4"
"sigs.k8s.io/cluster-api/controllers/mdutil"
"sigs.k8s.io/cluster-api/util"
"sigs.k8s.io/cluster-api/util/conditions"
"sigs.k8s.io/cluster-api/util/patch"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand All @@ -55,7 +56,7 @@ func (r *MachineDeploymentReconciler) sync(ctx context.Context, d *clusterv1.Mac
// // TODO: Clean up the deployment when it's paused and no rollback is in flight.
//
allMSs := append(oldMSs, newMS)
return r.syncDeploymentStatus(allMSs, newMS, d)
return r.syncDeploymentStatus(ctx, allMSs, newMS, d)
}

// getAllMachineSetsAndSyncRevision returns all the machine sets for the provided deployment (new and all old), with new MS's and deployment's revision updated.
Expand Down Expand Up @@ -351,8 +352,85 @@ func (r *MachineDeploymentReconciler) scale(ctx context.Context, deployment *clu
}

// syncDeploymentStatus checks if the status is up-to-date and sync it if necessary
func (r *MachineDeploymentReconciler) syncDeploymentStatus(allMSs []*clusterv1.MachineSet, newMS *clusterv1.MachineSet, d *clusterv1.MachineDeployment) error {
d.Status = calculateStatus(allMSs, newMS, d)
func (r *MachineDeploymentReconciler) syncDeploymentStatus(ctx context.Context, allMSs []*clusterv1.MachineSet, newMS *clusterv1.MachineSet, d *clusterv1.MachineDeployment) error {

log := ctrl.LoggerFrom(ctx)
// If there is no progressDeadlineSeconds set, remove any Progressing condition.
if !mdutil.HasProgressDeadline(d) {
conditions.Delete(d, clusterv1.MachineDeploymentProgressing)
}

newStatus := calculateStatus(allMSs, newMS, d)

currentCond := conditions.Get(d, clusterv1.MachineDeploymentProgressing)
isCompleteDeployment := newStatus.Replicas == newStatus.UpdatedReplicas && currentCond != nil && currentCond.Reason == clusterv1.NewMSAvailableReason
var cond *clusterv1.Condition
if mdutil.HasProgressDeadline(d) && !isCompleteDeployment {
switch {
case mdutil.DeploymentComplete(d, &newStatus):
log.V(4).Info("MachineDeployment successfully progressed", "machinedeployment", d.Name)
// Update the deployment conditions with a message for the new machine set that
// was successfully deployed. If the condition already exists, we ignore this update.
msg := fmt.Sprintf("MachineDeployment %q has successfully progressed.", d.Name)
if newMS != nil {
msg = fmt.Sprintf("MachineSet %q has successfully progressed.", newMS.Name)
}
cond = &clusterv1.Condition{
Type: clusterv1.MachineDeploymentProgressing,
Status: corev1.ConditionTrue,
Reason: clusterv1.NewMSAvailableReason,
Severity: clusterv1.ConditionSeverityInfo,
Message: msg,
}
case mdutil.DeploymentProgressing(d, &newStatus):
log.V(4).Info("MachineDeployment is progressing", "machinedeployment", d.Name)
// If there is any progress made, continue by not checking if the deployment failed. This
// behavior emulates the rolling updater progressDeadline check.
msg := fmt.Sprintf("MachineDeployment %q is progressing.", d.Name)
if newMS != nil {
msg = fmt.Sprintf("MachineSet %q is progressing.", newMS.Name)
}
cond = &clusterv1.Condition{
Type: clusterv1.MachineDeploymentProgressing,
Status: corev1.ConditionTrue,
Reason: clusterv1.MachineSetUpdatedReason,
Severity: clusterv1.ConditionSeverityInfo,
Message: msg,
}
case mdutil.DeploymentTimedOut(d, &newStatus):
log.V(4).Info("MachineDeployment has timed out progressing", "machinedeployment", d.Name)
// Update the deployment with a timeout condition. If the condition already exists,
// we ignore this update.
msg := fmt.Sprintf("MachineDeployment %q has timed out progressing.", d.Name)
if newMS != nil {
msg = fmt.Sprintf("MachineSet %q has timed out progressing.", newMS.Name)
}
cond = &clusterv1.Condition{
Type: clusterv1.MachineDeploymentProgressing,
Status: corev1.ConditionFalse,
Reason: clusterv1.TimedOutReason,
Severity: clusterv1.ConditionSeverityError,
Message: msg,
}
}
}
d.Status = newStatus
if cond != nil {
conditions.Set(d, cond)
}
if failed, reason, msg := getMachineSetFailures(allMSs); failed {
conditions.Set(d, &clusterv1.Condition{
Type: clusterv1.MachineDeploymentReadyCondition,
Status: corev1.ConditionFalse,
Reason: reason,
Message: msg,
})
} else {
conditions.Set(d, &clusterv1.Condition{
Type: clusterv1.MachineDeploymentReadyCondition,
Status: corev1.ConditionTrue,
})
}
return nil
}

Expand Down Expand Up @@ -382,6 +460,20 @@ func calculateStatus(allMSs []*clusterv1.MachineSet, newMS *clusterv1.MachineSet
UnavailableReplicas: unavailableReplicas,
}

// Copy conditions one by one so we won't mutate the original object.
existingConditions := deployment.Status.Conditions
for i := range existingConditions {
status.Conditions = append(status.Conditions, existingConditions[i])
}
if availableReplicas >= *(deployment.Spec.Replicas)-mdutil.MaxUnavailable(*deployment) {
status.Conditions = append(status.Conditions, clusterv1.Condition{
Type: clusterv1.MachineDeploymentAvailable,
Status: corev1.ConditionTrue,
Reason: clusterv1.MinimumMachinesAvailable,
Message: "MachineDeployment has minimum availability",
})
}

if *deployment.Spec.Replicas == status.ReadyReplicas {
status.Phase = string(clusterv1.MachineDeploymentPhaseRunning)
}
Expand All @@ -393,14 +485,11 @@ func calculateStatus(allMSs []*clusterv1.MachineSet, newMS *clusterv1.MachineSet
if totalReplicas-availableReplicas < 0 {
status.Phase = string(clusterv1.MachineDeploymentPhaseScalingDown)
}
for _, ms := range allMSs {
if ms != nil {
if ms.Status.FailureReason != nil || ms.Status.FailureMessage != nil {
status.Phase = string(clusterv1.MachineDeploymentPhaseFailed)
break
}
}

if failed, _, _ := getMachineSetFailures(allMSs); failed {
status.Phase = string(clusterv1.MachineDeploymentPhaseFailed)
}

return status
}

Expand Down Expand Up @@ -526,3 +615,22 @@ func updateMachineDeployment(ctx context.Context, c client.Client, d *clusterv1.
return patchHelper.Patch(ctx, d)
})
}

// getMachineSetFailures returns true if any one of the MachineSets have failed.
func getMachineSetFailures(allMSs []*clusterv1.MachineSet) (bool, string, string) {
for _, ms := range allMSs {
if ms != nil {
if ms.Status.FailureReason != nil || ms.Status.FailureMessage != nil {
var reason, msg string
if ms.Status.FailureReason != nil {
reason = string(*ms.Status.FailureReason)
}
if ms.Status.FailureMessage != nil {
msg = *ms.Status.FailureMessage
}
return true, reason, msg
}
}
}
return false, "", ""
}
Loading