Skip to content

Commit 5bfb272

Browse files
committed
Add status fields to FrontProxy
On-behalf-of: SAP <[email protected]> Signed-off-by: Marvin Beckers <[email protected]>
1 parent baf6844 commit 5bfb272

File tree

6 files changed

+299
-35
lines changed

6 files changed

+299
-35
lines changed

config/crd/bases/operator.kcp.io_frontproxies.yaml

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,17 @@ spec:
1414
singular: frontproxy
1515
scope: Namespaced
1616
versions:
17-
- name: v1alpha1
17+
- additionalPrinterColumns:
18+
- jsonPath: .spec.rootShard.ref.name
19+
name: RootShard
20+
type: string
21+
- jsonPath: .status.phase
22+
name: Phase
23+
type: string
24+
- jsonPath: .metadata.creationTimestamp
25+
name: Age
26+
type: date
27+
name: v1alpha1
1828
schema:
1929
openAPIV3Schema:
2030
description: FrontProxy is the Schema for the frontproxies API
@@ -187,6 +197,68 @@ spec:
187197
type: object
188198
status:
189199
description: FrontProxyStatus defines the observed state of FrontProxy
200+
properties:
201+
conditions:
202+
items:
203+
description: Condition contains details for one aspect of the current
204+
state of this API Resource.
205+
properties:
206+
lastTransitionTime:
207+
description: |-
208+
lastTransitionTime is the last time the condition transitioned from one status to another.
209+
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
210+
format: date-time
211+
type: string
212+
message:
213+
description: |-
214+
message is a human readable message indicating details about the transition.
215+
This may be an empty string.
216+
maxLength: 32768
217+
type: string
218+
observedGeneration:
219+
description: |-
220+
observedGeneration represents the .metadata.generation that the condition was set based upon.
221+
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
222+
with respect to the current state of the instance.
223+
format: int64
224+
minimum: 0
225+
type: integer
226+
reason:
227+
description: |-
228+
reason contains a programmatic identifier indicating the reason for the condition's last transition.
229+
Producers of specific condition types may define expected values and meanings for this field,
230+
and whether the values are considered a guaranteed API.
231+
The value should be a CamelCase string.
232+
This field may not be empty.
233+
maxLength: 1024
234+
minLength: 1
235+
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
236+
type: string
237+
status:
238+
description: status of the condition, one of True, False, Unknown.
239+
enum:
240+
- "True"
241+
- "False"
242+
- Unknown
243+
type: string
244+
type:
245+
description: type of condition in CamelCase or in foo.example.com/CamelCase.
246+
maxLength: 316
247+
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
248+
type: string
249+
required:
250+
- lastTransitionTime
251+
- message
252+
- reason
253+
- status
254+
- type
255+
type: object
256+
type: array
257+
x-kubernetes-list-map-keys:
258+
- type
259+
x-kubernetes-list-type: map
260+
phase:
261+
type: string
190262
type: object
191263
type: object
192264
served: true

internal/controller/frontproxy_controller.go

Lines changed: 131 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,19 @@ import (
2424

2525
appsv1 "k8s.io/api/apps/v1"
2626
corev1 "k8s.io/api/core/v1"
27+
"k8s.io/apimachinery/pkg/api/equality"
28+
apimeta "k8s.io/apimachinery/pkg/api/meta"
2729
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2830
"k8s.io/apimachinery/pkg/runtime"
2931
"k8s.io/apimachinery/pkg/types"
32+
kerrors "k8s.io/apimachinery/pkg/util/errors"
33+
"k8s.io/utils/ptr"
3034
ctrl "sigs.k8s.io/controller-runtime"
3135
"sigs.k8s.io/controller-runtime/pkg/client"
3236
"sigs.k8s.io/controller-runtime/pkg/log"
3337

3438
"github.com/kcp-dev/kcp-operator/internal/reconciling"
39+
"github.com/kcp-dev/kcp-operator/internal/resources"
3540
"github.com/kcp-dev/kcp-operator/internal/resources/frontproxy"
3641
operatorv1alpha1 "github.com/kcp-dev/kcp-operator/sdk/apis/operator/v1alpha1"
3742
)
@@ -59,70 +64,170 @@ func (r *FrontProxyReconciler) SetupWithManager(mgr ctrl.Manager) error {
5964
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
6065
// +kubebuilder:rbac:groups=core,resources=services;configmaps;secrets,verbs=get;list;watch;create;update;patch;delete
6166

62-
func (r *FrontProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
67+
func (r *FrontProxyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, recErr error) {
6368
logger := log.FromContext(ctx)
64-
6569
logger.Info("Reconciling FrontProxy object")
70+
6671
var frontProxy operatorv1alpha1.FrontProxy
6772
if err := r.Client.Get(ctx, req.NamespacedName, &frontProxy); err != nil {
68-
return ctrl.Result{}, fmt.Errorf("failed to find %s/%s: %w", req.Namespace, req.Name, err)
73+
if client.IgnoreNotFound(err) != nil {
74+
return ctrl.Result{}, fmt.Errorf("failed to find %s/%s: %w", req.Namespace, req.Name, err)
75+
}
76+
77+
// Object has apparently been deleted already.
78+
return ctrl.Result{}, nil
6979
}
7080

71-
ownerRefWrapper := k8creconciling.OwnerRefWrapper(*metav1.NewControllerRef(&frontProxy, operatorv1alpha1.SchemeGroupVersion.WithKind("FrontProxy")))
81+
defer func() {
82+
if err := r.reconcileStatus(ctx, &frontProxy); err != nil {
83+
recErr = kerrors.NewAggregate([]error{recErr, err})
84+
}
85+
}()
86+
87+
return ctrl.Result{}, r.reconcile(ctx, &frontProxy)
88+
}
89+
90+
func (r *FrontProxyReconciler) reconcile(ctx context.Context, frontProxy *operatorv1alpha1.FrontProxy) error {
91+
var errs []error
92+
93+
ownerRefWrapper := k8creconciling.OwnerRefWrapper(*metav1.NewControllerRef(frontProxy, operatorv1alpha1.SchemeGroupVersion.WithKind("FrontProxy")))
7294

7395
ref := frontProxy.Spec.RootShard.Reference
7496
rootShard := &operatorv1alpha1.RootShard{}
7597
switch {
7698
case ref != nil:
77-
if err := r.Client.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: req.Namespace}, rootShard); err != nil {
78-
return ctrl.Result{}, fmt.Errorf("referenced RootShard '%s' could not be fetched", ref.Name)
99+
if err := r.Client.Get(ctx, types.NamespacedName{Name: ref.Name, Namespace: frontProxy.Namespace}, rootShard); err != nil {
100+
return fmt.Errorf("referenced RootShard '%s' could not be fetched", ref.Name)
79101
}
80102
default:
81-
return ctrl.Result{}, fmt.Errorf("no valid RootShard in FrontProxy spec defined")
103+
return fmt.Errorf("no valid RootShard in FrontProxy spec defined")
82104
}
83105

84106
configMapReconcilers := []k8creconciling.NamedConfigMapReconcilerFactory{
85-
frontproxy.ConfigmapReconciler(&frontProxy, rootShard),
107+
frontproxy.ConfigmapReconciler(frontProxy, rootShard),
86108
}
87109

88110
secretReconcilers := []k8creconciling.NamedSecretReconcilerFactory{
89-
frontproxy.DynamicKubeconfigSecretReconciler(&frontProxy, rootShard),
111+
frontproxy.DynamicKubeconfigSecretReconciler(frontProxy, rootShard),
90112
}
91113

92114
certReconcilers := []reconciling.NamedCertificateReconcilerFactory{
93-
frontproxy.ServerCertificateReconciler(&frontProxy, rootShard),
94-
frontproxy.KubeconfigReconciler(&frontProxy, rootShard),
95-
frontproxy.AdminKubeconfigReconciler(&frontProxy, rootShard),
96-
frontproxy.RequestHeaderReconciler(&frontProxy, rootShard),
115+
frontproxy.ServerCertificateReconciler(frontProxy, rootShard),
116+
frontproxy.KubeconfigReconciler(frontProxy, rootShard),
117+
frontproxy.AdminKubeconfigReconciler(frontProxy, rootShard),
118+
frontproxy.RequestHeaderReconciler(frontProxy, rootShard),
97119
}
98120

99121
deploymentReconcilers := []k8creconciling.NamedDeploymentReconcilerFactory{
100-
frontproxy.DeploymentReconciler(&frontProxy, rootShard),
122+
frontproxy.DeploymentReconciler(frontProxy, rootShard),
101123
}
102124

103125
serviceReconcilers := []k8creconciling.NamedServiceReconcilerFactory{
104-
frontproxy.ServiceReconciler(&frontProxy),
126+
frontproxy.ServiceReconciler(frontProxy),
127+
}
128+
129+
if err := k8creconciling.ReconcileConfigMaps(ctx, configMapReconcilers, frontProxy.Namespace, r.Client, ownerRefWrapper); err != nil {
130+
errs = append(errs, err)
131+
}
132+
133+
if err := k8creconciling.ReconcileSecrets(ctx, secretReconcilers, frontProxy.Namespace, r.Client, ownerRefWrapper); err != nil {
134+
errs = append(errs, err)
135+
}
136+
137+
if err := reconciling.ReconcileCertificates(ctx, certReconcilers, frontProxy.Namespace, r.Client, ownerRefWrapper); err != nil {
138+
errs = append(errs, err)
139+
}
140+
141+
if err := k8creconciling.ReconcileDeployments(ctx, deploymentReconcilers, frontProxy.Namespace, r.Client, ownerRefWrapper); err != nil {
142+
errs = append(errs, err)
143+
}
144+
145+
if err := k8creconciling.ReconcileServices(ctx, serviceReconcilers, frontProxy.Namespace, r.Client, ownerRefWrapper); err != nil {
146+
errs = append(errs, err)
105147
}
106148

107-
if err := k8creconciling.ReconcileConfigMaps(ctx, configMapReconcilers, req.Namespace, r.Client, ownerRefWrapper); err != nil {
108-
return ctrl.Result{}, err
149+
return kerrors.NewAggregate(errs)
150+
}
151+
152+
func (r *FrontProxyReconciler) reconcileStatus(ctx context.Context, oldFrontProxy *operatorv1alpha1.FrontProxy) error {
153+
frontProxy := oldFrontProxy.DeepCopy()
154+
var errs []error
155+
156+
if frontProxy.Status.Phase == "" {
157+
frontProxy.Status.Phase = operatorv1alpha1.FrontProxyPhaseProvisioning
158+
}
159+
160+
if frontProxy.DeletionTimestamp != nil {
161+
frontProxy.Status.Phase = operatorv1alpha1.FrontProxyPhaseDeleting
109162
}
110163

111-
if err := k8creconciling.ReconcileSecrets(ctx, secretReconcilers, req.Namespace, r.Client, ownerRefWrapper); err != nil {
112-
return ctrl.Result{}, err
164+
if err := r.setAvailableCondition(ctx, frontProxy); err != nil {
165+
errs = append(errs, err)
113166
}
114167

115-
if err := reconciling.ReconcileCertificates(ctx, certReconcilers, req.Namespace, r.Client, ownerRefWrapper); err != nil {
116-
return ctrl.Result{}, err
168+
if cond := apimeta.FindStatusCondition(frontProxy.Status.Conditions, string(operatorv1alpha1.RootShardConditionTypeAvailable)); cond.Status == metav1.ConditionTrue {
169+
frontProxy.Status.Phase = operatorv1alpha1.FrontProxyPhaseRunning
117170
}
118171

119-
if err := k8creconciling.ReconcileDeployments(ctx, deploymentReconcilers, req.Namespace, r.Client, ownerRefWrapper); err != nil {
120-
return ctrl.Result{}, err
172+
// only patch the status if there are actual changes.
173+
if !equality.Semantic.DeepEqual(oldFrontProxy.Status, frontProxy.Status) {
174+
if err := r.Client.Status().Patch(ctx, frontProxy, client.MergeFrom(oldFrontProxy)); err != nil {
175+
errs = append(errs, err)
176+
}
121177
}
122178

123-
if err := k8creconciling.ReconcileServices(ctx, serviceReconcilers, req.Namespace, r.Client, ownerRefWrapper); err != nil {
124-
return ctrl.Result{}, err
179+
return kerrors.NewAggregate(errs)
180+
}
181+
182+
func (r *FrontProxyReconciler) setAvailableCondition(ctx context.Context, frontProxy *operatorv1alpha1.FrontProxy) error {
183+
184+
var dep appsv1.Deployment
185+
depKey := types.NamespacedName{Namespace: frontProxy.Namespace, Name: resources.GetFrontProxyDeploymentName(frontProxy)}
186+
if err := r.Client.Get(ctx, depKey, &dep); client.IgnoreNotFound(err) != nil {
187+
return err
188+
}
189+
190+
available := metav1.ConditionFalse
191+
reason := operatorv1alpha1.FrontProxyConditionReasonDeploymentUnavailable
192+
msg := fmt.Sprintf("Deployment %s", depKey)
193+
194+
if dep.Name != "" {
195+
if dep.Status.UpdatedReplicas == dep.Status.ReadyReplicas && dep.Status.ReadyReplicas == ptr.Deref(dep.Spec.Replicas, 0) {
196+
available = metav1.ConditionTrue
197+
reason = operatorv1alpha1.FrontProxyConditionReasonReplicasUp
198+
msg += " is fully up and running"
199+
} else {
200+
available = metav1.ConditionFalse
201+
reason = operatorv1alpha1.FrontProxyConditionReasonReplicasUnavailable
202+
msg += " is not in desired replica state"
203+
}
204+
} else {
205+
msg += " does not exist"
206+
}
207+
208+
if frontProxy.Status.Conditions == nil {
209+
frontProxy.Status.Conditions = make([]metav1.Condition, 0)
210+
}
211+
212+
cond := apimeta.FindStatusCondition(frontProxy.Status.Conditions, string(operatorv1alpha1.FrontProxyConditionTypeAvailable))
213+
214+
if cond == nil || cond.ObservedGeneration != frontProxy.Generation || cond.Status != available {
215+
transitionTime := metav1.Now()
216+
if cond != nil && cond.Status == available {
217+
// We only need to set LastTransitionTime if we are actually toggling the status
218+
// or if no transition time was set.
219+
transitionTime = cond.LastTransitionTime
220+
}
221+
222+
apimeta.SetStatusCondition(&frontProxy.Status.Conditions, metav1.Condition{
223+
Type: string(operatorv1alpha1.FrontProxyConditionTypeAvailable),
224+
Status: available,
225+
ObservedGeneration: frontProxy.Generation,
226+
LastTransitionTime: transitionTime,
227+
Reason: string(reason),
228+
Message: msg,
229+
})
125230
}
126231

127-
return ctrl.Result{}, nil
232+
return nil
128233
}

sdk/apis/operator/v1alpha1/frontproxy_types.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,41 @@ type ServiceSpec struct {
5151

5252
// FrontProxyStatus defines the observed state of FrontProxy
5353
type FrontProxyStatus struct {
54-
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
55-
// Important: Run "make" to regenerate code after modifying this file
54+
Phase FrontProxyPhase `json:"phase,omitempty"`
55+
56+
// +listType=map
57+
// +listMapKey=type
58+
Conditions []metav1.Condition `json:"conditions,omitempty"`
5659
}
5760

61+
type FrontProxyPhase string
62+
63+
const (
64+
FrontProxyPhaseProvisioning FrontProxyPhase = "Provisioning"
65+
FrontProxyPhaseRunning FrontProxyPhase = "Running"
66+
FrontProxyPhaseDeleting FrontProxyPhase = "Deleting"
67+
)
68+
69+
type FrontProxyConditionType string
70+
71+
const (
72+
FrontProxyConditionTypeAvailable FrontProxyConditionType = "Available"
73+
)
74+
75+
type FrontProxyConditionReason string
76+
77+
const (
78+
FrontProxyConditionReasonDeploymentUnavailable FrontProxyConditionReason = "DeploymentUnavailable"
79+
FrontProxyConditionReasonReplicasUp FrontProxyConditionReason = "ReplicasUp"
80+
FrontProxyConditionReasonReplicasUnavailable FrontProxyConditionReason = "ReplicasUnavailable"
81+
)
82+
5883
// +genclient
5984
// +kubebuilder:object:root=true
6085
// +kubebuilder:subresource:status
86+
// +kubebuilder:printcolumn:JSONPath=".spec.rootShard.ref.name",name="RootShard",type="string"
87+
// +kubebuilder:printcolumn:JSONPath=".status.phase",name="Phase",type="string"
88+
// +kubebuilder:printcolumn:JSONPath=".metadata.creationTimestamp",name="Age",type="date"
6189

6290
// FrontProxy is the Schema for the frontproxies API
6391
type FrontProxy struct {

sdk/applyconfiguration/operator/v1alpha1/frontproxy.go

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

0 commit comments

Comments
 (0)