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 }