Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(operator): Update workload instance controller, add example #102

Merged
merged 8 commits into from
Oct 4, 2022
34 changes: 34 additions & 0 deletions examples/single-service/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: test
name: test
spec:
replicas: 1
selector:
matchLabels:
app: test
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: test
annotations:
keptn.sh/workload: waiter
keptn.sh/version: "0.2"
keptn.sh/app: waiter
keptn.sh/post-deployment-tasks: pre-deployment-hello

spec:
containers:
- image: busybox
name: busybox
command: ['sh', '-c', 'echo The app is running! && sleep infinity']
initContainers:
- name: init-myservice
image: busybox:1.28
command: ['sh', '-c', 'sleep 30']
status: {}
9 changes: 9 additions & 0 deletions examples/single-service/pre-deployment-tasks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: lifecycle.keptn.sh/v1alpha1
kind: KeptnTaskDefinition
metadata:
name: pre-deployment-hello
spec:
function:
inline:
code: |
console.log("Pre-Deployment Task has been executed");
4 changes: 4 additions & 0 deletions operator/api/v1alpha1/keptnworkloadinstance_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,7 @@ func (i KeptnWorkloadInstance) IsPreDeploymentCompleted() bool {
func (i KeptnWorkloadInstance) IsPostDeploymentCompleted() bool {
return i.Status.PostDeploymentStatus.IsCompleted()
}

func (i KeptnWorkloadInstance) IsDeploymentCompleted() bool {
return i.Status.DeploymentStatus.IsCompleted()
}
1 change: 1 addition & 0 deletions operator/config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ rules:
- events
verbs:
- create
- patch
- watch
- apiGroups:
- ""
Expand Down
116 changes: 22 additions & 94 deletions operator/controllers/keptnworkloadinstance/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ import (

"github.com/go-logr/logr"
"github.com/google/uuid"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
Expand Down Expand Up @@ -63,7 +61,7 @@ type KeptnWorkloadInstanceReconciler struct {
//+kubebuilder:rbac:groups=lifecycle.keptn.sh,resources=keptntasks,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=lifecycle.keptn.sh,resources=keptntasks/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=lifecycle.keptn.sh,resources=keptntasks/finalizers,verbs=update
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;watch
//+kubebuilder:rbac:groups=core,resources=events,verbs=create;watch;patch
//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch
//+kubebuilder:rbac:groups=apps,resources=replicasets;deployments;statefulsets,verbs=get;list;watch

Expand Down Expand Up @@ -91,44 +89,37 @@ func (r *KeptnWorkloadInstanceReconciler) Reconcile(ctx context.Context, req ctr
return reconcile.Result{}, fmt.Errorf("could not fetch KeptnWorkloadInstance: %+v", err)
}

r.Log.Info("Workload Instance found", "instance", workloadInstance)

if workloadInstance.IsPostDeploymentCompleted() {
return reconcile.Result{}, nil
if !workloadInstance.IsPreDeploymentCompleted() {
r.Log.Info("Pre deployment checks not finished")
err := r.reconcilePreDeployment(ctx, req, workloadInstance)
if err != nil {
r.Log.Error(err, "Error reconciling pre-deployment checks")
return ctrl.Result{Requeue: true}, err
}
return ctrl.Result{Requeue: true, RequeueAfter: 5 * time.Second}, nil
}

r.Log.Info("Post deployment checks not finished")

isDeployed, err := r.UpdateWorkloadResourceDeploymentStatus(ctx, workloadInstance)
if err != nil {
return reconcile.Result{}, err
if !workloadInstance.IsDeploymentCompleted() {
r.Log.Info("Deployment not finished")
err := r.reconcileDeployment(ctx, workloadInstance)
if err != nil {
r.Log.Error(err, "Error reconciling deployment")
return ctrl.Result{Requeue: true}, err
}
return ctrl.Result{Requeue: true, RequeueAfter: 10 * time.Second}, nil
}
if isDeployed {

if !workloadInstance.IsPostDeploymentCompleted() {
r.Log.Info("Post deployment checks not finished")
err = r.reconcilePostDeployment(ctx, req, workloadInstance)
if err != nil {
r.Log.Error(err, "Error reconciling post-deployment checks")
return ctrl.Result{}, err
return ctrl.Result{Requeue: true}, err
}
return ctrl.Result{Requeue: true, RequeueAfter: 5 * time.Second}, nil
}

r.Log.Info("Deployment of pods not finished")

if workloadInstance.IsPreDeploymentCompleted() {
r.Log.Info("Post deployment checks not finished")
return ctrl.Result{Requeue: true}, nil
}

r.Log.Info("Pre deployment checks not finished")

err = r.reconcilePreDeployment(ctx, req, workloadInstance)
if err != nil {
r.Log.Error(err, "Error reconciling pre-deployment checks")
return ctrl.Result{}, err
}

return ctrl.Result{Requeue: true, RequeueAfter: 5 * time.Second}, nil

return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
Expand All @@ -143,69 +134,6 @@ func (r *KeptnWorkloadInstanceReconciler) generateSuffix() string {
return uid[:10]
}

func (r *KeptnWorkloadInstanceReconciler) UpdateWorkloadResourceDeploymentStatus(ctx context.Context, workloadInstance *klcv1alpha1.KeptnWorkloadInstance) (bool, error) {
if workloadInstance.Status.DeploymentStatus == common.StateSucceeded {
return true, nil
}
if workloadInstance.Spec.ResourceReference.Kind == "Pod" {
isPodRunning, err := r.IsPodRunning(ctx, workloadInstance.Spec.ResourceReference, workloadInstance.Namespace)
if err != nil {
return isPodRunning, err
}
if isPodRunning {
workloadInstance.Status.DeploymentStatus = common.StateSucceeded
return true, r.Client.Status().Update(ctx, workloadInstance)
}
return false, nil
}
isReplicaRunning, err := r.IsReplicaSetRunning(ctx, workloadInstance.Spec.ResourceReference, workloadInstance.Namespace)
if err != nil {
return isReplicaRunning, err
}
if isReplicaRunning {
workloadInstance.Status.DeploymentStatus = common.StateSucceeded
return true, r.Client.Status().Update(ctx, workloadInstance)
}
return false, nil
}

func (r *KeptnWorkloadInstanceReconciler) IsPodRunning(ctx context.Context, resource klcv1alpha1.ResourceReference, namespace string) (bool, error) {
podList := &corev1.PodList{}
if err := r.Client.List(ctx, podList, client.InNamespace(namespace)); err != nil {
return false, err
}
for _, p := range podList.Items {
if p.UID == resource.UID {
if p.Status.Phase == corev1.PodRunning {
return true, nil
}
return false, nil
}
}
return false, nil
}

func (r *KeptnWorkloadInstanceReconciler) IsReplicaSetRunning(ctx context.Context, resource klcv1alpha1.ResourceReference, namespace string) (bool, error) {
replica := &appsv1.ReplicaSetList{}
if err := r.Client.List(ctx, replica, client.InNamespace(namespace)); err != nil {
return false, err
}
for _, re := range replica.Items {
if re.UID == resource.UID {
replicas, err := r.GetDesiredReplicas(ctx, re.OwnerReferences[0], namespace)
if err != nil {
return false, err
}
if re.Status.ReadyReplicas == replicas {
return true, nil
}
return false, nil
}
}
return false, nil

}

func (r *KeptnWorkloadInstanceReconciler) getTaskStatus(taskName string, instanceStatus []klcv1alpha1.WorkloadTaskStatus) klcv1alpha1.WorkloadTaskStatus {
for _, status := range instanceStatus {
if status.TaskDefinitionName == taskName {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestKeptnWorkloadInstanceReconciler_IsPodRunning(t *testing.T) {
r := &KeptnWorkloadInstanceReconciler{
Client: fake.NewClientBuilder().WithLists(podList).Build(),
}
isPodRunning, err := r.IsPodRunning(context.TODO(), v1alpha1.ResourceReference{UID: types.UID("pod1")}, "node1")
isPodRunning, err := r.isPodRunning(context.TODO(), v1alpha1.ResourceReference{UID: types.UID("pod1")}, "node1")
testrequire.Nil(t, err)
if !isPodRunning {
t.Errorf("Wrong!")
Expand All @@ -29,7 +29,7 @@ func TestKeptnWorkloadInstanceReconciler_IsPodRunning(t *testing.T) {
r2 := &KeptnWorkloadInstanceReconciler{
Client: fake.NewClientBuilder().WithLists(podList2).Build(),
}
isPodRunning, err = r2.IsPodRunning(context.TODO(), v1alpha1.ResourceReference{UID: types.UID("pod1")}, "node1")
isPodRunning, err = r2.isPodRunning(context.TODO(), v1alpha1.ResourceReference{UID: types.UID("pod1")}, "node1")
testrequire.Nil(t, err)
if isPodRunning {
t.Errorf("Wrong!")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package keptnworkloadinstance

import (
"context"
klcv1alpha1 "github.com/keptn-sandbox/lifecycle-controller/operator/api/v1alpha1"
"github.com/keptn-sandbox/lifecycle-controller/operator/api/v1alpha1/common"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func (r *KeptnWorkloadInstanceReconciler) reconcileDeployment(ctx context.Context, workloadInstance *klcv1alpha1.KeptnWorkloadInstance) error {
if workloadInstance.Spec.ResourceReference.Kind == "Pod" {
isPodRunning, err := r.isPodRunning(ctx, workloadInstance.Spec.ResourceReference, workloadInstance.Namespace)
if err != nil {
return err
}
if isPodRunning {
workloadInstance.Status.DeploymentStatus = common.StateSucceeded
}
}

isReplicaRunning, err := r.isReplicaSetRunning(ctx, workloadInstance.Spec.ResourceReference, workloadInstance.Namespace)
if err != nil {
return err
}
if isReplicaRunning {
workloadInstance.Status.DeploymentStatus = common.StateSucceeded
}

err = r.Client.Status().Update(ctx, workloadInstance)
if err != nil {
return err
}
return nil
}

func (r *KeptnWorkloadInstanceReconciler) isReplicaSetRunning(ctx context.Context, resource klcv1alpha1.ResourceReference, namespace string) (bool, error) {
replica := &appsv1.ReplicaSetList{}
if err := r.Client.List(ctx, replica, client.InNamespace(namespace)); err != nil {
return false, err
}
for _, re := range replica.Items {
if re.UID == resource.UID {
replicas, err := r.getDesiredReplicas(ctx, re.OwnerReferences[0], namespace)
if err != nil {
return false, err
}
if re.Status.ReadyReplicas == replicas {
return true, nil
}
return false, nil
}
}
return false, nil

}

func (r *KeptnWorkloadInstanceReconciler) isPodRunning(ctx context.Context, resource klcv1alpha1.ResourceReference, namespace string) (bool, error) {
podList := &corev1.PodList{}
if err := r.Client.List(ctx, podList, client.InNamespace(namespace)); err != nil {
return false, err
}
for _, p := range podList.Items {
if p.UID == resource.UID {
if p.Status.Phase == corev1.PodRunning {
return true, nil
}
return false, nil
}
}
return false, nil
}

func (r *KeptnWorkloadInstanceReconciler) getDesiredReplicas(ctx context.Context, reference v1.OwnerReference, namespace string) (int32, error) {
var replicas *int32
switch reference.Kind {
case "Deployment":
dep := appsv1.Deployment{}
err := r.Client.Get(ctx, types.NamespacedName{Name: reference.Name, Namespace: namespace}, &dep)
if err != nil {
return 0, err
}
replicas = dep.Spec.Replicas
case "StatefulSet":
sts := appsv1.StatefulSet{}
err := r.Client.Get(ctx, types.NamespacedName{Name: reference.Name, Namespace: namespace}, &sts)
if err != nil {
return 0, err
}
replicas = sts.Spec.Replicas
}

return *replicas, nil

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import (
"context"
klcv1alpha1 "github.com/keptn-sandbox/lifecycle-controller/operator/api/v1alpha1"
"github.com/keptn-sandbox/lifecycle-controller/operator/api/v1alpha1/common"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
)

Expand All @@ -26,26 +23,3 @@ func (r *KeptnWorkloadInstanceReconciler) reconcilePostDeployment(ctx context.Co
}
return nil
}

func (r *KeptnWorkloadInstanceReconciler) GetDesiredReplicas(ctx context.Context, reference v1.OwnerReference, namespace string) (int32, error) {
var replicas *int32
switch reference.Kind {
case "Deployment":
dep := appsv1.Deployment{}
err := r.Client.Get(ctx, types.NamespacedName{Name: reference.Name, Namespace: namespace}, &dep)
if err != nil {
return 0, err
}
replicas = dep.Spec.Replicas
case "StatefulSet":
sts := appsv1.StatefulSet{}
err := r.Client.Get(ctx, types.NamespacedName{Name: reference.Name, Namespace: namespace}, &sts)
if err != nil {
return 0, err
}
replicas = sts.Spec.Replicas
}

return *replicas, nil

}