github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/lib/operatorstatus/monitor.go (about)

     1  package operatorstatus
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	configv1 "github.com/openshift/api/config/v1"
     8  	configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
     9  	"github.com/sirupsen/logrus"
    10  	"k8s.io/client-go/discovery"
    11  	"k8s.io/utils/clock"
    12  )
    13  
    14  const (
    15  	// Wait time before we probe next while checking whether cluster
    16  	// operator API is available.
    17  	defaultProbeInterval = 1 * time.Minute
    18  
    19  	// Default size of the notification channel.
    20  	defaultNotificationChannelSize = 64
    21  )
    22  
    23  // NewMonitor returns a new instance of Monitor that can be used to continuously
    24  // update a clusteroperator resource and an instance of Sender that can be used
    25  // to send update notifications to it.
    26  //
    27  // The name of the clusteroperator resource to monitor is specified in name.
    28  func NewMonitor(log *logrus.Logger, discovery discovery.DiscoveryInterface, configClient configv1client.ConfigV1Interface, names ...string) (Monitor, Sender) {
    29  	logger := log.WithField("monitor", "clusteroperator")
    30  	logger.Infof("monitoring the following components %s", names)
    31  
    32  	monitor := &monitor{
    33  		logger:         logger,
    34  		writer:         NewWriter(discovery, configClient),
    35  		notificationCh: make(chan NotificationFunc, defaultNotificationChannelSize),
    36  		names:          names,
    37  	}
    38  
    39  	return monitor, monitor
    40  }
    41  
    42  // Monitor is an interface that wraps the Run method.
    43  //
    44  // Run runs forever, it reads from an underlying notification channel and
    45  // updates an clusteroperator resource accordingly.
    46  // If the specified stop channel is closed the loop must terminate gracefully.
    47  type Monitor interface {
    48  	Run(stopCh <-chan struct{})
    49  }
    50  
    51  // MutatorFunc accepts an existing status object and appropriately mutates it
    52  // to reflect the observed states.
    53  type MutatorFunc func(existing *configv1.ClusterOperatorStatus) (new *configv1.ClusterOperatorStatus)
    54  
    55  // Mutate is a wrapper for MutatorFunc
    56  func (m MutatorFunc) Mutate(existing *configv1.ClusterOperatorStatus) (new *configv1.ClusterOperatorStatus) {
    57  	return m(existing)
    58  }
    59  
    60  // NotificationFunc wraps a notification event. it returns the name of the
    61  // cluster operator object associated and a mutator function that will set the
    62  // new status for the cluster operator object.
    63  type NotificationFunc func() (name string, mutator MutatorFunc)
    64  
    65  // Get is a wrapper for NotificationFunc.
    66  func (n NotificationFunc) Get() (name string, mutator MutatorFunc) {
    67  	return n()
    68  }
    69  
    70  // Sender is an interface that wraps the Send method.
    71  //
    72  // Send can be used to send notification(s) to the underlying monitor. Send is a
    73  // non-blocking operation.
    74  // If the underlying monitor is not ready to receive the notification will be lost.
    75  // If the notification context specified is nil then it is ignored.
    76  type Sender interface {
    77  	Send(NotificationFunc)
    78  }
    79  
    80  type monitor struct {
    81  	notificationCh chan NotificationFunc
    82  	writer         *Writer
    83  	logger         *logrus.Entry
    84  	names          []string
    85  }
    86  
    87  func (m *monitor) Send(notification NotificationFunc) {
    88  	if notification == nil {
    89  		return
    90  	}
    91  
    92  	select {
    93  	case m.notificationCh <- notification:
    94  	default:
    95  		m.logger.Warn("monitor not ready to receive")
    96  	}
    97  }
    98  
    99  func (m *monitor) Run(stopCh <-chan struct{}) {
   100  	m.logger.Info("starting clusteroperator monitor loop")
   101  	defer func() {
   102  		m.logger.Info("exiting from clusteroperator monitor loop")
   103  	}()
   104  
   105  	// First, we need to ensure that cluster operator API is available.
   106  	// We will keep probing until it is available.
   107  	for {
   108  		exists, err := m.writer.IsAPIAvailable()
   109  		if err != nil {
   110  			m.logger.Infof("ClusterOperator api not present, skipping update (%v)", err)
   111  		}
   112  
   113  		if exists {
   114  			m.logger.Info("ClusterOperator api is present")
   115  			break
   116  		}
   117  
   118  		// Wait before next probe, or quit if parent has asked to do so.
   119  		select {
   120  		case <-time.After(defaultProbeInterval):
   121  		case <-stopCh:
   122  			return
   123  		}
   124  	}
   125  
   126  	// If we are here, cluster operator is available.
   127  	// We are expecting CSV notification which may or may not arrive.
   128  	// Given this, let's write an initial ClusterOperator object with our expectation.
   129  	m.logger.Infof("initializing clusteroperator resource(s) for %s", m.names)
   130  
   131  	for _, name := range m.names {
   132  		for {
   133  			if err := m.init(name); err != nil {
   134  				m.logger.Errorf("initialization error - %v", err)
   135  			} else {
   136  				m.logger.Infof("initialized cluster resource - %s", name)
   137  				break
   138  			}
   139  			select {
   140  			case <-time.After(defaultProbeInterval):
   141  			case <-stopCh:
   142  				return
   143  			}
   144  		}
   145  	}
   146  
   147  	for {
   148  		select {
   149  		case notification := <-m.notificationCh:
   150  			if notification != nil {
   151  				name, mutator := notification.Get()
   152  				if err := m.update(name, mutator); err != nil {
   153  					m.logger.Errorf("status update error - %v", err)
   154  				}
   155  			}
   156  
   157  		case <-stopCh:
   158  			return
   159  		}
   160  	}
   161  }
   162  
   163  func (m *monitor) update(name string, mutator MutatorFunc) error {
   164  	if mutator == nil {
   165  		return fmt.Errorf("no status mutator specified name=%s", name)
   166  	}
   167  
   168  	existing, err := m.writer.EnsureExists(name)
   169  	if err != nil {
   170  		return fmt.Errorf("failed to ensure initial clusteroperator name=%s - %v", name, err)
   171  	}
   172  
   173  	existingStatus := existing.Status.DeepCopy()
   174  	newStatus := mutator.Mutate(existingStatus)
   175  	if err := m.writer.UpdateStatus(existing, newStatus); err != nil {
   176  		return fmt.Errorf("failed to update clusteroperator status name=%s - %v", name, err)
   177  	}
   178  
   179  	return nil
   180  }
   181  
   182  func (m *monitor) init(name string) error {
   183  	existing, err := m.writer.EnsureExists(name)
   184  	if err != nil {
   185  		return fmt.Errorf("failed to ensure name=%s - %v", name, err)
   186  	}
   187  
   188  	if len(existing.Status.Conditions) > 0 {
   189  		return nil
   190  	}
   191  
   192  	// No condition(s) in existing status, let's add conditions that reflect our expectation.
   193  	newStatus := Waiting(&clock.RealClock{}, name)
   194  	if err := m.writer.UpdateStatus(existing, newStatus); err != nil {
   195  		return fmt.Errorf("failed to update status name=%s - %v", name, err)
   196  	}
   197  
   198  	return nil
   199  }
   200  
   201  // Waiting returns an initialized ClusterOperatorStatus object that
   202  // is suited for creation if the given object does not exist already. The
   203  // initialized object has the expected status for cluster operator resource
   204  // before we have seen any corresponding CSV.
   205  func Waiting(clock clock.Clock, name string) *configv1.ClusterOperatorStatus {
   206  	builder := &Builder{
   207  		clock: clock,
   208  	}
   209  
   210  	status := builder.WithDegraded(configv1.ConditionFalse).
   211  		WithAvailable(configv1.ConditionFalse, "", "").
   212  		WithProgressing(configv1.ConditionTrue, fmt.Sprintf("waiting for events - source=%s", name)).
   213  		GetStatus()
   214  
   215  	return status
   216  }