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  }