github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/agent/storageprovisioner/internal/filesystemwatcher/watchers.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package filesystemwatcher 5 6 import ( 7 "github.com/juju/collections/set" 8 "github.com/juju/errors" 9 "gopkg.in/juju/names.v2" 10 "gopkg.in/tomb.v2" 11 12 "github.com/juju/juju/state" 13 "github.com/juju/juju/state/watcher" 14 ) 15 16 // Watchers provides methods for watching filesystems. The watches aggregate 17 // results from host- and model-scoped watchers, to conform to the behaviour 18 // of the storageprovisioner worker. The model-level storageprovisioner watches 19 // model-scoped filesystems that have no backing volume. The host-level worker 20 // watches both host-scoped filesystems, and model-scoped filesystems whose 21 // backing volumes are attached to the host. 22 type Watchers struct { 23 Backend Backend 24 } 25 26 // WatchModelManagedFilesystems returns a strings watcher that reports 27 // model-scoped filesystems that have no backing volume. Volume-backed 28 // filesystems are always managed by the host to which they are attached. 29 func (fw Watchers) WatchModelManagedFilesystems() state.StringsWatcher { 30 return newFilteredStringsWatcher(fw.Backend.WatchModelFilesystems(), func(id string) (bool, error) { 31 f, err := fw.Backend.Filesystem(names.NewFilesystemTag(id)) 32 if errors.IsNotFound(err) { 33 return false, nil 34 } else if err != nil { 35 return false, errors.Trace(err) 36 } 37 _, err = f.Volume() 38 return err == state.ErrNoBackingVolume, nil 39 }) 40 } 41 42 // WatchUnitManagedFilesystems returns a strings watcher that reports both 43 // unit-scoped filesystems, and model-scoped, volume-backed filesystems 44 // that are attached to units of the specified application. 45 func (fw Watchers) WatchUnitManagedFilesystems(app names.ApplicationTag) state.StringsWatcher { 46 w := &hostFilesystemsWatcher{ 47 stringsWatcherBase: stringsWatcherBase{out: make(chan []string)}, 48 backend: fw.Backend, 49 changes: set.NewStrings(), 50 hostFilesystems: fw.Backend.WatchUnitFilesystems(app), 51 modelFilesystems: fw.Backend.WatchModelFilesystems(), 52 modelVolumeAttachments: fw.Backend.WatchModelVolumeAttachments(), 53 modelVolumesAttached: names.NewSet(), 54 modelVolumeFilesystems: make(map[names.VolumeTag]names.FilesystemTag), 55 hostMatch: func(tag names.Tag) (bool, error) { 56 entity, err := names.UnitApplication(tag.Id()) 57 if err != nil { 58 return false, errors.Trace(err) 59 } 60 return app.Id() == entity, nil 61 }, 62 } 63 w.tomb.Go(func() error { 64 defer watcher.Stop(w.hostFilesystems, &w.tomb) 65 defer watcher.Stop(w.modelFilesystems, &w.tomb) 66 defer watcher.Stop(w.modelVolumeAttachments, &w.tomb) 67 return w.loop() 68 }) 69 return w 70 } 71 72 // WatchMachineManagedFilesystems returns a strings watcher that reports both 73 // machine-scoped filesystems, and model-scoped, volume-backed filesystems 74 // that are attached to the specified machine. 75 func (fw Watchers) WatchMachineManagedFilesystems(m names.MachineTag) state.StringsWatcher { 76 w := &hostFilesystemsWatcher{ 77 stringsWatcherBase: stringsWatcherBase{out: make(chan []string)}, 78 backend: fw.Backend, 79 changes: set.NewStrings(), 80 hostFilesystems: fw.Backend.WatchMachineFilesystems(m), 81 modelFilesystems: fw.Backend.WatchModelFilesystems(), 82 modelVolumeAttachments: fw.Backend.WatchModelVolumeAttachments(), 83 modelVolumesAttached: names.NewSet(), 84 modelVolumeFilesystems: make(map[names.VolumeTag]names.FilesystemTag), 85 hostMatch: func(tag names.Tag) (bool, error) { 86 return tag == m, nil 87 }, 88 } 89 w.tomb.Go(func() error { 90 defer watcher.Stop(w.hostFilesystems, &w.tomb) 91 defer watcher.Stop(w.modelFilesystems, &w.tomb) 92 defer watcher.Stop(w.modelVolumeAttachments, &w.tomb) 93 return w.loop() 94 }) 95 return w 96 } 97 98 // hostFilesystemsWatcher is a strings watcher that reports both 99 // host-scoped filesystems, and model-scoped, volume-backed filesystems 100 // that are attached to the specified host. 101 // 102 // NOTE(axw) we use the existence of the *volume* attachment rather than 103 // filesystem attachment because the filesystem attachment can be destroyed 104 // before the filesystem, but the volume attachment cannot. 105 type hostFilesystemsWatcher struct { 106 stringsWatcherBase 107 changes set.Strings 108 backend Backend 109 hostFilesystems state.StringsWatcher 110 modelFilesystems state.StringsWatcher 111 modelVolumeAttachments state.StringsWatcher 112 modelVolumesAttached names.Set 113 modelVolumeFilesystems map[names.VolumeTag]names.FilesystemTag 114 hostMatch func(names.Tag) (bool, error) 115 } 116 117 func (w *hostFilesystemsWatcher) loop() error { 118 defer close(w.out) 119 var out chan<- []string 120 var hostFilesystemsReceived bool 121 var modelFilesystemsReceived bool 122 var modelVolumeAttachmentsReceived bool 123 var sentFirst bool 124 for { 125 select { 126 case <-w.tomb.Dying(): 127 return tomb.ErrDying 128 case values, ok := <-w.hostFilesystems.Changes(): 129 if !ok { 130 return watcher.EnsureErr(w.hostFilesystems) 131 } 132 hostFilesystemsReceived = true 133 for _, v := range values { 134 w.changes.Add(v) 135 } 136 case values, ok := <-w.modelFilesystems.Changes(): 137 if !ok { 138 return watcher.EnsureErr(w.modelFilesystems) 139 } 140 modelFilesystemsReceived = true 141 for _, id := range values { 142 filesystemTag := names.NewFilesystemTag(id) 143 if err := w.modelFilesystemChanged(filesystemTag); err != nil { 144 return errors.Trace(err) 145 } 146 } 147 case values, ok := <-w.modelVolumeAttachments.Changes(): 148 if !ok { 149 return watcher.EnsureErr(w.modelVolumeAttachments) 150 } 151 modelVolumeAttachmentsReceived = true 152 for _, id := range values { 153 hostTag, volumeTag, err := state.ParseVolumeAttachmentId(id) 154 if err != nil { 155 return errors.Annotate(err, "parsing volume attachment ID") 156 } 157 match, err := w.hostMatch(hostTag) 158 if err != nil { 159 return errors.Annotate(err, "parsing volume host tag") 160 } 161 if !match { 162 continue 163 } 164 if err := w.modelVolumeAttachmentChanged(hostTag, volumeTag); err != nil { 165 return errors.Trace(err) 166 } 167 } 168 case out <- w.changes.SortedValues(): 169 w.changes = set.NewStrings() 170 out = nil 171 } 172 // NOTE(axw) we don't send any changes until we have received 173 // an initial event from each of the watchers. This ensures 174 // that we provide a complete view of the world in the initial 175 // event, which is expected of all watchers. 176 if hostFilesystemsReceived && 177 modelFilesystemsReceived && 178 modelVolumeAttachmentsReceived && 179 (!sentFirst || len(w.changes) > 0) { 180 sentFirst = true 181 out = w.out 182 } 183 } 184 } 185 186 func (w *hostFilesystemsWatcher) modelFilesystemChanged(filesystemTag names.FilesystemTag) error { 187 filesystem, err := w.backend.Filesystem(filesystemTag) 188 if errors.IsNotFound(err) { 189 // Filesystem removed: nothing more to do. 190 return nil 191 } else if err != nil { 192 return errors.Annotate(err, "getting filesystem") 193 } 194 volumeTag, err := filesystem.Volume() 195 if err == state.ErrNoBackingVolume { 196 // Filesystem has no backing volume: nothing more to do. 197 return nil 198 } else if err != nil { 199 return errors.Annotate(err, "getting filesystem volume") 200 } 201 w.modelVolumeFilesystems[volumeTag] = filesystemTag 202 if w.modelVolumesAttached.Contains(volumeTag) { 203 w.changes.Add(filesystemTag.Id()) 204 } 205 return nil 206 } 207 208 func (w *hostFilesystemsWatcher) modelVolumeAttachmentChanged(hostTag names.Tag, volumeTag names.VolumeTag) error { 209 va, err := w.backend.VolumeAttachment(hostTag, volumeTag) 210 if err != nil && !errors.IsNotFound(err) { 211 return errors.Annotate(err, "getting volume attachment") 212 } 213 if errors.IsNotFound(err) || va.Life() == state.Dead { 214 filesystemTag, ok := w.modelVolumeFilesystems[volumeTag] 215 if ok { 216 // If the volume attachment is Dead/removed, 217 // the filesystem must have been removed. We 218 // don't get a change notification for removed 219 // entities, so we use this to clean up. 220 delete(w.modelVolumeFilesystems, volumeTag) 221 w.changes.Remove(filesystemTag.Id()) 222 w.modelVolumesAttached.Remove(volumeTag) 223 } 224 return nil 225 } 226 w.modelVolumesAttached.Add(volumeTag) 227 if filesystemTag, ok := w.modelVolumeFilesystems[volumeTag]; ok { 228 w.changes.Add(filesystemTag.Id()) 229 } 230 return nil 231 } 232 233 // WatchModelManagedFilesystemAttachments returns a strings watcher that 234 // reports lifecycle changes to attachments of model-scoped filesystem that 235 // have no backing volume. Volume-backed filesystems are always managed by 236 // the host to which they are attached. 237 func (fw Watchers) WatchModelManagedFilesystemAttachments() state.StringsWatcher { 238 return newFilteredStringsWatcher(fw.Backend.WatchModelFilesystemAttachments(), func(id string) (bool, error) { 239 _, filesystemTag, err := state.ParseFilesystemAttachmentId(id) 240 if err != nil { 241 return false, errors.Annotate(err, "parsing filesystem attachment ID") 242 } 243 f, err := fw.Backend.Filesystem(filesystemTag) 244 if errors.IsNotFound(err) { 245 return false, nil 246 } else if err != nil { 247 return false, errors.Trace(err) 248 } 249 _, err = f.Volume() 250 return err == state.ErrNoBackingVolume, nil 251 }) 252 } 253 254 // WatchMachineManagedFilesystemAttachments returns a strings watcher that 255 // reports lifecycle changes for attachments to both machine-scoped filesystems, 256 // and model-scoped, volume-backed filesystems that are attached to the 257 // specified machine. 258 func (fw Watchers) WatchMachineManagedFilesystemAttachments(m names.MachineTag) state.StringsWatcher { 259 w := &hostFilesystemAttachmentsWatcher{ 260 stringsWatcherBase: stringsWatcherBase{out: make(chan []string)}, 261 backend: fw.Backend, 262 changes: set.NewStrings(), 263 hostFilesystemAttachments: fw.Backend.WatchMachineFilesystemAttachments(m), 264 modelFilesystemAttachments: fw.Backend.WatchModelFilesystemAttachments(), 265 modelVolumeAttachments: fw.Backend.WatchModelVolumeAttachments(), 266 modelVolumesAttached: names.NewSet(), 267 modelVolumeFilesystemAttachments: make(map[names.VolumeTag]string), 268 hostMatch: func(tag names.Tag) (bool, error) { 269 return tag == m, nil 270 }, 271 } 272 273 w.tomb.Go(func() error { 274 defer watcher.Stop(w.hostFilesystemAttachments, &w.tomb) 275 defer watcher.Stop(w.modelFilesystemAttachments, &w.tomb) 276 defer watcher.Stop(w.modelVolumeAttachments, &w.tomb) 277 return w.loop() 278 }) 279 return w 280 } 281 282 // WatchMachineManagedFilesystemAttachments returns a strings watcher that 283 // reports lifecycle changes for attachments to both unit-scoped filesystems, 284 // and model-scoped, volume-backed filesystems that are attached to units of the 285 // specified application. 286 func (fw Watchers) WatchUnitManagedFilesystemAttachments(app names.ApplicationTag) state.StringsWatcher { 287 w := &hostFilesystemAttachmentsWatcher{ 288 stringsWatcherBase: stringsWatcherBase{out: make(chan []string)}, 289 backend: fw.Backend, 290 changes: set.NewStrings(), 291 hostFilesystemAttachments: fw.Backend.WatchUnitFilesystemAttachments(app), 292 modelFilesystemAttachments: fw.Backend.WatchModelFilesystemAttachments(), 293 modelVolumeAttachments: fw.Backend.WatchModelVolumeAttachments(), 294 modelVolumesAttached: names.NewSet(), 295 modelVolumeFilesystemAttachments: make(map[names.VolumeTag]string), 296 hostMatch: func(tag names.Tag) (bool, error) { 297 unitApp, err := names.UnitApplication(tag.Id()) 298 if err != nil { 299 return false, errors.Trace(err) 300 } 301 return unitApp == app.Id(), nil 302 }, 303 } 304 305 w.tomb.Go(func() error { 306 defer watcher.Stop(w.hostFilesystemAttachments, &w.tomb) 307 defer watcher.Stop(w.modelFilesystemAttachments, &w.tomb) 308 defer watcher.Stop(w.modelVolumeAttachments, &w.tomb) 309 return w.loop() 310 }) 311 return w 312 } 313 314 // hostFilesystemAttachmentsWatcher is a strings watcher that reports 315 // lifechcle changes for attachments to both host-scoped filesystems, 316 // and model-scoped, volume-backed filesystems that are attached to the 317 // specified host. 318 // 319 // NOTE(axw) we use the existence of the *volume* attachment rather than 320 // filesystem attachment because the filesystem attachment can be destroyed 321 // before the filesystem, but the volume attachment cannot. 322 type hostFilesystemAttachmentsWatcher struct { 323 stringsWatcherBase 324 changes set.Strings 325 backend Backend 326 hostFilesystemAttachments state.StringsWatcher 327 modelFilesystemAttachments state.StringsWatcher 328 modelVolumeAttachments state.StringsWatcher 329 modelVolumesAttached names.Set 330 modelVolumeFilesystemAttachments map[names.VolumeTag]string 331 hostMatch func(names.Tag) (bool, error) 332 } 333 334 func (w *hostFilesystemAttachmentsWatcher) loop() error { 335 defer close(w.out) 336 var out chan<- []string 337 var machineFilesystemAttachmentsReceived bool 338 var modelFilesystemAttachmentsReceived bool 339 var modelVolumeAttachmentsReceived bool 340 var sentFirst bool 341 for { 342 select { 343 case <-w.tomb.Dying(): 344 return tomb.ErrDying 345 case values, ok := <-w.hostFilesystemAttachments.Changes(): 346 if !ok { 347 return watcher.EnsureErr(w.hostFilesystemAttachments) 348 } 349 machineFilesystemAttachmentsReceived = true 350 for _, v := range values { 351 w.changes.Add(v) 352 } 353 case values, ok := <-w.modelFilesystemAttachments.Changes(): 354 if !ok { 355 return watcher.EnsureErr(w.modelFilesystemAttachments) 356 } 357 modelFilesystemAttachmentsReceived = true 358 for _, id := range values { 359 hostTag, filesystemTag, err := state.ParseFilesystemAttachmentId(id) 360 if err != nil { 361 return errors.Annotate(err, "parsing filesystem attachment ID") 362 } 363 match, err := w.hostMatch(hostTag) 364 if err != nil { 365 return errors.Annotate(err, "parsing filesystem host tag") 366 } 367 if !match { 368 continue 369 } 370 if err := w.modelFilesystemAttachmentChanged(id, filesystemTag); err != nil { 371 return errors.Trace(err) 372 } 373 } 374 case values, ok := <-w.modelVolumeAttachments.Changes(): 375 if !ok { 376 return watcher.EnsureErr(w.modelVolumeAttachments) 377 } 378 modelVolumeAttachmentsReceived = true 379 for _, id := range values { 380 hostTag, volumeTag, err := state.ParseVolumeAttachmentId(id) 381 if err != nil { 382 return errors.Annotate(err, "parsing volume attachment ID") 383 } 384 match, err := w.hostMatch(hostTag) 385 if err != nil { 386 return errors.Annotate(err, "parsing volume host tag") 387 } 388 if !match { 389 continue 390 } 391 if err := w.modelVolumeAttachmentChanged(hostTag, volumeTag); err != nil { 392 return errors.Trace(err) 393 } 394 } 395 case out <- w.changes.SortedValues(): 396 w.changes = set.NewStrings() 397 out = nil 398 } 399 // NOTE(axw) we don't send any changes until we have received 400 // an initial event from each of the watchers. This ensures 401 // that we provide a complete view of the world in the initial 402 // event, which is expected of all watchers. 403 if machineFilesystemAttachmentsReceived && 404 modelFilesystemAttachmentsReceived && 405 modelVolumeAttachmentsReceived && 406 (!sentFirst || len(w.changes) > 0) { 407 sentFirst = true 408 out = w.out 409 } 410 } 411 } 412 413 func (w *hostFilesystemAttachmentsWatcher) modelFilesystemAttachmentChanged( 414 filesystemAttachmentId string, 415 filesystemTag names.FilesystemTag, 416 ) error { 417 filesystem, err := w.backend.Filesystem(filesystemTag) 418 if errors.IsNotFound(err) { 419 // Filesystem removed: nothing more to do. 420 return nil 421 } else if err != nil { 422 return errors.Annotate(err, "getting filesystem") 423 } 424 volumeTag, err := filesystem.Volume() 425 if err == state.ErrNoBackingVolume { 426 // Filesystem has no backing volume: nothing more to do. 427 return nil 428 } else if err != nil { 429 return errors.Annotate(err, "getting filesystem volume") 430 } 431 w.modelVolumeFilesystemAttachments[volumeTag] = filesystemAttachmentId 432 if w.modelVolumesAttached.Contains(volumeTag) { 433 w.changes.Add(filesystemAttachmentId) 434 } 435 return nil 436 } 437 438 func (w *hostFilesystemAttachmentsWatcher) modelVolumeAttachmentChanged(hostTag names.Tag, volumeTag names.VolumeTag) error { 439 va, err := w.backend.VolumeAttachment(hostTag, volumeTag) 440 if err != nil && !errors.IsNotFound(err) { 441 return errors.Annotate(err, "getting volume attachment") 442 } 443 if errors.IsNotFound(err) || va.Life() == state.Dead { 444 filesystemAttachmentId, ok := w.modelVolumeFilesystemAttachments[volumeTag] 445 if ok { 446 // If the volume attachment is Dead/removed, 447 // the filesystem attachment must have been 448 // removed. We don't get a change notification 449 // for removed entities, so we use this to 450 // clean up. 451 delete(w.modelVolumeFilesystemAttachments, volumeTag) 452 w.changes.Remove(filesystemAttachmentId) 453 w.modelVolumesAttached.Remove(volumeTag) 454 } 455 return nil 456 } 457 w.modelVolumesAttached.Add(volumeTag) 458 if filesystemAttachmentId, ok := w.modelVolumeFilesystemAttachments[volumeTag]; ok { 459 w.changes.Add(filesystemAttachmentId) 460 } 461 return nil 462 } 463 464 type filteredStringsWatcher struct { 465 stringsWatcherBase 466 w state.StringsWatcher 467 filter func(string) (bool, error) 468 } 469 470 func newFilteredStringsWatcher(w state.StringsWatcher, filter func(string) (bool, error)) *filteredStringsWatcher { 471 fw := &filteredStringsWatcher{ 472 stringsWatcherBase: stringsWatcherBase{out: make(chan []string)}, 473 w: w, 474 filter: filter, 475 } 476 fw.tomb.Go(func() error { 477 defer watcher.Stop(fw.w, &fw.tomb) 478 return fw.loop() 479 }) 480 return fw 481 } 482 483 func (fw *filteredStringsWatcher) loop() error { 484 defer close(fw.out) 485 var out chan []string 486 var values []string 487 for { 488 select { 489 case <-fw.tomb.Dying(): 490 return tomb.ErrDying 491 case in, ok := <-fw.w.Changes(): 492 if !ok { 493 return watcher.EnsureErr(fw.w) 494 } 495 values = make([]string, 0, len(in)) 496 for _, value := range in { 497 ok, err := fw.filter(value) 498 if err != nil { 499 return errors.Trace(err) 500 } else if ok { 501 values = append(values, value) 502 } 503 } 504 out = fw.out 505 case out <- values: 506 out = nil 507 } 508 } 509 } 510 511 type stringsWatcherBase struct { 512 tomb tomb.Tomb 513 out chan []string 514 } 515 516 // Err is part of the state.StringsWatcher interface. 517 func (w *stringsWatcherBase) Err() error { 518 return w.tomb.Err() 519 } 520 521 // Stop is part of the state.StringsWatcher interface. 522 func (w *stringsWatcherBase) Stop() error { 523 w.Kill() 524 return w.Wait() 525 } 526 527 // Kill is part of the state.StringsWatcher interface. 528 func (w *stringsWatcherBase) Kill() { 529 w.tomb.Kill(nil) 530 } 531 532 // Wait is part of the state.StringsWatcher interface. 533 func (w *stringsWatcherBase) Wait() error { 534 return w.tomb.Wait() 535 } 536 537 // Changes is part of the state.StringsWatcher interface. 538 func (w *stringsWatcherBase) Changes() <-chan []string { 539 return w.out 540 }