ARO-RP/pkg/util/ready/ready.go

269 строки
7.5 KiB
Go

package ready
// Copyright (c) Microsoft Corporation.
// Licensed under the Apache License 2.0.
import (
"context"
"fmt"
"net"
mcv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
mcoclientv1 "github.com/openshift/machine-config-operator/pkg/generated/clientset/versioned/typed/machineconfiguration.openshift.io/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
appsv1client "k8s.io/client-go/kubernetes/typed/apps/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
)
// NodeIsReady returns true if a Node is considered ready
func NodeIsReady(node *corev1.Node) bool {
for _, c := range node.Status.Conditions {
if c.Type == corev1.NodeReady &&
c.Status == corev1.ConditionTrue {
return true
}
}
return false
}
// DaemonSetIsReady returns true if a DaemonSet is considered ready
func DaemonSetIsReady(ds *appsv1.DaemonSet) bool {
return ds.Status.DesiredNumberScheduled == ds.Status.NumberAvailable &&
ds.Status.DesiredNumberScheduled == ds.Status.UpdatedNumberScheduled &&
ds.Status.CurrentNumberScheduled == ds.Status.NumberReady &&
ds.Status.CurrentNumberScheduled > 0 &&
ds.Generation == ds.Status.ObservedGeneration
}
// ServiceIsReady returns true if a Service is considered ready
func ServiceIsReady(svc *corev1.Service) bool {
switch svc.Spec.Type {
case corev1.ServiceTypeLoadBalancer:
if len(svc.Status.LoadBalancer.Ingress) <= 0 {
return false
}
case corev1.ServiceTypeClusterIP:
if net.ParseIP(svc.Spec.ClusterIP) == nil {
return false
}
default:
return false
}
return true
}
// CheckDaemonSetIsReady returns a function which polls a DaemonSet and returns
// its readiness
func CheckDaemonSetIsReady(ctx context.Context, cli appsv1client.DaemonSetInterface, name string) func() (bool, error) {
return func() (bool, error) {
ds, err := cli.Get(ctx, name, metav1.GetOptions{})
switch {
case kerrors.IsNotFound(err):
return false, nil
case err != nil:
return false, err
}
return DaemonSetIsReady(ds), nil
}
}
// DeploymentIsReady returns true if a Deployment is considered ready
func DeploymentIsReady(d *appsv1.Deployment) bool {
specReplicas := int32(1)
if d.Spec.Replicas != nil {
specReplicas = *d.Spec.Replicas
}
return specReplicas == d.Status.AvailableReplicas &&
specReplicas == d.Status.UpdatedReplicas &&
specReplicas == d.Status.Replicas &&
d.Generation == d.Status.ObservedGeneration
}
// CheckDeploymentIsReady returns a function which polls a Deployment and
// returns its readiness
func CheckDeploymentIsReady(ctx context.Context, cli appsv1client.DeploymentInterface, name string) func() (bool, error) {
return func() (bool, error) {
d, err := cli.Get(ctx, name, metav1.GetOptions{})
switch {
case kerrors.IsNotFound(err):
return false, nil
case err != nil:
return false, err
}
return DeploymentIsReady(d), nil
}
}
// PodIsRunning returns true if a Pod is running
func PodIsRunning(p *corev1.Pod) bool {
return p.Status.Phase == corev1.PodRunning
}
// CheckPodIsRunning returns a function which polls a Pod and returns if it is
// running
func CheckPodIsRunning(ctx context.Context, cli corev1client.PodInterface, name string) func() (bool, error) {
return func() (bool, error) {
p, err := cli.Get(ctx, name, metav1.GetOptions{})
switch {
case kerrors.IsNotFound(err):
return false, nil
case err != nil:
return false, err
}
return PodIsRunning(p), nil
}
}
// CheckPodsAreRunning returns a function which polls multiple Pods by label and returns if it is
// running
func CheckPodsAreRunning(ctx context.Context, cli corev1client.PodInterface, labels map[string]string) func() (bool, error) {
return func() (bool, error) {
// build the label selector
options := metav1.ListOptions{
LabelSelector: "",
}
for key, value := range labels {
options.LabelSelector += key + "=" + value
}
// get list of pods
podList, err := cli.List(ctx, options)
// check status from List
switch {
case kerrors.IsNotFound(err):
return false, nil
case err != nil:
return false, err
}
// Test if each pod is running
// return true if all pods are running
// else return false
for _, p := range podList.Items {
if !PodIsRunning(&p) {
return false, nil
}
}
// didn't fail any tests, return true
return true, nil
}
}
// StatefulSetIsReady returns true if a StatefulSet is considered ready
func StatefulSetIsReady(s *appsv1.StatefulSet) bool {
specReplicas := int32(1)
if s.Spec.Replicas != nil {
specReplicas = *s.Spec.Replicas
}
return specReplicas == s.Status.ReadyReplicas &&
specReplicas == s.Status.UpdatedReplicas &&
s.Generation == s.Status.ObservedGeneration
}
// MachineConfigPoolIsReady returns true if a MachineConfigPool is considered
// ready
func MachineConfigPoolIsReady(s *mcv1.MachineConfigPool) bool {
return s.Status.MachineCount == s.Status.UpdatedMachineCount &&
s.Status.MachineCount == s.Status.ReadyMachineCount &&
s.Generation == s.Status.ObservedGeneration
}
// CheckMachineConfigPoolIsReady returns a function which polls a
// MachineConfigPool and returns its readiness
func CheckMachineConfigPoolIsReady(ctx context.Context, cli mcoclientv1.MachineConfigPoolInterface, name string) func() (bool, error) {
return func() (bool, error) {
s, err := cli.Get(ctx, name, metav1.GetOptions{})
switch {
case kerrors.IsNotFound(err):
return false, nil
case err != nil:
return false, err
}
return MachineConfigPoolIsReady(s), nil
}
}
type MCPLister interface {
List(ctx context.Context, opts metav1.ListOptions) (*mcv1.MachineConfigPoolList, error)
}
type NodeLister interface {
List(ctx context.Context, opts metav1.ListOptions) (*corev1.NodeList, error)
}
// SameNumberOfNodesAndMachines returns true if the cluster has the same number of nodes and total machines,
// and an error if any. Otherwise it returns false and nil.
func SameNumberOfNodesAndMachines(ctx context.Context, mcpLister MCPLister, nodeLister NodeLister) (bool, error) {
if mcpLister == nil {
return false, fmt.Errorf("mcpLister is nil")
}
if nodeLister == nil {
return false, fmt.Errorf("nodeLister is nil")
}
machineConfigPoolList, err := mcpLister.List(ctx, metav1.ListOptions{})
if err != nil {
return false, err
}
totalMachines, err := TotalMachinesInTheMCPs(machineConfigPoolList.Items)
if err != nil {
return false, err
}
nodes, err := nodeLister.List(ctx, metav1.ListOptions{})
if err != nil {
return false, err
}
nNodes := len(nodes.Items)
if nNodes != totalMachines {
return false, fmt.Errorf("cluster has %d nodes but %d under MCPs, not removing private DNS zone", nNodes, totalMachines)
}
return true, nil
}
// TotalMachinesInTheMCPs returns the total number of machines in the machineConfigPools
// and an error, if any.
func TotalMachinesInTheMCPs(machineConfigPools []mcv1.MachineConfigPool) (int, error) {
totalMachines := 0
for _, mcp := range machineConfigPools {
if !MCPContainsARODNSConfig(mcp) {
return 0, fmt.Errorf("ARO DNS config not found in MCP %s", mcp.Name)
}
if !MachineConfigPoolIsReady(&mcp) {
return 0, fmt.Errorf("MCP %s not ready", mcp.Name)
}
totalMachines += int(mcp.Status.MachineCount)
}
return totalMachines, nil
}
func MCPContainsARODNSConfig(mcp mcv1.MachineConfigPool) bool {
for _, source := range mcp.Status.Configuration.Source {
mcpPrefix := "99-"
mcpSuffix := "-aro-dns"
if source.Name == mcpPrefix+mcp.Name+mcpSuffix {
return true
}
}
return false
}