diff --git a/api/v1alpha1/linodemachine_types.go b/api/v1alpha1/linodemachine_types.go index 151b9afb1..151c217e1 100644 --- a/api/v1alpha1/linodemachine_types.go +++ b/api/v1alpha1/linodemachine_types.go @@ -80,38 +80,28 @@ type LinodeMachineSpec struct { // InstanceMetadataOptions defines metadata of instance type InstanceMetadataOptions struct { // UserData expects a Base64-encoded string - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" UserData string `json:"userData,omitempty"` } // InstanceConfigInterfaceCreateOptions defines network interface config type InstanceConfigInterfaceCreateOptions struct { - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" IPAMAddress string `json:"ipamAddress,omitempty"` // +kubebuilder:validation:MinLength=3 // +kubebuilder:validation:MaxLength=63 - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" // +optional - Label string `json:"label,omitempty"` - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + Label string `json:"label,omitempty"` Purpose linodego.ConfigInterfacePurpose `json:"purpose,omitempty"` - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" - Primary bool `json:"primary,omitempty"` - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + Primary bool `json:"primary,omitempty"` // +optional SubnetID *int `json:"subnetId,omitempty"` - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" // +optional - IPv4 *VPCIPv4 `json:"ipv4,omitempty"` - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + IPv4 *VPCIPv4 `json:"ipv4,omitempty"` IPRanges []string `json:"ipRanges,omitempty"` } // VPCIPv4 defines VPC IPV4 settings type VPCIPv4 struct { - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" - VPC string `json:"vpc,omitempty"` - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" + VPC string `json:"vpc,omitempty"` NAT1To1 string `json:"nat1to1,omitempty"` } diff --git a/api/v1alpha1/linodevpc_types.go b/api/v1alpha1/linodevpc_types.go index 71bfa67d6..61c4072e8 100644 --- a/api/v1alpha1/linodevpc_types.go +++ b/api/v1alpha1/linodevpc_types.go @@ -47,10 +47,8 @@ type LinodeVPCSpec struct { type VPCSubnetCreateOptions struct { // +kubebuilder:validation:MinLength=3 // +kubebuilder:validation:MaxLength=63 - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" // +optional Label string `json:"label,omitempty"` - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable" // +optional IPv4 string `json:"ipv4,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index b01eaae74..3e4d5a884 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -191,7 +191,7 @@ func (in *LinodeClusterTemplate) DeepCopyInto(out *LinodeClusterTemplate) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinodeClusterTemplate. @@ -247,7 +247,7 @@ func (in *LinodeClusterTemplateList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LinodeClusterTemplateResource) DeepCopyInto(out *LinodeClusterTemplateResource) { *out = *in - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinodeClusterTemplateResource. @@ -263,7 +263,7 @@ func (in *LinodeClusterTemplateResource) DeepCopy() *LinodeClusterTemplateResour // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LinodeClusterTemplateSpec) DeepCopyInto(out *LinodeClusterTemplateSpec) { *out = *in - out.Template = in.Template + in.Template.DeepCopyInto(&out.Template) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinodeClusterTemplateSpec. diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeclustertemplates.yaml index 9e4b85d41..23125d4ed 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeclustertemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodeclustertemplates.yaml @@ -74,6 +74,73 @@ spec: region: description: The Linode Region the LinodeCluster lives in. type: string + vpcRef: + description: "ObjectReference contains enough information + to let you inspect or modify the referred object. --- New + uses of this type are discouraged because of difficulty + describing its usage when embedded in APIs. 1. Ignored fields. + \ It includes many fields which are not generally honored. + \ For instance, ResourceVersion and FieldPath are both very + rarely valid in actual usage. 2. Invalid usage help. It + is impossible to add specific help for individual usage. + \ In most embedded usages, there are particular restrictions + like, \"must refer only to types A and B\" or \"UID not + honored\" or \"name must be restricted\". Those cannot be + well described when embedded. 3. Inconsistent validation. + \ Because the usages are different, the validation rules + are different by usage, which makes it hard for users to + predict what will happen. 4. The fields are both imprecise + and overly precise. Kind is not a precise mapping to a + URL. This can produce ambiguity during interpretation and + require a REST mapping. In most cases, the dependency is + on the group,resource tuple and the version of the actual + struct is irrelevant. 5. We cannot easily change it. Because + this type is embedded in many locations, updates to this + type will affect numerous schemas. Don't make new APIs + embed an underspecified API type they do not control. \n + Instead of using this type, create a locally provided and + used type that is well-focused on your reference. For example, + ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 + ." + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: 'If referring to a piece of an object instead + of an entire object, this string should contain a valid + JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container + within a pod, this would take on a value like: "spec.containers{name}" + (where "name" refers to the name of the container that + triggered the event) or if no container name is specified + "spec.containers[2]" (container with index 2 in this + pod). This syntax is chosen only to have some well-defined + way of referencing a part of an object. TODO: this design + is not final and this field is subject to change in + the future.' + type: string + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + namespace: + description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/' + type: string + resourceVersion: + description: 'Specific resourceVersion to which this reference + is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency' + type: string + uid: + description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' + type: string + type: object + x-kubernetes-map-type: atomic + x-kubernetes-validations: + - message: Value is immutable + rule: self == oldSelf required: - region type: object diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachines.yaml index d0681ee1b..54cbaa185 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachines.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachines.yaml @@ -85,55 +85,28 @@ spec: items: type: string type: array - x-kubernetes-validations: - - message: Value is immutable - rule: self == oldSelf ipamAddress: type: string - x-kubernetes-validations: - - message: Value is immutable - rule: self == oldSelf ipv4: description: VPCIPv4 defines VPC IPV4 settings properties: nat1to1: type: string - x-kubernetes-validations: - - message: Value is immutable - rule: self == oldSelf vpc: type: string - x-kubernetes-validations: - - message: Value is immutable - rule: self == oldSelf type: object - x-kubernetes-validations: - - message: Value is immutable - rule: self == oldSelf label: maxLength: 63 minLength: 3 type: string - x-kubernetes-validations: - - message: Value is immutable - rule: self == oldSelf primary: type: boolean - x-kubernetes-validations: - - message: Value is immutable - rule: self == oldSelf purpose: description: ConfigInterfacePurpose options start with InterfacePurpose and include all known interface purpose types type: string - x-kubernetes-validations: - - message: Value is immutable - rule: self == oldSelf subnetId: type: integer - x-kubernetes-validations: - - message: Value is immutable - rule: self == oldSelf type: object type: array x-kubernetes-validations: @@ -152,9 +125,6 @@ spec: userData: description: UserData expects a Base64-encoded string type: string - x-kubernetes-validations: - - message: Value is immutable - rule: self == oldSelf type: object x-kubernetes-validations: - message: Value is immutable diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml index eb65b1b73..f80f5f275 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml @@ -109,6 +109,8 @@ spec: type: string type: object label: + maxLength: 63 + minLength: 3 type: string primary: type: boolean @@ -125,6 +127,8 @@ spec: - message: Value is immutable rule: self == oldSelf label: + maxLength: 63 + minLength: 3 type: string x-kubernetes-validations: - message: Value is immutable diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodevpcs.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodevpcs.yaml index a9f8b94bb..6addad9ce 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodevpcs.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodevpcs.yaml @@ -54,16 +54,10 @@ spec: properties: ipv4: type: string - x-kubernetes-validations: - - message: Value is immutable - rule: self == oldSelf label: maxLength: 63 minLength: 3 type: string - x-kubernetes-validations: - - message: Value is immutable - rule: self == oldSelf type: object type: array x-kubernetes-validations: diff --git a/controller/linodemachine_controller.go b/controller/linodemachine_controller.go index f2e51fa65..6b5237282 100644 --- a/controller/linodemachine_controller.go +++ b/controller/linodemachine_controller.go @@ -54,12 +54,12 @@ var skippedMachinePhases = map[string]bool{ } var skippedInstanceStatuses = map[linodego.InstanceStatus]bool{ - linodego.InstanceOffline: true, linodego.InstanceShuttingDown: true, linodego.InstanceDeleting: true, } var requeueInstanceStatuses = map[linodego.InstanceStatus]bool{ + linodego.InstanceOffline: true, linodego.InstanceBooting: true, linodego.InstanceRebooting: true, linodego.InstanceProvisioning: true, @@ -154,7 +154,7 @@ func (r *LinodeMachineReconciler) Reconcile(ctx context.Context, req ctrl.Reques Name: cluster.Spec.InfrastructureRef.Name, } - if err := r.Client.Get(ctx, linodeClusterKey, linodeCluster); err != nil { + if err = r.Client.Get(ctx, linodeClusterKey, linodeCluster); err != nil { log.Error(err, "Failed to fetch Linode cluster") return ctrl.Result{}, client.IgnoreNotFound(err) @@ -248,6 +248,8 @@ func (r *LinodeMachineReconciler) reconcile( } func (r *LinodeMachineReconciler) reconcileCreate(ctx context.Context, machineScope *scope.MachineScope, logger logr.Logger) (*linodego.Instance, error) { + logger.Info("creating machine") + tags := []string{string(machineScope.LinodeCluster.UID), string(machineScope.LinodeMachine.UID)} linodeInstances, err := machineScope.LinodeClient.ListInstances(ctx, linodego.NewListOptions(1, util.CreateLinodeAPIFilter("", tags))) @@ -327,21 +329,20 @@ func (r *LinodeMachineReconciler) reconcileCreate(ctx context.Context, machineSc } func (r *LinodeMachineReconciler) reconcileUpdate(ctx context.Context, logger logr.Logger, machineScope *scope.MachineScope) (res reconcile.Result, linodeInstance *linodego.Instance, err error) { - if machineScope.LinodeMachine.Spec.InstanceID == nil { - err = errors.New("missing instance ID") - - return - } + logger.Info("updating machine") res = ctrl.Result{} - if linodeInstance, err = machineScope.LinodeClient.GetInstance(ctx, *machineScope.LinodeMachine.Spec.InstanceID); err != nil { - logger.Error(err, "Failed to get Linode machine instance") + if machineScope.LinodeMachine.Spec.InstanceID == nil { + return res, nil, errors.New("missing instance ID") + } - // Not found is not an error - apiErr := linodego.Error{Code: http.StatusNotFound} - if apiErr.Is(err) { - err = nil + if linodeInstance, err = machineScope.LinodeClient.GetInstance(ctx, *machineScope.LinodeMachine.Spec.InstanceID); err != nil { + err = util.IgnoreLinodeAPIError(err, http.StatusNotFound) + if err != nil { + logger.Error(err, "Failed to get Linode machine instance") + } else { + logger.Info("Instance not found, let's create a new one") // Create new machine machineScope.LinodeMachine.Spec.ProviderID = nil @@ -350,32 +351,36 @@ func (r *LinodeMachineReconciler) reconcileUpdate(ctx context.Context, logger lo conditions.MarkFalse(machineScope.LinodeMachine, clusterv1.ReadyCondition, string("missing"), clusterv1.ConditionSeverityWarning, "instance not found") } - return + return res, linodeInstance, err } if _, ok := requeueInstanceStatuses[linodeInstance.Status]; ok { if linodeInstance.Updated.Add(reconciler.DefaultMachineControllerWaitForRunningTimeout).After(time.Now()) { logger.Info("Instance has one operaton running, re-queuing reconciliation", "status", linodeInstance.Status) - res = ctrl.Result{RequeueAfter: reconciler.DefaultMachineControllerWaitForRunningDelay} - } else { - logger.Info("Instance has one operaton long running, skipping reconciliation", "status", linodeInstance.Status) + return ctrl.Result{RequeueAfter: reconciler.DefaultMachineControllerWaitForRunningDelay}, linodeInstance, nil } - return + logger.Info("Instance has one operaton long running, skipping reconciliation", "status", linodeInstance.Status) + + conditions.MarkFalse(machineScope.LinodeMachine, clusterv1.ReadyCondition, string(linodeInstance.Status), clusterv1.ConditionSeverityInfo, "skipped due to long running operation") + + return res, linodeInstance, nil } else if _, ok := skippedInstanceStatuses[linodeInstance.Status]; ok || linodeInstance.Status != linodego.InstanceRunning { logger.Info("Instance has incompatible status, skipping reconciliation", "status", linodeInstance.Status) conditions.MarkFalse(machineScope.LinodeMachine, clusterv1.ReadyCondition, string(linodeInstance.Status), clusterv1.ConditionSeverityInfo, "incompatible status") - return + return res, linodeInstance, nil } + machineScope.LinodeMachine.Status.Ready = true + conditions.MarkTrue(machineScope.LinodeMachine, clusterv1.ReadyCondition) r.Recorder.Event(machineScope.LinodeMachine, corev1.EventTypeNormal, string(clusterv1.ReadyCondition), "instance is running") - return + return res, linodeInstance, nil } func (r *LinodeMachineReconciler) reconcileDelete(ctx context.Context, logger logr.Logger, machineScope *scope.MachineScope) error { @@ -383,11 +388,9 @@ func (r *LinodeMachineReconciler) reconcileDelete(ctx context.Context, logger lo if machineScope.LinodeMachine.Spec.InstanceID != nil { if err := machineScope.LinodeClient.DeleteInstance(ctx, *machineScope.LinodeMachine.Spec.InstanceID); err != nil { - logger.Info("Failed to delete Linode machine instance") + if util.IgnoreLinodeAPIError(err, http.StatusNotFound) != nil { + logger.Error(err, "Failed to delete Linode machine instance") - // Not found is not an error - apiErr := linodego.Error{Code: http.StatusNotFound} - if !apiErr.Is(err) { return err } } diff --git a/controller/linodevpc_controller.go b/controller/linodevpc_controller.go index f6e6b07ad..6c1ca2edb 100644 --- a/controller/linodevpc_controller.go +++ b/controller/linodevpc_controller.go @@ -32,13 +32,13 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/go-logr/logr" infrav1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" "github.com/linode/cluster-api-provider-linode/cloud/scope" "github.com/linode/cluster-api-provider-linode/util" "github.com/linode/cluster-api-provider-linode/util/reconciler" - "github.com/linode/linodego" ) // LinodeVPCReconciler reconciles a LinodeVPC object @@ -129,7 +129,7 @@ func (r *LinodeVPCReconciler) reconcile( if !vpcScope.LinodeVPC.ObjectMeta.DeletionTimestamp.IsZero() { failureReason = infrav1.DeleteVPCError - err = r.reconcileDelete(ctx, logger, vpcScope) + res, err = r.reconcileDelete(ctx, logger, vpcScope) return } @@ -156,6 +156,8 @@ func (r *LinodeVPCReconciler) reconcile( } func (r *LinodeVPCReconciler) reconcileCreate(ctx context.Context, vpcScope *scope.VPCScope, logger logr.Logger) error { + logger.Info("creating vpc") + if err := r.reconcileVPC(ctx, vpcScope, logger); err != nil { logger.Error(err, "Failed to create VPC") @@ -171,6 +173,9 @@ func (r *LinodeVPCReconciler) reconcileCreate(ctx context.Context, vpcScope *sco } func (r *LinodeVPCReconciler) reconcileUpdate(ctx context.Context, logger logr.Logger, vpcScope *scope.VPCScope) error { + logger.Info("updating vpc") + + // Update is not supported at the moment if err := r.reconcileVPC(ctx, vpcScope, logger); err != nil { logger.Error(err, "Failed to update VPC") @@ -185,17 +190,46 @@ func (r *LinodeVPCReconciler) reconcileUpdate(ctx context.Context, logger logr.L return nil } -func (r *LinodeVPCReconciler) reconcileDelete(ctx context.Context, logger logr.Logger, vpcScope *scope.VPCScope) error { +//nolint:nestif // As simple as possible. +func (r *LinodeVPCReconciler) reconcileDelete(ctx context.Context, logger logr.Logger, vpcScope *scope.VPCScope) (reconcile.Result, error) { logger.Info("deleting VPC") + res := ctrl.Result{} + if vpcScope.LinodeVPC.Spec.VPCID != nil { - if err := vpcScope.LinodeClient.DeleteVPC(ctx, *vpcScope.LinodeVPC.Spec.VPCID); err != nil { - logger.Error(err, "Failed to delete VPC") + vpc, err := vpcScope.LinodeClient.GetVPC(ctx, *vpcScope.LinodeVPC.Spec.VPCID) + if util.IgnoreLinodeAPIError(err, http.StatusNotFound) != nil { + logger.Error(err, "Failed to fetch VPC") + + return res, err + } else if vpc == nil { + return res, errors.New("failed to fetch VPC") + } + + if vpc != nil { + for i := range vpc.Subnets { + if len(vpc.Subnets[i].Linodes) == 0 { + continue + } + + if vpc.Updated.Add(reconciler.DefaultVPCControllerWaitForHasNodesTimeout).After(time.Now()) { + logger.Info("VPC has node(s) attached, re-queuing reconciliation") - // Not found is not an error - apiErr := linodego.Error{Code: http.StatusNotFound} - if !apiErr.Is(err) { - return err + return ctrl.Result{RequeueAfter: reconciler.DefaultVPCControllerWaitForHasNodesDelay}, nil + } + + logger.Info("VPC has node(s) attached for long, skipping reconciliation") + + conditions.MarkFalse(vpcScope.LinodeVPC, clusterv1.ReadyCondition, clusterv1.DeletionFailedReason, clusterv1.ConditionSeverityInfo, "skipped due to node(s) attached") + + return res, nil + } + + err = vpcScope.LinodeClient.DeleteVPC(ctx, *vpcScope.LinodeVPC.Spec.VPCID) + if util.IgnoreLinodeAPIError(err, http.StatusNotFound) != nil { + logger.Error(err, "Failed to delete VPC") + + return res, err } } } else { @@ -209,7 +243,7 @@ func (r *LinodeVPCReconciler) reconcileDelete(ctx context.Context, logger logr.L vpcScope.LinodeVPC.Spec.VPCID = nil controllerutil.RemoveFinalizer(vpcScope.LinodeVPC, infrav1.GroupVersion.String()) - return nil + return res, nil } // SetupWithManager sets up the controller with the Manager. diff --git a/controller/linodevpc_controller_helpers.go b/controller/linodevpc_controller_helpers.go index 67b2185a1..5e9a3ff03 100644 --- a/controller/linodevpc_controller_helpers.go +++ b/controller/linodevpc_controller_helpers.go @@ -54,12 +54,12 @@ func (r *LinodeVPCReconciler) reconcileVPC(ctx context.Context, vpcScope *scope. return nil } - linodeVPC, err := vpcScope.LinodeClient.CreateVPC(ctx, *createConfig) + vpc, err := vpcScope.LinodeClient.CreateVPC(ctx, *createConfig) if err != nil { logger.Error(err, "Failed to create VPC") return err - } else if linodeVPC == nil { + } else if vpc == nil { err = errors.New("missing VPC") logger.Error(err, "Panic! Failed to create VPC") @@ -67,7 +67,7 @@ func (r *LinodeVPCReconciler) reconcileVPC(ctx context.Context, vpcScope *scope. return err } - vpcScope.LinodeVPC.Spec.VPCID = &linodeVPC.ID + vpcScope.LinodeVPC.Spec.VPCID = &vpc.ID return nil } diff --git a/util/helpers.go b/util/helpers.go index 352555ec6..3607960d3 100644 --- a/util/helpers.go +++ b/util/helpers.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/linode/linodego" "k8s.io/apimachinery/pkg/types" ) @@ -38,3 +39,13 @@ func CreateLinodeAPIFilter(label string, tags []string) string { return string(rawFilter) } + +// IgnoreLinodeAPIError returns the error except matches to status code +func IgnoreLinodeAPIError(err error, code int) error { + apiErr := linodego.Error{Code: code} + if apiErr.Is(err) { + err = nil + } + + return err +} diff --git a/util/reconciler/defaults.go b/util/reconciler/defaults.go index 0c789e20a..7d75f0589 100644 --- a/util/reconciler/defaults.go +++ b/util/reconciler/defaults.go @@ -30,6 +30,11 @@ const ( DefaultMachineControllerWaitForRunningDelay = 5 * time.Second // DefaultMachineControllerWaitForRunningTimeout is the default timeout if instance is not running. DefaultMachineControllerWaitForRunningTimeout = 20 * time.Minute + + // DefaultVPCControllerWaitForHasNodesDelay is the default requeue delay if VPC has nodes. + DefaultVPCControllerWaitForHasNodesDelay = 5 * time.Second + // DefaultVPCControllerWaitForHasNodesTimeout is the default timeout if instance is not running. + DefaultVPCControllerWaitForHasNodesTimeout = 20 * time.Minute ) // DefaultedLoopTimeout will default the timeout if it is zero-valued.