From e75f3cac44ad43a1c5dbc0c0ead153b3f79bd848 Mon Sep 17 00:00:00 2001 From: Joel Speed Date: Fri, 12 Mar 2021 11:38:04 +0000 Subject: [PATCH] Bug 1910318: Add condition to show actuator exists condition on machine --- go.mod | 2 +- go.sum | 4 +- .../apis/machine/v1beta1/condition_consts.go | 15 ++ .../pkg/apis/machine/v1beta1/machine_types.go | 11 ++ .../apis/machine/v1beta1/machine_webhook.go | 13 +- .../machine/v1beta1/machineset_webhook.go | 15 +- .../machine/v1beta1/zz_generated.deepcopy.go | 7 + .../pkg/controller/machine/controller.go | 172 +++++++++++++----- .../pkg/util/conditions/conditions.go | 26 +++ .../pkg/util/conditions/getter.go | 49 +++++ .../pkg/util/conditions/matcher.go | 102 +++++++++++ .../pkg/util/conditions/setter.go | 127 +++++++++++++ vendor/modules.txt | 3 +- 13 files changed, 474 insertions(+), 72 deletions(-) create mode 100644 vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/conditions.go create mode 100644 vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/getter.go create mode 100644 vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/matcher.go create mode 100644 vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/setter.go diff --git a/go.mod b/go.mod index 4338bd4533..614e3f3262 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/onsi/ginkgo v1.14.1 github.com/onsi/gomega v1.10.2 github.com/openshift/api v0.0.0-20201216151826-78a19e96f9eb - github.com/openshift/machine-api-operator v0.2.1-0.20210104142355-8e6ae0acdfcf + github.com/openshift/machine-api-operator v0.2.1-0.20210310053650-d40398c49baf // kube 1.18 k8s.io/api v0.20.0 diff --git a/go.sum b/go.sum index b9c4d2f35f..2d9169c124 100644 --- a/go.sum +++ b/go.sum @@ -557,8 +557,8 @@ github.com/openshift/machine-api-operator v0.2.1-0.20200926044412-b7d860f8074c/g github.com/openshift/machine-api-operator v0.2.1-0.20201002104344-6abfb5440597 h1:2leDrsKmE7ppJSthf6SiD+Pqjyis633L/n+YdTVdBbo= github.com/openshift/machine-api-operator v0.2.1-0.20201002104344-6abfb5440597/go.mod h1:+oAfoCl+TUd2TM79/6NdqLpFUHIJpmqkKdmiHe2O7mw= github.com/openshift/machine-api-operator v0.2.1-0.20201203125141-79567cb3368e/go.mod h1:Vxdx8K+8sbdcGozW86hSvcVl5JgJOqNFYhLRRhEM9HY= -github.com/openshift/machine-api-operator v0.2.1-0.20210104142355-8e6ae0acdfcf h1:+/Lqs2LFqB0X38Kwakqq5qWy/1YBstY/vuNJcYwqJ3A= -github.com/openshift/machine-api-operator v0.2.1-0.20210104142355-8e6ae0acdfcf/go.mod h1:U5eAHChde1XvtQy3s1Zcr7ll4X7heb0SzYpaiAwxmQc= +github.com/openshift/machine-api-operator v0.2.1-0.20210310053650-d40398c49baf h1:C4nn2kbSDhLXwg7W87EgcDpri83QnGSbH1C0cqJxHgI= +github.com/openshift/machine-api-operator v0.2.1-0.20210310053650-d40398c49baf/go.mod h1:N3Q+UKEziycun6J3kyxQnRsBBebjwm9fnD6vSnUWqRU= github.com/operator-framework/operator-sdk v0.5.1-0.20190301204940-c2efe6f74e7b/go.mod h1:iVyukRkam5JZa8AnjYf+/G3rk7JI1+M6GsU0sq0B9NA= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= diff --git a/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/condition_consts.go b/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/condition_consts.go index c840522d21..0ca7a139e8 100644 --- a/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/condition_consts.go +++ b/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/condition_consts.go @@ -27,3 +27,18 @@ const ( // from making any further remediations. TooManyUnhealthyReason = "TooManyUnhealthy" ) + +const ( + // InstanceExistsCondition is set on the Machine to show whether a virtual mahcine has been created by the cloud provider. + InstanceExistsCondition ConditionType = "InstanceExists" + + // ErrorCheckingProviderReason is the reason used when the exist operation fails. + // This would normally be because we cannot contact the provider. + ErrorCheckingProviderReason = "ErrorCheckingProvider" + + // InstanceMissingReason is the reason used when the machine was provisioned, but the instance has gone missing. + InstanceMissingReason = "InstanceMissing" + + // InstanceNotCreatedReason is the reason used when the machine has not yet been provisioned. + InstanceNotCreatedReason = "InstanceNotCreated" +) diff --git a/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/machine_types.go b/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/machine_types.go index 6fa53ba670..a38c8909cf 100644 --- a/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/machine_types.go +++ b/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/machine_types.go @@ -59,6 +59,14 @@ type Machine struct { Status MachineStatus `json:"status,omitempty"` } +func (m *Machine) GetConditions() Conditions { + return m.Status.Conditions +} + +func (m *Machine) SetConditions(conditions Conditions) { + m.Status.Conditions = conditions +} + // MachineSpec defines the desired state of Machine type MachineSpec struct { // ObjectMeta will autopopulate the Node created. Use this to @@ -165,6 +173,9 @@ type MachineStatus struct { // One of: Failed, Provisioning, Provisioned, Running, Deleting // +optional Phase *string `json:"phase,omitempty"` + + // Conditions defines the current state of the Machine + Conditions Conditions `json:"conditions,omitempty"` } // LastOperation represents the detail of the last performed operation on the MachineObject. diff --git a/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/machine_webhook.go b/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/machine_webhook.go index a5299328bc..991edc952b 100644 --- a/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/machine_webhook.go +++ b/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/machine_webhook.go @@ -224,27 +224,18 @@ type machineDefaulterHandler struct { } // NewValidator returns a new machineValidatorHandler. -func NewMachineValidator() (*machineValidatorHandler, error) { +func NewMachineValidator(client client.Client) (*machineValidatorHandler, error) { infra, err := getInfra() if err != nil { return nil, err } - cfg, err := ctrl.GetConfig() - if err != nil { - return nil, err - } - c, err := client.New(cfg, client.Options{}) - if err != nil { - return nil, fmt.Errorf("failed to build kubernetes client: %v", err) - } - dns, err := getDNS() if err != nil { return nil, err } - return createMachineValidator(infra, c, dns), nil + return createMachineValidator(infra, client, dns), nil } func createMachineValidator(infra *osconfigv1.Infrastructure, client client.Client, dns *osconfigv1.DNS) *machineValidatorHandler { diff --git a/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/machineset_webhook.go b/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/machineset_webhook.go index 8b6bda1e6f..7d71ed5413 100644 --- a/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/machineset_webhook.go +++ b/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/machineset_webhook.go @@ -3,14 +3,12 @@ package v1beta1 import ( "context" "encoding/json" - "fmt" "net/http" osconfigv1 "github.com/openshift/api/config/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/klog/v2" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) @@ -30,27 +28,18 @@ type machineSetDefaulterHandler struct { } // NewMachineSetValidator returns a new machineSetValidatorHandler. -func NewMachineSetValidator() (*machineSetValidatorHandler, error) { +func NewMachineSetValidator(client client.Client) (*machineSetValidatorHandler, error) { infra, err := getInfra() if err != nil { return nil, err } - cfg, err := ctrl.GetConfig() - if err != nil { - return nil, err - } - c, err := client.New(cfg, client.Options{}) - if err != nil { - return nil, fmt.Errorf("failed to build kubernetes client: %v", err) - } - dns, err := getDNS() if err != nil { return nil, err } - return createMachineSetValidator(infra, c, dns), nil + return createMachineSetValidator(infra, client, dns), nil } func createMachineSetValidator(infra *osconfigv1.Infrastructure, client client.Client, dns *osconfigv1.DNS) *machineSetValidatorHandler { diff --git a/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/zz_generated.deepcopy.go b/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/zz_generated.deepcopy.go index 059e3e04da..29075384e9 100644 --- a/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/zz_generated.deepcopy.go +++ b/vendor/github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1/zz_generated.deepcopy.go @@ -453,6 +453,13 @@ func (in *MachineStatus) DeepCopyInto(out *MachineStatus) { *out = new(string) **out = **in } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MachineStatus. diff --git a/vendor/github.com/openshift/machine-api-operator/pkg/controller/machine/controller.go b/vendor/github.com/openshift/machine-api-operator/pkg/controller/machine/controller.go index 9cf50089a9..6b578e9768 100644 --- a/vendor/github.com/openshift/machine-api-operator/pkg/controller/machine/controller.go +++ b/vendor/github.com/openshift/machine-api-operator/pkg/controller/machine/controller.go @@ -20,11 +20,13 @@ import ( "context" "errors" "fmt" + "reflect" "time" machinev1 "github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1" "github.com/openshift/machine-api-operator/pkg/metrics" "github.com/openshift/machine-api-operator/pkg/util" + "github.com/openshift/machine-api-operator/pkg/util/conditions" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -35,6 +37,7 @@ import ( "k8s.io/client-go/tools/record" "k8s.io/klog/v2" "k8s.io/kubectl/pkg/drain" + "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -92,7 +95,7 @@ const ( // Hardcoded instance state set on machine failure unknownInstanceState = "Unknown" - skipWaitForDeleteTimeoutSeconds = 60 * 5 + skipWaitForDeleteTimeoutSeconds = 1 ) var DefaultActuator Actuator @@ -144,6 +147,9 @@ type ReconcileMachine struct { eventRecorder record.EventRecorder actuator Actuator + + // nowFunc is used to mock time in testing. It should be nil in production. + nowFunc func() time.Time } // Reconcile reads that state of the cluster for a Machine object and makes changes based on the state read @@ -167,6 +173,9 @@ func (r *ReconcileMachine) Reconcile(ctx context.Context, request reconcile.Requ machineName := m.GetName() klog.Infof("%v: reconciling Machine", machineName) + // Get the original state of conditions now so that they can be used to calculate the patch later + originalConditions := m.GetConditions() + if errList := m.Validate(); len(errList) > 0 { err := fmt.Errorf("%v: machine validation failed: %v", machineName, errList.ToAggregate().Error()) klog.Error(err) @@ -195,7 +204,7 @@ func (r *ReconcileMachine) Reconcile(ctx context.Context, request reconcile.Requ } if !m.ObjectMeta.DeletionTimestamp.IsZero() { - if err := r.setPhase(m, phaseDeleting, ""); err != nil { + if err := r.updateStatus(m, phaseDeleting, nil, originalConditions); err != nil { return reconcile.Result{}, err } @@ -269,51 +278,87 @@ func (r *ReconcileMachine) Reconcile(ctx context.Context, request reconcile.Requ instanceExists, err := r.actuator.Exists(ctx, m) if err != nil { klog.Errorf("%v: failed to check if machine exists: %v", machineName, err) + + conditions.Set(m, conditions.UnknownCondition( + machinev1.InstanceExistsCondition, + machinev1.ErrorCheckingProviderReason, + "Failed to check if machine exists: %v", err, + )) + + if patchErr := r.updateStatus(m, pointer.StringPtrDerefOr(m.Status.Phase, ""), nil, originalConditions); patchErr != nil { + klog.Errorf("%v: error patching status: %v", machineName, patchErr) + } + return reconcile.Result{}, err } if instanceExists { + conditions.MarkTrue(m, machinev1.InstanceExistsCondition) + klog.Infof("%v: reconciling machine triggers idempotent update", machineName) if err := r.actuator.Update(ctx, m); err != nil { klog.Errorf("%v: error updating machine: %v, retrying in %v seconds", machineName, err, requeueAfter) + + if patchErr := r.updateStatus(m, pointer.StringPtrDerefOr(m.Status.Phase, ""), nil, originalConditions); patchErr != nil { + klog.Errorf("%v: error patching status: %v", machineName, patchErr) + } + return reconcile.Result{RequeueAfter: requeueAfter}, nil } if !machineIsProvisioned(m) { klog.Errorf("%v: instance exists but providerID or addresses has not been given to the machine yet, requeuing", machineName) + if patchErr := r.updateStatus(m, pointer.StringPtrDerefOr(m.Status.Phase, ""), nil, originalConditions); patchErr != nil { + klog.Errorf("%v: error patching status: %v", machineName, patchErr) + } + return reconcile.Result{RequeueAfter: requeueAfter}, nil } if !machineHasNode(m) { // Requeue until we reach running phase - if err := r.setPhase(m, phaseProvisioned, ""); err != nil { + if err := r.updateStatus(m, phaseProvisioned, nil, originalConditions); err != nil { return reconcile.Result{}, err } klog.Infof("%v: has no node yet, requeuing", machineName) return reconcile.Result{RequeueAfter: requeueAfter}, nil } - return reconcile.Result{}, r.setPhase(m, phaseRunning, "") + return reconcile.Result{}, r.updateStatus(m, phaseRunning, nil, originalConditions) } // Instance does not exist but the machine has been given a providerID/address. // This can only be reached if an instance was deleted outside the machine API if machineIsProvisioned(m) { - if err := r.setPhase(m, phaseFailed, "Can't find created instance."); err != nil { + conditions.Set(m, conditions.FalseCondition( + machinev1.InstanceExistsCondition, + machinev1.InstanceMissingReason, + machinev1.ConditionSeverityWarning, + "Instance not found on provider", + )) + + if err := r.updateStatus(m, phaseFailed, errors.New("Can't find created instance."), originalConditions); err != nil { return reconcile.Result{}, err } return reconcile.Result{}, nil } + conditions.Set(m, conditions.FalseCondition( + machinev1.InstanceExistsCondition, + machinev1.InstanceNotCreatedReason, + machinev1.ConditionSeverityWarning, + "Instance has not been created", + )) + // Machine resource created and instance does not exist yet. - if err := r.setPhase(m, phaseProvisioning, ""); err != nil { + if err := r.updateStatus(m, phaseProvisioning, nil, originalConditions); err != nil { return reconcile.Result{}, err } klog.Infof("%v: reconciling machine triggers idempotent create", machineName) if err := r.actuator.Create(ctx, m); err != nil { klog.Warningf("%v: failed to create machine: %v", machineName, err) if isInvalidMachineConfigurationError(err) { - if err := r.setPhase(m, phaseFailed, err.Error()); err != nil { + if err := r.updateStatus(m, phaseFailed, err, originalConditions); err != nil { return reconcile.Result{}, err } return reconcile.Result{}, nil @@ -362,9 +407,12 @@ func (r *ReconcileMachine) drainNode(machine *machinev1.Machine) error { } if nodeIsUnreachable(node) { - klog.Infof("%q: Node %q is unreachable, draining will wait %d seconds after pod is signalled for deletion and skip after it", - machine.Name, node.Name, skipWaitForDeleteTimeoutSeconds) + klog.Infof("%q: Node %q is unreachable, draining will ignore gracePeriod. PDBs are still honored.", + machine.Name, node.Name) + // Since kubelet is unreachable, pods will never disappear and we still + // need SkipWaitForDeleteTimeoutSeconds so we don't wait for them. drainer.SkipWaitForDeleteTimeoutSeconds = skipWaitForDeleteTimeoutSeconds + drainer.GracePeriodSeconds = 1 } if err := drain.RunCordonOrUncordon(drainer, node, true); err != nil { @@ -418,53 +466,80 @@ func isInvalidMachineConfigurationError(err error) bool { return false } -func (r *ReconcileMachine) setPhase(machine *machinev1.Machine, phase string, errorMessage string) error { +// updateStatus is intended to ensure that the status of the Machine reflects the input to this function. +// Because the conditions are set on the machine outside of this function, we must pass the original state of the +// machine conditions so that the diff can be calculated properly within this function. +func (r *ReconcileMachine) updateStatus(machine *machinev1.Machine, phase string, failureCause error, originalConditions []machinev1.Condition) error { if stringPointerDeref(machine.Status.Phase) != phase { klog.V(3).Infof("%v: going into phase %q", machine.GetName(), phase) + } - // A call to Patch will mutate our local copy of the machine to match what is stored in the API. - // Before we make any changes to the status subresource on our local copy, we need to patch the object first, - // otherwise our local changes to the status subresource will be lost. - if phase == phaseFailed { - err := r.patchFailedMachineInstanceAnnotation(machine) - if err != nil { - klog.Errorf("Failed to update machine %q: %v", machine.GetName(), err) - return err - } + // Conditions need to be copied as they are set outside of this function. + // They will be restored after any updates to the base. + conditions := machine.GetConditions() + + // A call to Patch will mutate our local copy of the machine to match what is stored in the API. + // Before we make any changes to the status subresource on our local copy, we need to patch the object first, + // otherwise our local changes to the status subresource will be lost. + if phase == phaseFailed { + err := r.patchFailedMachineInstanceAnnotation(machine) + if err != nil { + klog.Errorf("Failed to update machine %q: %v", machine.GetName(), err) + return err } + } - // Since we may have mutated the local copy of the machine above, we need to calculate baseToPatch here. - // Any updates to the status must be done after this point. - baseToPatch := client.MergeFrom(machine.DeepCopy()) + // To ensure conditions can be patched properly, set the original conditions on the baseMachine. + // This allows the difference to be calculated as part of the patch. + baseMachine := machine.DeepCopy() + baseMachine.SetConditions(originalConditions) + machine.SetConditions(conditions) - if phase == phaseFailed { - if err := r.overrideFailedMachineProviderStatusState(machine); err != nil { - klog.Errorf("Failed to update machine provider status %q: %v", machine.GetName(), err) - return err - } - } + // Since we may have mutated the local copy of the machine above, we need to calculate baseToPatch here. + // Any updates to the status must be done after this point. + baseToPatch := client.MergeFrom(baseMachine) - machine.Status.Phase = &phase - machine.Status.ErrorMessage = nil - now := metav1.Now() - machine.Status.LastUpdated = &now - if phase == phaseFailed && errorMessage != "" { - machine.Status.ErrorMessage = &errorMessage - } - if err := r.Client.Status().Patch(context.Background(), machine, baseToPatch); err != nil { - klog.Errorf("Failed to update machine status %q: %v", machine.GetName(), err) + if phase == phaseFailed { + if err := r.overrideFailedMachineProviderStatusState(machine); err != nil { + klog.Errorf("Failed to update machine provider status %q: %v", machine.GetName(), err) return err } + } - // Update the metric after everything else has succeeded to prevent duplicate - // entries when there are failures - if phase != phaseDeleting { - // Apart from deleting, update the transition metric - // Deleting would always end up in the infinite bucket - timeElapsed := time.Now().Sub(machine.GetCreationTimestamp().Time).Seconds() - metrics.MachinePhaseTransitionSeconds.With(map[string]string{"phase": phase}).Observe(timeElapsed) + machine.Status.Phase = &phase + machine.Status.ErrorReason = nil + machine.Status.ErrorMessage = nil + if phase == phaseFailed && failureCause != nil { + var machineError *MachineError + if errors.As(failureCause, &machineError) { + machine.Status.ErrorReason = &machineError.Reason + machine.Status.ErrorMessage = &machineError.Message + } else { + errorMessage := failureCause.Error() + machine.Status.ErrorMessage = &errorMessage } } + + if !reflect.DeepEqual(baseMachine.Status, machine.Status) { + // Something on the status has been changed this reconcile + now := metav1.NewTime(r.now()) + machine.Status.LastUpdated = &now + } + + if err := r.Client.Status().Patch(context.Background(), machine, baseToPatch); err != nil { + klog.Errorf("Failed to update machine status %q: %v", machine.GetName(), err) + return err + } + + // Update the metric after everything else has succeeded to prevent duplicate + // entries when there are failures + if phase != phaseDeleting { + // Apart from deleting, update the transition metric + // Deleting would always end up in the infinite bucket + timeElapsed := r.now().Sub(machine.GetCreationTimestamp().Time).Seconds() + metrics.MachinePhaseTransitionSeconds.With(map[string]string{"phase": phase}).Observe(timeElapsed) + } + return nil } @@ -519,6 +594,15 @@ func (r *ReconcileMachine) overrideFailedMachineProviderStatusState(machine *mac return nil } +// now is used to get the current time. If the reconciler nowFunc is no nil this will be used instead of time.Now(). +// This is only here so that tests can modify the time to check time based assertions. +func (r *ReconcileMachine) now() time.Time { + if r.nowFunc != nil { + return r.nowFunc() + } + return time.Now() +} + func machineIsProvisioned(machine *machinev1.Machine) bool { return len(machine.Status.Addresses) > 0 || stringPointerDeref(machine.Spec.ProviderID) != "" } diff --git a/vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/conditions.go b/vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/conditions.go new file mode 100644 index 0000000000..f4763845c1 --- /dev/null +++ b/vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/conditions.go @@ -0,0 +1,26 @@ +package conditions + +import ( + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" +) + +// GetNodeCondition returns node condition by type +func GetNodeCondition(node *corev1.Node, conditionType corev1.NodeConditionType) *corev1.NodeCondition { + for _, cond := range node.Status.Conditions { + if cond.Type == conditionType { + return &cond + } + } + return nil +} + +// GetDeploymentCondition returns node condition by type +func GetDeploymentCondition(deployment *appsv1.Deployment, conditionType appsv1.DeploymentConditionType) *appsv1.DeploymentCondition { + for _, cond := range deployment.Status.Conditions { + if cond.Type == conditionType { + return &cond + } + } + return nil +} diff --git a/vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/getter.go b/vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/getter.go new file mode 100644 index 0000000000..5899476a75 --- /dev/null +++ b/vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/getter.go @@ -0,0 +1,49 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package conditions + +import ( + mapiv1 "github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// Getter interface defines methods that a Machine API object should implement in order to +// use the conditions package for getting conditions. +type Getter interface { + runtime.Object + metav1.Object + + // GetConditions returns the list of conditions for a cluster API object. + GetConditions() mapiv1.Conditions +} + +// Get returns the condition with the given type, if the condition does not exists, +// it returns nil. +func Get(from Getter, t mapiv1.ConditionType) *mapiv1.Condition { + conditions := from.GetConditions() + if conditions == nil { + return nil + } + + for _, condition := range conditions { + if condition.Type == t { + return &condition + } + } + return nil +} diff --git a/vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/matcher.go b/vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/matcher.go new file mode 100644 index 0000000000..74a9aa31aa --- /dev/null +++ b/vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/matcher.go @@ -0,0 +1,102 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package conditions + +import ( + "fmt" + + . "github.com/onsi/gomega" + "github.com/onsi/gomega/types" + mapiv1 "github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1" +) + +// MatchConditions returns a custom matcher to check equality of mapiv1.Conditions +func MatchConditions(expected mapiv1.Conditions) types.GomegaMatcher { + return &matchConditions{ + expected: expected, + } +} + +type matchConditions struct { + expected mapiv1.Conditions +} + +func (m matchConditions) Match(actual interface{}) (success bool, err error) { + elems := []interface{}{} + for _, condition := range m.expected { + elems = append(elems, MatchCondition(condition)) + } + + return ConsistOf(elems).Match(actual) +} + +func (m matchConditions) FailureMessage(actual interface{}) (message string) { + return fmt.Sprintf("expected\n\t%#v\nto match\n\t%#v\n", actual, m.expected) +} + +func (m matchConditions) NegatedFailureMessage(actual interface{}) (message string) { + return fmt.Sprintf("expected\n\t%#v\nto not match\n\t%#v\n", actual, m.expected) +} + +// MatchCondition returns a custom matcher to check equality of mapiv1.Condition +func MatchCondition(expected mapiv1.Condition) types.GomegaMatcher { + return &matchCondition{ + expected: expected, + } +} + +type matchCondition struct { + expected mapiv1.Condition +} + +func (m matchCondition) Match(actual interface{}) (success bool, err error) { + actualCondition, ok := actual.(mapiv1.Condition) + if !ok { + return false, fmt.Errorf("actual should be of type Condition") + } + + ok, err = Equal(m.expected.Type).Match(actualCondition.Type) + if !ok { + return ok, err + } + ok, err = Equal(m.expected.Status).Match(actualCondition.Status) + if !ok { + return ok, err + } + ok, err = Equal(m.expected.Severity).Match(actualCondition.Severity) + if !ok { + return ok, err + } + ok, err = Equal(m.expected.Reason).Match(actualCondition.Reason) + if !ok { + return ok, err + } + ok, err = Equal(m.expected.Message).Match(actualCondition.Message) + if !ok { + return ok, err + } + + return ok, err +} + +func (m matchCondition) FailureMessage(actual interface{}) (message string) { + return fmt.Sprintf("expected\n\t%#v\nto match\n\t%#v\n", actual, m.expected) +} + +func (m matchCondition) NegatedFailureMessage(actual interface{}) (message string) { + return fmt.Sprintf("expected\n\t%#v\nto not match\n\t%#v\n", actual, m.expected) +} diff --git a/vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/setter.go b/vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/setter.go new file mode 100644 index 0000000000..73e9a6b4f5 --- /dev/null +++ b/vendor/github.com/openshift/machine-api-operator/pkg/util/conditions/setter.go @@ -0,0 +1,127 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package conditions + +import ( + "fmt" + "sort" + "time" + + mapiv1 "github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Setter interface defines methods that a Machine API object should implement in order to +// use the conditions package for setting conditions. +type Setter interface { + Getter + SetConditions(mapiv1.Conditions) +} + +// Set sets the given condition. +// +// NOTE: If a condition already exists, the LastTransitionTime is updated only if a change is detected +// in any of the following fields: Status, Reason, Severity and Message. +func Set(to Setter, condition *mapiv1.Condition) { + if to == nil || condition == nil { + return + } + + // Check if the new conditions already exists, and change it only if there is a status + // transition (otherwise we should preserve the current last transition time)- + conditions := to.GetConditions() + exists := false + for i := range conditions { + existingCondition := conditions[i] + if existingCondition.Type == condition.Type { + exists = true + if !hasSameState(&existingCondition, condition) { + condition.LastTransitionTime = metav1.NewTime(time.Now().UTC().Truncate(time.Second)) + conditions[i] = *condition + break + } + condition.LastTransitionTime = existingCondition.LastTransitionTime + break + } + } + + // If the condition does not exist, add it, setting the transition time only if not already set + if !exists { + if condition.LastTransitionTime.IsZero() { + condition.LastTransitionTime = metav1.NewTime(time.Now().UTC().Truncate(time.Second)) + } + conditions = append(conditions, *condition) + } + + // Sorts conditions for convenience of the consumer, i.e. kubectl. + sort.Slice(conditions, func(i, j int) bool { + return lexicographicLess(&conditions[i], &conditions[j]) + }) + + to.SetConditions(conditions) +} + +// TrueCondition returns a condition with Status=True and the given type. +func TrueCondition(t mapiv1.ConditionType) *mapiv1.Condition { + return &mapiv1.Condition{ + Type: t, + Status: corev1.ConditionTrue, + } +} + +// FalseCondition returns a condition with Status=False and the given type. +func FalseCondition(t mapiv1.ConditionType, reason string, severity mapiv1.ConditionSeverity, messageFormat string, messageArgs ...interface{}) *mapiv1.Condition { + return &mapiv1.Condition{ + Type: t, + Status: corev1.ConditionFalse, + Reason: reason, + Severity: severity, + Message: fmt.Sprintf(messageFormat, messageArgs...), + } +} + +// UnknownCondition returns a condition with Status=Unknown and the given type. +func UnknownCondition(t mapiv1.ConditionType, reason string, messageFormat string, messageArgs ...interface{}) *mapiv1.Condition { + return &mapiv1.Condition{ + Type: t, + Status: corev1.ConditionUnknown, + Reason: reason, + Message: fmt.Sprintf(messageFormat, messageArgs...), + } +} + +// MarkTrue sets Status=True for the condition with the given type. +func MarkTrue(to Setter, t mapiv1.ConditionType) { + Set(to, TrueCondition(t)) +} + +// lexicographicLess returns true if a condition is less than another with regards to the +// to order of conditions designed for convenience of the consumer, i.e. kubectl. +func lexicographicLess(i, j *mapiv1.Condition) bool { + return i.Type < j.Type +} + +// hasSameState returns true if a condition has the same state of another; state is defined +// by the union of following fields: Type, Status, Reason, Severity and Message (it excludes LastTransitionTime). +func hasSameState(i, j *mapiv1.Condition) bool { + return i.Type == j.Type && + i.Status == j.Status && + i.Reason == j.Reason && + i.Severity == j.Severity && + i.Message == j.Message +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 42cb52abfa..ce465f866c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -212,7 +212,7 @@ github.com/openshift/client-go/config/clientset/versioned/scheme github.com/openshift/client-go/config/clientset/versioned/typed/config/v1 # github.com/openshift/cluster-api-provider-gcp v0.0.1-0.20201201000827-1117a4fc438c github.com/openshift/cluster-api-provider-gcp/pkg/apis/gcpprovider/v1beta1 -# github.com/openshift/machine-api-operator v0.2.1-0.20210104142355-8e6ae0acdfcf +# github.com/openshift/machine-api-operator v0.2.1-0.20210310053650-d40398c49baf ## explicit github.com/openshift/machine-api-operator/pkg/apis/machine github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1 @@ -226,6 +226,7 @@ github.com/openshift/machine-api-operator/pkg/generated/informers/externalversio github.com/openshift/machine-api-operator/pkg/generated/listers/machine/v1beta1 github.com/openshift/machine-api-operator/pkg/metrics github.com/openshift/machine-api-operator/pkg/util +github.com/openshift/machine-api-operator/pkg/util/conditions # github.com/peterbourgon/diskv v2.0.1+incompatible github.com/peterbourgon/diskv # github.com/pkg/errors v0.9.1