Skip to content

Commit 60ba3c8

Browse files
authored
Merge pull request #272 from syself/feature/autoscaling-from-zero
✨ Add support for autoscaling from zero
2 parents c489572 + d8079af commit 60ba3c8

17 files changed

+776
-3
lines changed

api/v1beta1/hcloudmachinetemplate_types.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package v1beta1
1818

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

30+
// HCloudMachineTemplateStatus defines the observed state of HCloudMachineTemplate.
31+
type HCloudMachineTemplateStatus struct {
32+
// Capacity defines the resource capacity for this machine.
33+
// This value is used for autoscaling from zero operations as defined in:
34+
// https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20210310-opt-in-autoscaling-from-zero.md
35+
// +optional
36+
Capacity corev1.ResourceList `json:"capacity,omitempty"`
37+
38+
// Conditions defines current service state of the HCloudMachineTemplate.
39+
// +optional
40+
Conditions clusterv1.Conditions `json:"conditions,omitempty"`
41+
}
42+
43+
// +kubebuilder:subresource:status
2944
// +kubebuilder:object:root=true
3045
// +kubebuilder:resource:path=hcloudmachinetemplates,scope=Namespaced,categories=cluster-api,shortName=capihcmt
3146
// +kubebuilder:printcolumn:name="Image",type="string",JSONPath=".spec.template.spec.imageName",description="Image name"
@@ -39,7 +54,18 @@ type HCloudMachineTemplate struct {
3954
metav1.TypeMeta `json:",inline"`
4055
metav1.ObjectMeta `json:"metadata,omitempty"`
4156

42-
Spec HCloudMachineTemplateSpec `json:"spec,omitempty"`
57+
Spec HCloudMachineTemplateSpec `json:"spec,omitempty"`
58+
Status HCloudMachineTemplateStatus `json:"status,omitempty"`
59+
}
60+
61+
// GetConditions returns the observations of the operational state of the HCloudMachine resource.
62+
func (r *HCloudMachineTemplate) GetConditions() clusterv1.Conditions {
63+
return r.Status.Conditions
64+
}
65+
66+
// SetConditions sets the underlying service state of the HCloudMachine to the predescribed clusterv1.Conditions.
67+
func (r *HCloudMachineTemplate) SetConditions(conditions clusterv1.Conditions) {
68+
r.Status.Conditions = conditions
4369
}
4470

4571
//+kubebuilder:object:root=true

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/infrastructure.cluster.x-k8s.io_hcloudmachinetemplates.yaml

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,69 @@ spec:
155155
required:
156156
- template
157157
type: object
158+
status:
159+
description: HCloudMachineTemplateStatus defines the observed state of
160+
HCloudMachineTemplate.
161+
properties:
162+
capacity:
163+
additionalProperties:
164+
anyOf:
165+
- type: integer
166+
- type: string
167+
pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
168+
x-kubernetes-int-or-string: true
169+
description: 'Capacity defines the resource capacity for this machine.
170+
This value is used for autoscaling from zero operations as defined
171+
in: https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20210310-opt-in-autoscaling-from-zero.md'
172+
type: object
173+
conditions:
174+
description: Conditions defines current service state of the HCloudMachineTemplate.
175+
items:
176+
description: Condition defines an observation of a Cluster API resource
177+
operational state.
178+
properties:
179+
lastTransitionTime:
180+
description: Last time the condition transitioned from one status
181+
to another. This should be when the underlying condition changed.
182+
If that is not known, then using the time when the API field
183+
changed is acceptable.
184+
format: date-time
185+
type: string
186+
message:
187+
description: A human readable message indicating details about
188+
the transition. This field may be empty.
189+
type: string
190+
reason:
191+
description: The reason for the condition's last transition
192+
in CamelCase. The specific API may choose whether or not this
193+
field is considered a guaranteed API. This field may not be
194+
empty.
195+
type: string
196+
severity:
197+
description: Severity provides an explicit classification of
198+
Reason code, so the users or machines can immediately understand
199+
the current situation and act accordingly. The Severity field
200+
MUST be set only when Status=False.
201+
type: string
202+
status:
203+
description: Status of the condition, one of True, False, Unknown.
204+
type: string
205+
type:
206+
description: Type of condition in CamelCase or in foo.example.com/CamelCase.
207+
Many .condition.type values are consistent across resources
208+
like Available, but because arbitrary conditions can be useful
209+
(see .node.status.conditions), the ability to deconflict is
210+
important.
211+
type: string
212+
required:
213+
- lastTransitionTime
214+
- status
215+
- type
216+
type: object
217+
type: array
218+
type: object
158219
type: object
159220
served: true
160221
storage: true
161-
subresources: {}
222+
subresources:
223+
status: {}

config/rbac/role.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,26 @@ rules:
9393
- get
9494
- patch
9595
- update
96+
- apiGroups:
97+
- infrastructure.cluster.x-k8s.io
98+
resources:
99+
- hcloudmachinetemplates
100+
verbs:
101+
- create
102+
- delete
103+
- get
104+
- list
105+
- patch
106+
- update
107+
- watch
108+
- apiGroups:
109+
- infrastructure.cluster.x-k8s.io
110+
resources:
111+
- hcloudmachinetemplates/status
112+
verbs:
113+
- get
114+
- patch
115+
- update
96116
- apiGroups:
97117
- infrastructure.cluster.x-k8s.io
98118
resources:

controllers/controllers_suite_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ var _ = BeforeSuite(func() {
8484
WatchFilterValue: "",
8585
}).SetupWithManager(ctx, testEnv.Manager, controller.Options{})).To(Succeed())
8686

87+
Expect((&HCloudMachineTemplateReconciler{
88+
Client: testEnv.Manager.GetClient(),
89+
APIReader: testEnv.Manager.GetAPIReader(),
90+
HCloudClientFactory: testEnv.HCloudClientFactory,
91+
WatchFilterValue: "",
92+
}).SetupWithManager(ctx, testEnv.Manager, controller.Options{})).To(Succeed())
93+
8794
Expect((&HetznerBareMetalHostReconciler{
8895
Client: testEnv.Manager.GetClient(),
8996
APIReader: testEnv.Manager.GetAPIReader(),
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package controllers
18+
19+
import (
20+
"context"
21+
"time"
22+
23+
"github.com/pkg/errors"
24+
infrav1 "github.com/syself/cluster-api-provider-hetzner/api/v1beta1"
25+
"github.com/syself/cluster-api-provider-hetzner/pkg/scope"
26+
secretutil "github.com/syself/cluster-api-provider-hetzner/pkg/secrets"
27+
hcloudclient "github.com/syself/cluster-api-provider-hetzner/pkg/services/hcloud/client"
28+
"github.com/syself/cluster-api-provider-hetzner/pkg/services/hcloud/machinetemplate"
29+
"sigs.k8s.io/cluster-api/util"
30+
ctrl "sigs.k8s.io/controller-runtime"
31+
"sigs.k8s.io/controller-runtime/pkg/client"
32+
"sigs.k8s.io/controller-runtime/pkg/controller"
33+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
34+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
35+
)
36+
37+
// HCloudMachineTemplateReconciler reconciles a HCloudMachineTemplate object.
38+
type HCloudMachineTemplateReconciler struct {
39+
client.Client
40+
APIReader client.Reader
41+
HCloudClientFactory hcloudclient.Factory
42+
WatchFilterValue string
43+
}
44+
45+
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=hcloudmachinetemplates,verbs=get;list;watch;create;update;patch;delete
46+
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=hcloudmachinetemplates/status,verbs=get;update;patch
47+
48+
// Reconcile manages the lifecycle of an HCloudMachineTemplate object.
49+
func (r *HCloudMachineTemplateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ ctrl.Result, reterr error) {
50+
log := ctrl.LoggerFrom(ctx).WithValues("hcloudmachinetemplate", req.NamespacedName)
51+
log.Info("Reconcile HCloudMachineTemplate")
52+
53+
machineTemplate := &infrav1.HCloudMachineTemplate{}
54+
if err := r.Get(ctx, req.NamespacedName, machineTemplate); err != nil {
55+
log.Error(err, "unable to fetch HCloudMachineTemplate")
56+
return ctrl.Result{}, client.IgnoreNotFound(err)
57+
}
58+
59+
// Fetch the Cluster.
60+
cluster, err := util.GetClusterFromMetadata(ctx, r.Client, machineTemplate.ObjectMeta)
61+
if err != nil {
62+
log.Info("Machine is missing cluster label or cluster does not exist")
63+
return ctrl.Result{}, nil
64+
}
65+
66+
log = log.WithValues("cluster", cluster.Name)
67+
68+
hetznerCluster := &infrav1.HetznerCluster{}
69+
70+
hetznerClusterName := client.ObjectKey{
71+
Namespace: machineTemplate.Namespace,
72+
Name: cluster.Spec.InfrastructureRef.Name,
73+
}
74+
if err := r.Client.Get(ctx, hetznerClusterName, hetznerCluster); err != nil {
75+
log.Info("HetznerCluster is not available yet")
76+
return ctrl.Result{}, nil
77+
}
78+
79+
// Create the scope.
80+
secretManager := secretutil.NewSecretManager(log, r.Client, r.APIReader)
81+
hcloudToken, _, err := getAndValidateHCloudToken(ctx, req.Namespace, hetznerCluster, secretManager)
82+
if err != nil {
83+
return hcloudTokenErrorResult(ctx, err, machineTemplate, infrav1.InstanceReadyCondition, r.Client)
84+
}
85+
86+
hcc := r.HCloudClientFactory.NewClient(hcloudToken)
87+
88+
machineTemplateScope, err := scope.NewHCloudMachineTemplateScope(ctx, scope.HCloudMachineTemplateScopeParams{
89+
Client: r.Client,
90+
Logger: &log,
91+
HCloudMachineTemplate: machineTemplate,
92+
HCloudClient: hcc,
93+
})
94+
if err != nil {
95+
return reconcile.Result{}, errors.Errorf("failed to create scope: %+v", err)
96+
}
97+
98+
// Always close the scope when exiting this function so we can persist any HCloudMachine changes.
99+
defer func() {
100+
if err := machineTemplateScope.Close(ctx); err != nil && reterr == nil {
101+
reterr = err
102+
}
103+
}()
104+
105+
// check whether rate limit has been reached and if so, then wait.
106+
if wait := reconcileRateLimit(machineTemplate); wait {
107+
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
108+
}
109+
110+
return r.reconcile(ctx, machineTemplateScope)
111+
}
112+
113+
func (r *HCloudMachineTemplateReconciler) reconcile(ctx context.Context, machineTemplateScope *scope.HCloudMachineTemplateScope) (reconcile.Result, error) {
114+
machineTemplateScope.Info("Reconciling HCloudMachineTemplate")
115+
hcloudMachine := machineTemplateScope.HCloudMachineTemplate
116+
117+
// If the HCloudMachineTemplate doesn't have our finalizer, add it.
118+
controllerutil.AddFinalizer(machineTemplateScope.HCloudMachineTemplate, infrav1.MachineFinalizer)
119+
120+
// Register the finalizer immediately to avoid orphaning HCloud resources on delete
121+
if err := machineTemplateScope.PatchObject(ctx); err != nil {
122+
return ctrl.Result{}, err
123+
}
124+
125+
// reconcile machinetemplate
126+
if result, brk, err := breakReconcile(machinetemplate.NewService(machineTemplateScope).Reconcile(ctx)); brk {
127+
return result, errors.Wrapf(err, "failed to reconcile machinetemplate for HCloudMachineTemplate %s/%s", hcloudMachine.Namespace, hcloudMachine.Name)
128+
}
129+
130+
return reconcile.Result{}, nil
131+
}
132+
133+
func (r *HCloudMachineTemplateReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
134+
return ctrl.NewControllerManagedBy(mgr).
135+
WithOptions(options).
136+
For(&infrav1.HCloudMachineTemplate{}).
137+
Complete(r)
138+
}

0 commit comments

Comments
 (0)