github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/caasunitprovisioner/application_worker.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package caasunitprovisioner 5 6 import ( 7 "reflect" 8 "strings" 9 10 "github.com/juju/errors" 11 "gopkg.in/juju/names.v2" 12 "gopkg.in/juju/worker.v1" 13 "gopkg.in/juju/worker.v1/catacomb" 14 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/core/status" 17 "github.com/juju/juju/core/watcher" 18 ) 19 20 type applicationWorker struct { 21 catacomb catacomb.Catacomb 22 application string 23 serviceBroker ServiceBroker 24 containerBroker ContainerBroker 25 26 provisioningStatusSetter ProvisioningStatusSetter 27 provisioningInfoGetter ProvisioningInfoGetter 28 applicationGetter ApplicationGetter 29 applicationUpdater ApplicationUpdater 30 unitUpdater UnitUpdater 31 } 32 33 func newApplicationWorker( 34 application string, 35 serviceBroker ServiceBroker, 36 containerBroker ContainerBroker, 37 provisioningStatusSetter ProvisioningStatusSetter, 38 provisioningInfoGetter ProvisioningInfoGetter, 39 applicationGetter ApplicationGetter, 40 applicationUpdater ApplicationUpdater, 41 unitUpdater UnitUpdater, 42 ) (*applicationWorker, error) { 43 w := &applicationWorker{ 44 application: application, 45 serviceBroker: serviceBroker, 46 containerBroker: containerBroker, 47 provisioningStatusSetter: provisioningStatusSetter, 48 provisioningInfoGetter: provisioningInfoGetter, 49 applicationGetter: applicationGetter, 50 applicationUpdater: applicationUpdater, 51 unitUpdater: unitUpdater, 52 } 53 if err := catacomb.Invoke(catacomb.Plan{ 54 Site: &w.catacomb, 55 Work: w.loop, 56 }); err != nil { 57 return nil, errors.Trace(err) 58 } 59 return w, nil 60 } 61 62 // Kill is part of the worker.Worker interface. 63 func (aw *applicationWorker) Kill() { 64 aw.catacomb.Kill(nil) 65 } 66 67 // Wait is part of the worker.Worker interface. 68 func (aw *applicationWorker) Wait() error { 69 return aw.catacomb.Wait() 70 } 71 72 func (aw *applicationWorker) loop() error { 73 deploymentWorker, err := newDeploymentWorker( 74 aw.application, 75 aw.provisioningStatusSetter, 76 aw.serviceBroker, 77 aw.provisioningInfoGetter, 78 aw.applicationGetter, 79 aw.applicationUpdater, 80 ) 81 if err != nil { 82 return errors.Trace(err) 83 } 84 aw.catacomb.Add(deploymentWorker) 85 86 var ( 87 brokerUnitsWatcher watcher.NotifyWatcher 88 appOperatorWatcher watcher.NotifyWatcher 89 ) 90 // The caas watcher can just die from underneath hence it needs to be 91 // restarted all the time. So we don't abuse the catacomb by adding new 92 // workers unbounded, use a defer to stop the running worker. 93 defer func() { 94 if brokerUnitsWatcher != nil { 95 worker.Stop(brokerUnitsWatcher) 96 } 97 if appOperatorWatcher != nil { 98 worker.Stop(appOperatorWatcher) 99 } 100 }() 101 102 // Cache the last reported status information 103 // so we only report true changes. 104 lastReportedStatus := make(map[string]status.StatusInfo) 105 106 for { 107 // The caas watcher can just die from underneath so recreate if needed. 108 if brokerUnitsWatcher == nil { 109 brokerUnitsWatcher, err = aw.containerBroker.WatchUnits(aw.application) 110 if err != nil { 111 if strings.Contains(err.Error(), "unexpected EOF") { 112 logger.Warningf("k8s cloud hosting %q has disappeared", aw.application) 113 return nil 114 } 115 return errors.Annotatef(err, "failed to start unit watcher for %q", aw.application) 116 } 117 } 118 if appOperatorWatcher == nil { 119 appOperatorWatcher, err = aw.containerBroker.WatchOperator(aw.application) 120 if err != nil { 121 if strings.Contains(err.Error(), "unexpected EOF") { 122 logger.Warningf("k8s cloud hosting %q has disappeared", aw.application) 123 return nil 124 } 125 return errors.Annotatef(err, "failed to start operator watcher for %q", aw.application) 126 } 127 } 128 129 select { 130 // We must handle any processing due to application being removed prior 131 // to shutdown so that we don't leave stuff running in the cloud. 132 case <-aw.catacomb.Dying(): 133 return aw.catacomb.ErrDying() 134 case _, ok := <-brokerUnitsWatcher.Changes(): 135 logger.Debugf("units changed: %#v", ok) 136 if !ok { 137 logger.Debugf("%v", brokerUnitsWatcher.Wait()) 138 worker.Stop(brokerUnitsWatcher) 139 brokerUnitsWatcher = nil 140 continue 141 } 142 units, err := aw.containerBroker.Units(aw.application) 143 if err != nil { 144 return errors.Trace(err) 145 } 146 logger.Debugf("units for %v: %+v", aw.application, units) 147 args := params.UpdateApplicationUnits{ 148 ApplicationTag: names.NewApplicationTag(aw.application).String(), 149 } 150 for _, u := range units { 151 // For pods managed by the substrate, any marked as dying 152 // are treated as non-existing. 153 if u.Dying { 154 continue 155 } 156 unitStatus := u.Status 157 lastStatus, ok := lastReportedStatus[u.Id] 158 lastReportedStatus[u.Id] = unitStatus 159 if ok { 160 // If we've seen the same status value previously, 161 // report as unknown as this value is ignored. 162 if reflect.DeepEqual(lastStatus, unitStatus) { 163 unitStatus = status.StatusInfo{ 164 Status: status.Unknown, 165 } 166 } 167 } 168 unitParams := params.ApplicationUnitParams{ 169 ProviderId: u.Id, 170 Address: u.Address, 171 Ports: u.Ports, 172 Status: unitStatus.Status.String(), 173 Info: unitStatus.Message, 174 Data: unitStatus.Data, 175 } 176 // Fill in any filesystem info for volumes attached to the unit. 177 // A unit will not become active until all required volumes are 178 // provisioned, so it makes sense to send this information along 179 // with the units to which they are attached. 180 for _, info := range u.FilesystemInfo { 181 unitParams.FilesystemInfo = append(unitParams.FilesystemInfo, params.KubernetesFilesystemInfo{ 182 StorageName: info.StorageName, 183 FilesystemId: info.FilesystemId, 184 Size: info.Size, 185 MountPoint: info.MountPoint, 186 ReadOnly: info.ReadOnly, 187 Status: info.Status.Status.String(), 188 Info: info.Status.Message, 189 Data: info.Status.Data, 190 Volume: params.KubernetesVolumeInfo{ 191 VolumeId: info.Volume.VolumeId, 192 Size: info.Volume.Size, 193 Persistent: info.Volume.Persistent, 194 Status: info.Volume.Status.Status.String(), 195 Info: info.Volume.Status.Message, 196 Data: info.Volume.Status.Data, 197 }, 198 }) 199 200 } 201 args.Units = append(args.Units, unitParams) 202 } 203 if err := aw.unitUpdater.UpdateUnits(args); err != nil { 204 // We can ignore not found errors as the worker will get stopped anyway. 205 if !errors.IsNotFound(err) { 206 return errors.Trace(err) 207 } 208 } 209 case _, ok := <-appOperatorWatcher.Changes(): 210 if !ok { 211 logger.Debugf("%v", appOperatorWatcher.Wait()) 212 worker.Stop(appOperatorWatcher) 213 appOperatorWatcher = nil 214 continue 215 } 216 logger.Debugf("operator update for %v", aw.application) 217 operator, err := aw.containerBroker.Operator(aw.application) 218 if errors.IsNotFound(err) { 219 logger.Debugf("pod not found for application %q", aw.application) 220 if err := aw.provisioningStatusSetter.SetOperatorStatus(aw.application, status.Terminated, "", nil); err != nil { 221 return errors.Trace(err) 222 } 223 } else if err != nil { 224 return errors.Trace(err) 225 } else { 226 if err := aw.provisioningStatusSetter.SetOperatorStatus(aw.application, operator.Status.Status, operator.Status.Message, operator.Status.Data); err != nil { 227 return errors.Trace(err) 228 } 229 } 230 } 231 232 } 233 }