Skip to content

Commit

Permalink
Merge pull request #272 from syself/feature/autoscaling-from-zero
Browse files Browse the repository at this point in the history
✨ Add support for autoscaling from zero
  • Loading branch information
batistein authored Aug 30, 2022
2 parents c489572 + d8079af commit 60ba3c8
Show file tree
Hide file tree
Showing 17 changed files with 776 additions and 3 deletions.
28 changes: 27 additions & 1 deletion api/v1beta1/hcloudmachinetemplate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package v1beta1

import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
)
Expand All @@ -26,6 +27,20 @@ type HCloudMachineTemplateSpec struct {
Template HCloudMachineTemplateResource `json:"template"`
}

// HCloudMachineTemplateStatus defines the observed state of HCloudMachineTemplate.
type HCloudMachineTemplateStatus struct {
// Capacity defines the resource capacity for this machine.
// This value is used for autoscaling from zero operations as defined in:
// https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20210310-opt-in-autoscaling-from-zero.md
// +optional
Capacity corev1.ResourceList `json:"capacity,omitempty"`

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

// +kubebuilder:subresource:status
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=hcloudmachinetemplates,scope=Namespaced,categories=cluster-api,shortName=capihcmt
// +kubebuilder:printcolumn:name="Image",type="string",JSONPath=".spec.template.spec.imageName",description="Image name"
Expand All @@ -39,7 +54,18 @@ type HCloudMachineTemplate struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec HCloudMachineTemplateSpec `json:"spec,omitempty"`
Spec HCloudMachineTemplateSpec `json:"spec,omitempty"`
Status HCloudMachineTemplateStatus `json:"status,omitempty"`
}

// GetConditions returns the observations of the operational state of the HCloudMachine resource.
func (r *HCloudMachineTemplate) GetConditions() clusterv1.Conditions {
return r.Status.Conditions
}

// SetConditions sets the underlying service state of the HCloudMachine to the predescribed clusterv1.Conditions.
func (r *HCloudMachineTemplate) SetConditions(conditions clusterv1.Conditions) {
r.Status.Conditions = conditions
}

//+kubebuilder:object:root=true
Expand Down
30 changes: 30 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

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

Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,69 @@ spec:
required:
- template
type: object
status:
description: HCloudMachineTemplateStatus defines the observed state of
HCloudMachineTemplate.
properties:
capacity:
additionalProperties:
anyOf:
- type: integer
- type: string
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
x-kubernetes-int-or-string: true
description: 'Capacity defines the resource capacity for this machine.
This value is used for autoscaling from zero operations as defined
in: https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20210310-opt-in-autoscaling-from-zero.md'
type: object
conditions:
description: Conditions defines current service state of the HCloudMachineTemplate.
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:
- lastTransitionTime
- status
- type
type: object
type: array
type: object
type: object
served: true
storage: true
subresources: {}
subresources:
status: {}
20 changes: 20 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ rules:
- get
- patch
- update
- apiGroups:
- infrastructure.cluster.x-k8s.io
resources:
- hcloudmachinetemplates
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- infrastructure.cluster.x-k8s.io
resources:
- hcloudmachinetemplates/status
verbs:
- get
- patch
- update
- apiGroups:
- infrastructure.cluster.x-k8s.io
resources:
Expand Down
7 changes: 7 additions & 0 deletions controllers/controllers_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ var _ = BeforeSuite(func() {
WatchFilterValue: "",
}).SetupWithManager(ctx, testEnv.Manager, controller.Options{})).To(Succeed())

Expect((&HCloudMachineTemplateReconciler{
Client: testEnv.Manager.GetClient(),
APIReader: testEnv.Manager.GetAPIReader(),
HCloudClientFactory: testEnv.HCloudClientFactory,
WatchFilterValue: "",
}).SetupWithManager(ctx, testEnv.Manager, controller.Options{})).To(Succeed())

Expect((&HetznerBareMetalHostReconciler{
Client: testEnv.Manager.GetClient(),
APIReader: testEnv.Manager.GetAPIReader(),
Expand Down
138 changes: 138 additions & 0 deletions controllers/hcloudmachinetemplate_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
Copyright 2022 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 controllers

import (
"context"
"time"

"github.com/pkg/errors"
infrav1 "github.com/syself/cluster-api-provider-hetzner/api/v1beta1"
"github.com/syself/cluster-api-provider-hetzner/pkg/scope"
secretutil "github.com/syself/cluster-api-provider-hetzner/pkg/secrets"
hcloudclient "github.com/syself/cluster-api-provider-hetzner/pkg/services/hcloud/client"
"github.com/syself/cluster-api-provider-hetzner/pkg/services/hcloud/machinetemplate"
"sigs.k8s.io/cluster-api/util"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

// HCloudMachineTemplateReconciler reconciles a HCloudMachineTemplate object.
type HCloudMachineTemplateReconciler struct {
client.Client
APIReader client.Reader
HCloudClientFactory hcloudclient.Factory
WatchFilterValue string
}

// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=hcloudmachinetemplates,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=hcloudmachinetemplates/status,verbs=get;update;patch

// Reconcile manages the lifecycle of an HCloudMachineTemplate object.
func (r *HCloudMachineTemplateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
log := ctrl.LoggerFrom(ctx).WithValues("hcloudmachinetemplate", req.NamespacedName)
log.Info("Reconcile HCloudMachineTemplate")

machineTemplate := &infrav1.HCloudMachineTemplate{}
if err := r.Get(ctx, req.NamespacedName, machineTemplate); err != nil {
log.Error(err, "unable to fetch HCloudMachineTemplate")
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// Fetch the Cluster.
cluster, err := util.GetClusterFromMetadata(ctx, r.Client, machineTemplate.ObjectMeta)
if err != nil {
log.Info("Machine is missing cluster label or cluster does not exist")
return ctrl.Result{}, nil
}

log = log.WithValues("cluster", cluster.Name)

hetznerCluster := &infrav1.HetznerCluster{}

hetznerClusterName := client.ObjectKey{
Namespace: machineTemplate.Namespace,
Name: cluster.Spec.InfrastructureRef.Name,
}
if err := r.Client.Get(ctx, hetznerClusterName, hetznerCluster); err != nil {
log.Info("HetznerCluster is not available yet")
return ctrl.Result{}, nil
}

// Create the scope.
secretManager := secretutil.NewSecretManager(log, r.Client, r.APIReader)
hcloudToken, _, err := getAndValidateHCloudToken(ctx, req.Namespace, hetznerCluster, secretManager)
if err != nil {
return hcloudTokenErrorResult(ctx, err, machineTemplate, infrav1.InstanceReadyCondition, r.Client)
}

hcc := r.HCloudClientFactory.NewClient(hcloudToken)

machineTemplateScope, err := scope.NewHCloudMachineTemplateScope(ctx, scope.HCloudMachineTemplateScopeParams{
Client: r.Client,
Logger: &log,
HCloudMachineTemplate: machineTemplate,
HCloudClient: hcc,
})
if err != nil {
return reconcile.Result{}, errors.Errorf("failed to create scope: %+v", err)
}

// Always close the scope when exiting this function so we can persist any HCloudMachine changes.
defer func() {
if err := machineTemplateScope.Close(ctx); err != nil && reterr == nil {
reterr = err
}
}()

// check whether rate limit has been reached and if so, then wait.
if wait := reconcileRateLimit(machineTemplate); wait {
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}

return r.reconcile(ctx, machineTemplateScope)
}

func (r *HCloudMachineTemplateReconciler) reconcile(ctx context.Context, machineTemplateScope *scope.HCloudMachineTemplateScope) (reconcile.Result, error) {
machineTemplateScope.Info("Reconciling HCloudMachineTemplate")
hcloudMachine := machineTemplateScope.HCloudMachineTemplate

// If the HCloudMachineTemplate doesn't have our finalizer, add it.
controllerutil.AddFinalizer(machineTemplateScope.HCloudMachineTemplate, infrav1.MachineFinalizer)

// Register the finalizer immediately to avoid orphaning HCloud resources on delete
if err := machineTemplateScope.PatchObject(ctx); err != nil {
return ctrl.Result{}, err
}

// reconcile machinetemplate
if result, brk, err := breakReconcile(machinetemplate.NewService(machineTemplateScope).Reconcile(ctx)); brk {
return result, errors.Wrapf(err, "failed to reconcile machinetemplate for HCloudMachineTemplate %s/%s", hcloudMachine.Namespace, hcloudMachine.Name)
}

return reconcile.Result{}, nil
}

func (r *HCloudMachineTemplateReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
return ctrl.NewControllerManagedBy(mgr).
WithOptions(options).
For(&infrav1.HCloudMachineTemplate{}).
Complete(r)
}
Loading

0 comments on commit 60ba3c8

Please sign in to comment.