github.com/verrazzano/verrazzano@v1.7.0/pkg/k8s/ready/available.go (about)

     1  // Copyright (c) 2022, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package ready
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    10  	vzapi "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1alpha1"
    11  	appsv1 "k8s.io/api/apps/v1"
    12  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    13  	"k8s.io/apimachinery/pkg/types"
    14  	clipkg "sigs.k8s.io/controller-runtime/pkg/client"
    15  )
    16  
    17  const (
    18  	typeDeployment  = "deployment"
    19  	typeStatefulset = "statefulset"
    20  	typeDaemonset   = "daemonset"
    21  )
    22  
    23  type (
    24  	isObjectAvailableSig func(client clipkg.Client, nsn types.NamespacedName) error
    25  	AvailabilityObjects  struct {
    26  		StatefulsetNames    []types.NamespacedName
    27  		DeploymentNames     []types.NamespacedName
    28  		DeploymentSelectors []clipkg.ListOption
    29  		DaemonsetNames      []types.NamespacedName
    30  	}
    31  )
    32  
    33  func (c *AvailabilityObjects) IsAvailable(log vzlog.VerrazzanoLogger, client clipkg.Client) (string, vzapi.ComponentAvailability) {
    34  	if err := DeploymentsAreAvailable(client, c.DeploymentNames); err != nil {
    35  		return handleNotAvailableError(log, err)
    36  	}
    37  	if err := DeploymentsAreAvailableBySelector(client, c.DeploymentSelectors); err != nil {
    38  		return handleNotAvailableError(log, err)
    39  	}
    40  	if err := StatefulSetsAreAvailable(client, c.StatefulsetNames); err != nil {
    41  		return handleNotAvailableError(log, err)
    42  	}
    43  	if err := DaemonsetsAreAvailable(client, c.DaemonsetNames); err != nil {
    44  		return handleNotAvailableError(log, err)
    45  	}
    46  	return "", vzapi.ComponentAvailable
    47  }
    48  
    49  func handleNotAvailableError(log vzlog.VerrazzanoLogger, err error) (string, vzapi.ComponentAvailability) {
    50  	log.Progressf(err.Error())
    51  	return err.Error(), vzapi.ComponentUnavailable
    52  }
    53  
    54  // DeploymentsAreAvailable a list of deployments is available when the expected replicas is equal to the ready replicas
    55  func DeploymentsAreAvailable(client clipkg.Client, deployments []types.NamespacedName) error {
    56  	return objectsAreAvailable(client, deployments, isDeploymentAvailable)
    57  }
    58  
    59  // StatefulSetsAreAvailable a list of statefulsets is available when the expected replicas is equal to the ready replicas
    60  func StatefulSetsAreAvailable(client clipkg.Client, statefulsets []types.NamespacedName) error {
    61  	return objectsAreAvailable(client, statefulsets, isStatefulsetAvailable)
    62  }
    63  
    64  // DaemonsetsAreAvailable a list of daemonsets is available when the expected replicas is equal to the ready replicas
    65  func DaemonsetsAreAvailable(client clipkg.Client, daemonsets []types.NamespacedName) error {
    66  	return objectsAreAvailable(client, daemonsets, isDaemonsetAvailable)
    67  }
    68  
    69  func DeploymentsAreAvailableBySelector(client clipkg.Client, selectors []clipkg.ListOption) error {
    70  	if len(selectors) < 1 {
    71  		return nil
    72  	}
    73  	deploymentList := &appsv1.DeploymentList{}
    74  	if err := client.List(context.TODO(), deploymentList, selectors...); err != nil {
    75  		return handleListFailure(typeDeployment, err)
    76  	}
    77  	if deploymentList.Items == nil || len(deploymentList.Items) < 1 {
    78  		return handleListNotFound(typeDeployment, selectors)
    79  	}
    80  	for _, deploy := range deploymentList.Items {
    81  		nsn := types.NamespacedName{
    82  			Namespace: deploy.Namespace,
    83  			Name:      deploy.Name,
    84  		}
    85  		if err := handleReplicasNotReady(deploy.Status.ReadyReplicas, deploy.Status.Replicas, nsn, typeDeployment); err != nil {
    86  			return err
    87  		}
    88  	}
    89  	return nil
    90  }
    91  
    92  // StatefulSetsAreAvailableBySelector returns an error if not all pods controlled by statefulsets selected by the selector are ready
    93  func StatefulSetsAreAvailableBySelector(client clipkg.Client, selectors []clipkg.ListOption) error {
    94  	if len(selectors) < 1 {
    95  		return nil
    96  	}
    97  	statefulsetList := &appsv1.StatefulSetList{}
    98  	if err := client.List(context.TODO(), statefulsetList, selectors...); err != nil {
    99  		return handleListFailure(typeStatefulset, err)
   100  	}
   101  	if statefulsetList.Items == nil || len(statefulsetList.Items) < 1 {
   102  		return handleListNotFound(typeStatefulset, selectors)
   103  	}
   104  	for _, sts := range statefulsetList.Items {
   105  		nsn := types.NamespacedName{
   106  			Namespace: sts.Namespace,
   107  			Name:      sts.Name,
   108  		}
   109  		if err := handleReplicasNotReady(sts.Status.ReadyReplicas, sts.Status.Replicas, nsn, typeStatefulset); err != nil {
   110  			return err
   111  		}
   112  	}
   113  	return nil
   114  }
   115  
   116  func objectsAreAvailable(client clipkg.Client, objectKeys []types.NamespacedName, objectAvailableFunc isObjectAvailableSig) error {
   117  	for _, nsn := range objectKeys {
   118  		if err := objectAvailableFunc(client, nsn); err != nil {
   119  			return err
   120  		}
   121  	}
   122  	return nil
   123  }
   124  
   125  func isDeploymentAvailable(client clipkg.Client, nsn types.NamespacedName) error {
   126  	deploy := &appsv1.Deployment{}
   127  	if err := client.Get(context.TODO(), nsn, deploy); err != nil {
   128  		return handleGetError(err, nsn, typeDeployment)
   129  	}
   130  	return handleReplicasNotReady(deploy.Status.ReadyReplicas, deploy.Status.Replicas, nsn, typeDeployment)
   131  }
   132  
   133  func isStatefulsetAvailable(client clipkg.Client, nsn types.NamespacedName) error {
   134  	sts := &appsv1.StatefulSet{}
   135  	if err := client.Get(context.TODO(), nsn, sts); err != nil {
   136  		return handleGetError(err, nsn, typeStatefulset)
   137  	}
   138  	return handleReplicasNotReady(sts.Status.ReadyReplicas, sts.Status.Replicas, nsn, typeStatefulset)
   139  }
   140  
   141  func isDaemonsetAvailable(client clipkg.Client, nsn types.NamespacedName) error {
   142  	ds := &appsv1.DaemonSet{}
   143  	if err := client.Get(context.TODO(), nsn, ds); err != nil {
   144  		return handleGetError(err, nsn, typeDaemonset)
   145  	}
   146  	return handleReplicasNotReady(ds.Status.NumberReady, ds.Status.DesiredNumberScheduled, nsn, typeDaemonset)
   147  }
   148  
   149  func handleGetError(err error, nsn types.NamespacedName, objectType string) error {
   150  	if apierrors.IsNotFound(err) {
   151  		return fmt.Errorf("waiting for %s %v to exist", objectType, nsn)
   152  	}
   153  	return fmt.Errorf("failed getting %s %v: %v", objectType, nsn, err)
   154  }
   155  
   156  func handleReplicasNotReady(ready, expected int32, nsn types.NamespacedName, objectType string) error {
   157  	if expected == 0 {
   158  		// an enabled component should have at least one expected replica
   159  		expected = 1
   160  	}
   161  	if ready != expected {
   162  		return fmt.Errorf("%s %v not available: %d/%d replicas ready", objectType, nsn, ready, expected)
   163  	}
   164  	return nil
   165  }
   166  
   167  func handleListFailure(objectType string, err error) error {
   168  	return fmt.Errorf("failed getting %s: %v", objectType, err)
   169  }
   170  
   171  func handleListNotFound(objectType string, selectors []clipkg.ListOption) error {
   172  	return fmt.Errorf("waiting for %s matching selectors %v to exist", objectType, selectors)
   173  }