Skip to content

Commit

Permalink
Add health to status field of duros resource. (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerrit91 authored Sep 15, 2021
1 parent 44f2e0e commit 58c4bbd
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 20 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ ifeq (, $(shell which controller-gen))
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
cd $$CONTROLLER_GEN_TMP_DIR ;\
go mod init tmp ;\
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.5.0 ;\
go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.2 ;\
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
}
CONTROLLER_GEN=$(GOBIN)/controller-gen
Expand Down
27 changes: 27 additions & 0 deletions api/v1/duros_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
// +kubebuilder:object:root=true
// +kubebuilder:printcolumn:name="ProjectID",type=string,JSONPath=`.spec.metalProjectID`
// +kubebuilder:printcolumn:name="StorageClasses",type=string,JSONPath=`.spec.storageClasses`
// +kubebuilder:subresource:status
type Duros struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Expand Down Expand Up @@ -57,8 +58,34 @@ type DurosStatus struct {
// SecretRef to the create JWT Token
// TODO, this can be used to detect required key rotation
SecretRef string `json:"secret,omitempty" description:"Reference to JWT Token generated on the duros storage side for this project"`

// ManagedResourceStatuses contains a list of statuses of resources managed by this controller
ManagedResourceStatuses []ManagedResourceStatus `json:"managedResourceStatuses" description:"A list of managed resource statuses"`
}

type ManagedResourceStatus struct {
// Name is the name of the resource described by this status
Name string `json:"name" description:"The name of the resource"`
// Group is the api group kind of the resource described by this status
Group string `json:"group" description:"The group kind of the resource"`
// State is the actual state of the managed resource
State HealthState `json:"state" description:"The state of this resource"`
// Description further describes the state of the managed resource
Description string `json:"description" description:"The description of the state of this component"`
// LastUpdateTime is the last time the status was updated
LastUpdateTime metav1.Time `json:"lastUpdateTime" description:"The time when this status was last updated"`
}

// HealthState describes the state of a managed resource
type HealthState string

const (
// HealthStateRunning indicates that the resource is running
HealthStateRunning HealthState = "Running"
// HealthStateNotRunning indicates that the resource is not running
HealthStateNotRunning HealthState = "Not Running"
)

// StorageClass defines the storageClass parameters
type StorageClass struct {
Name string `json:"name"`
Expand Down
26 changes: 24 additions & 2 deletions api/v1/zz_generated.deepcopy.go

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

40 changes: 38 additions & 2 deletions config/crd/bases/storage.metal-stack.io_duros.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.4.0
controller-gen.kubebuilder.io/version: v0.6.2
creationTimestamp: null
name: duros.storage.metal-stack.io
spec:
Expand Down Expand Up @@ -68,15 +68,51 @@ spec:
status:
description: DurosStatus defines the observed state of Duros
properties:
managedResourceStatuses:
description: ManagedResourceStatuses contains a list of statuses of
resources managed by this controller
items:
properties:
description:
description: Description further describes the state of the
managed resource
type: string
group:
description: Group is the api group kind of the resource described
by this status
type: string
lastUpdateTime:
description: LastUpdateTime is the last time the status was
updated
format: date-time
type: string
name:
description: Name is the name of the resource described by this
status
type: string
state:
description: State is the actual state of the managed resource
type: string
required:
- description
- group
- lastUpdateTime
- name
- state
type: object
type: array
secret:
description: SecretRef to the create JWT Token TODO, this can be used
to detect required key rotation
type: string
required:
- managedResourceStatuses
type: object
type: object
served: true
storage: true
subresources: {}
subresources:
status: {}
status:
acceptedNames:
kind: ""
Expand Down
91 changes: 84 additions & 7 deletions controllers/duros_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,26 @@ import (

"github.com/go-logr/logr"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/predicate"

"github.com/metal-stack/duros-go"
durosv2 "github.com/metal-stack/duros-go/api/duros/v2"

storagev1 "github.com/metal-stack/duros-controller/api/v1"
v1 "github.com/metal-stack/duros-controller/api/v1"

appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// DurosReconciler reconciles a Duros object
type DurosReconciler struct {
client.Client
Shoot client.Client
Log logr.Logger
Scheme *runtime.Scheme
Namespace string
DurosClient durosv2.DurosAPIClient
Endpoints duros.EPs
Expand All @@ -66,12 +69,13 @@ func (r *DurosReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
return ctrl.Result{}, nil
}
// first get the metal-api projectID
var duros storagev1.Duros
if err := r.Get(ctx, req.NamespacedName, &duros); err != nil {
duros := &storagev1.Duros{}
if err := r.Get(ctx, req.NamespacedName, duros); err != nil {
if apierrors.IsNotFound(err) {
log.Info("no duros storage defined")
return requeue, err
return ctrl.Result{}, nil
}
return requeue, err
}
err := validateDuros(duros)
if err != nil {
Expand Down Expand Up @@ -102,17 +106,90 @@ func (r *DurosReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
if err != nil {
return requeue, err
}
return ctrl.Result{}, nil

err = r.reconcileStatus(ctx, duros)
if err != nil {
return requeue, err
}

return ctrl.Result{
// we requeue in a small interval to ensure resources are recreated quickly
// and status is updated regularly
RequeueAfter: 30 * time.Second,
}, nil
}

func (r *DurosReconciler) reconcileStatus(ctx context.Context, duros *storagev1.Duros) error {
var (
updateTime = metav1.NewTime(time.Now())
ds = &appsv1.DaemonSet{}
sts = &appsv1.StatefulSet{}
)

duros.Status.SecretRef = "" // TODO?

err := r.Shoot.Get(ctx, types.NamespacedName{Name: lbCSINodeName, Namespace: namespace}, ds)
if err != nil {
return fmt.Errorf("error getting daemon set: %w", err)
}

dsStatus := v1.ManagedResourceStatus{
Name: ds.Name,
Group: "DaemonSet", // ds.GetObjectKind().GroupVersionKind().String() --> this does not work :(
State: v1.HealthStateRunning,
Description: "All replicas are ready",
LastUpdateTime: updateTime,
}

if ds.Status.DesiredNumberScheduled != ds.Status.NumberReady {
dsStatus.State = v1.HealthStateNotRunning
dsStatus.Description = fmt.Sprintf("%d/%d replicas are ready", ds.Status.NumberReady, ds.Status.DesiredNumberScheduled)
}

err = r.Shoot.Get(ctx, types.NamespacedName{Name: lbCSIControllerName, Namespace: namespace}, sts)
if err != nil {
return fmt.Errorf("error getting statefulset: %w", err)
}

stsStatus := v1.ManagedResourceStatus{
Name: sts.Name,
Group: "StatefulSet", // sts.GetObjectKind().GroupVersionKind().String() --> this does not work :(
State: v1.HealthStateRunning,
Description: "All replicas are ready",
LastUpdateTime: updateTime,
}

replicas := int32(1)
if sts.Spec.Replicas != nil {
replicas = *sts.Spec.Replicas
}

if replicas != sts.Status.ReadyReplicas {
stsStatus.State = v1.HealthStateNotRunning
stsStatus.Description = fmt.Sprintf("%d/%d replicas are ready", sts.Status.ReadyReplicas, replicas)
}

duros.Status.ManagedResourceStatuses = []v1.ManagedResourceStatus{dsStatus, stsStatus}
err = r.Status().Update(ctx, duros)
if err != nil {
return fmt.Errorf("error updating status: %w", err)
}

r.Log.Info("status updated", "name", duros.Name)

return nil
}

// SetupWithManager boilerplate to setup the Reconciler
func (r *DurosReconciler) SetupWithManager(mgr ctrl.Manager) error {
pred := predicate.GenerationChangedPredicate{} // prevents reconcile on status sub resource update
return ctrl.NewControllerManagedBy(mgr).
For(&storagev1.Duros{}).
WithEventFilter(pred).
Complete(r)
}

func validateDuros(duros v1.Duros) error {
func validateDuros(duros *v1.Duros) error {
if len(duros.Spec.MetalProjectID) == 0 {
return fmt.Errorf("metalProjectID is empty")
}
Expand Down
9 changes: 6 additions & 3 deletions controllers/storageclass.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const (
provisioner = "csi.lightbitslabs.com"

storageClassCredentialsRef = "lb-csi-creds"

lbCSIControllerName = "lb-csi-controller"
lbCSINodeName = "lb-csi-node"
)

var (
Expand Down Expand Up @@ -677,9 +680,9 @@ var (
}

// Node DaemonSet
nodeRoleLabels = map[string]string{"app": "lb-csi-node", "role": "node"}
nodeRoleLabels = map[string]string{"app": lbCSINodeName, "role": "node"}
csiNodeDaemonSet = apps.DaemonSet{
ObjectMeta: metav1.ObjectMeta{Name: "lb-csi-node", Namespace: namespace},
ObjectMeta: metav1.ObjectMeta{Name: lbCSINodeName, Namespace: namespace},
Spec: apps.DaemonSetSpec{
Selector: &metav1.LabelSelector{MatchLabels: nodeRoleLabels},
UpdateStrategy: apps.DaemonSetUpdateStrategy{
Expand Down Expand Up @@ -846,7 +849,7 @@ func (r *DurosReconciler) deployStorageClass(ctx context.Context, projectID stri
log.Info("clusterrolebindinding", "name", crb.Name, "operation", op)
}

sts := &apps.StatefulSet{ObjectMeta: metav1.ObjectMeta{Name: "lb-csi-controller", Namespace: namespace}}
sts := &apps.StatefulSet{ObjectMeta: metav1.ObjectMeta{Name: lbCSIControllerName, Namespace: namespace}}
op, err := controllerutil.CreateOrUpdate(ctx, r.Shoot, sts, func() error {

controllerRoleLabels := map[string]string{"app": "lb-csi-plugin", "role": "controller"}
Expand Down
9 changes: 4 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
v2 "github.com/metal-stack/duros-go/api/duros/v2"
"github.com/metal-stack/v"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
Expand All @@ -45,9 +46,8 @@ var (
)

func init() {
_ = clientgoscheme.AddToScheme(scheme)

_ = storagev1.AddToScheme(scheme)
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(storagev1.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme
}

Expand Down Expand Up @@ -110,7 +110,7 @@ func main() {
setupLog.Error(err, "unable to create shoot restconfig")
os.Exit(1)
}
shootClient, err = client.New(shootRestConfig, client.Options{})
shootClient, err = client.New(shootRestConfig, client.Options{Scheme: scheme})
if err != nil {
setupLog.Error(err, "unable to create shoot client")
os.Exit(1)
Expand Down Expand Up @@ -193,7 +193,6 @@ func main() {
Client: mgr.GetClient(),
Shoot: shootClient,
Log: ctrl.Log.WithName("controllers").WithName("LightBits"),
Scheme: mgr.GetScheme(),
Namespace: namespace,
DurosClient: durosClient,
Endpoints: durosEPs,
Expand Down

0 comments on commit 58c4bbd

Please sign in to comment.