github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/instancemutater/worker.go (about)

     1  // Copyright 2019 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package instancemutater
     5  
     6  import (
     7  	"sync"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/names/v5"
    11  	"github.com/juju/worker/v3"
    12  	"github.com/juju/worker/v3/catacomb"
    13  	"github.com/juju/worker/v3/dependency"
    14  
    15  	"github.com/juju/juju/agent"
    16  	"github.com/juju/juju/api/agent/instancemutater"
    17  	"github.com/juju/juju/core/watcher"
    18  	"github.com/juju/juju/environs"
    19  )
    20  
    21  type InstanceMutaterAPI interface {
    22  	WatchModelMachines() (watcher.StringsWatcher, error)
    23  	Machine(tag names.MachineTag) (instancemutater.MutaterMachine, error)
    24  }
    25  
    26  // Logger represents the logging methods called.
    27  type Logger interface {
    28  	Warningf(message string, args ...interface{})
    29  	Infof(message string, args ...interface{})
    30  	Debugf(message string, args ...interface{})
    31  	Errorf(message string, args ...interface{})
    32  	Tracef(message string, args ...interface{})
    33  }
    34  
    35  // Config represents the configuration required to run a new instance machineApi
    36  // worker.
    37  type Config struct {
    38  	Facade InstanceMutaterAPI
    39  
    40  	// Logger is the Logger for this worker.
    41  	Logger Logger
    42  
    43  	Broker environs.LXDProfiler
    44  
    45  	AgentConfig agent.Config
    46  
    47  	// Tag is the current MutaterMachine tag
    48  	Tag names.Tag
    49  
    50  	// GetMachineWatcher allows the worker to watch different "machines"
    51  	// depending on whether this work is running with an environ broker
    52  	// or a container broker.
    53  	GetMachineWatcher func() (watcher.StringsWatcher, error)
    54  
    55  	// GetRequiredLXDProfiles provides a slice of strings representing the
    56  	// lxd profiles to be included on every LXD machine used given the
    57  	// current model name.
    58  	GetRequiredLXDProfiles RequiredLXDProfilesFunc
    59  
    60  	// GetRequiredContext provides a way to override the given context
    61  	// Note: the following is required for testing purposes when we have an
    62  	// error case and we want to know when it's valid to kill/clean the worker.
    63  	GetRequiredContext RequiredMutaterContextFunc
    64  }
    65  
    66  type RequiredLXDProfilesFunc func(string) []string
    67  
    68  type RequiredMutaterContextFunc func(MutaterContext) MutaterContext
    69  
    70  // Validate checks for missing values from the configuration and checks that
    71  // they conform to a given type.
    72  func (config Config) Validate() error {
    73  	if config.Logger == nil {
    74  		return errors.NotValidf("nil Logger")
    75  	}
    76  	if config.Facade == nil {
    77  		return errors.NotValidf("nil Facade")
    78  	}
    79  	if config.Broker == nil {
    80  		return errors.NotValidf("nil Broker")
    81  	}
    82  	if config.AgentConfig == nil {
    83  		return errors.NotValidf("nil AgentConfig")
    84  	}
    85  	if config.Tag == nil {
    86  		return errors.NotValidf("nil Tag")
    87  	}
    88  	if _, ok := config.Tag.(names.MachineTag); !ok {
    89  		if config.Tag.Kind() != names.ControllerAgentTagKind {
    90  			// On K8s controllers, the controller agent has a ControllerAgentTagKind not a MachineKind
    91  			// However, we shouldn't be running the InstanceMutater worker to track the state on the Controller
    92  			// machine anyway. This is a hack for bug #1866623
    93  			return errors.NotValidf("Tag of kind %v", config.Tag.Kind())
    94  		}
    95  		config.Logger.Debugf("asked to start an instance mutator with Tag of kind %q", config.Tag.Kind())
    96  	}
    97  	if config.GetMachineWatcher == nil {
    98  		return errors.NotValidf("nil GetMachineWatcher")
    99  	}
   100  	if config.GetRequiredLXDProfiles == nil {
   101  		return errors.NotValidf("nil GetRequiredLXDProfiles")
   102  	}
   103  	if config.GetRequiredContext == nil {
   104  		return errors.NotValidf("nil GetRequiredContext")
   105  	}
   106  	return nil
   107  }
   108  
   109  // NewEnvironWorker returns a worker that keeps track of
   110  // the machines in the state and polls their instance
   111  // for addition or removal changes.
   112  func NewEnvironWorker(config Config) (worker.Worker, error) {
   113  	config.GetMachineWatcher = config.Facade.WatchModelMachines
   114  	config.GetRequiredLXDProfiles = func(modelName string) []string {
   115  		return []string{"default", "juju-" + modelName}
   116  	}
   117  	config.GetRequiredContext = func(ctx MutaterContext) MutaterContext {
   118  		return ctx
   119  	}
   120  	return newWorker(config)
   121  }
   122  
   123  // NewContainerWorker returns a worker that keeps track of
   124  // the containers in the state for this machine agent and
   125  // polls their instance for addition or removal changes.
   126  func NewContainerWorker(config Config) (worker.Worker, error) {
   127  	if _, ok := config.Tag.(names.MachineTag); !ok {
   128  		config.Logger.Warningf("cannot start a ContainerWorker on a %q, not restarting", config.Tag.Kind())
   129  		return nil, dependency.ErrUninstall
   130  	}
   131  	m, err := config.Facade.Machine(config.Tag.(names.MachineTag))
   132  	if err != nil {
   133  		return nil, errors.Trace(err)
   134  	}
   135  	config.GetRequiredLXDProfiles = func(_ string) []string { return []string{"default"} }
   136  	config.GetMachineWatcher = m.WatchContainers
   137  	config.GetRequiredContext = func(ctx MutaterContext) MutaterContext {
   138  		return ctx
   139  	}
   140  	return newWorker(config)
   141  }
   142  
   143  func newWorker(config Config) (*mutaterWorker, error) {
   144  	if err := config.Validate(); err != nil {
   145  		return nil, errors.Trace(err)
   146  	}
   147  	watcher, err := config.GetMachineWatcher()
   148  	if err != nil {
   149  		return nil, errors.Trace(err)
   150  	}
   151  	w := &mutaterWorker{
   152  		logger:                     config.Logger,
   153  		facade:                     config.Facade,
   154  		broker:                     config.Broker,
   155  		machineWatcher:             watcher,
   156  		getRequiredLXDProfilesFunc: config.GetRequiredLXDProfiles,
   157  		getRequiredContextFunc:     config.GetRequiredContext,
   158  	}
   159  	// getRequiredContextFunc returns a MutaterContext, this is for overriding
   160  	// during testing.
   161  	err = catacomb.Invoke(catacomb.Plan{
   162  		Site: &w.catacomb,
   163  		Work: w.loop,
   164  		Init: []worker.Worker{watcher},
   165  	})
   166  	if err != nil {
   167  		return nil, errors.Trace(err)
   168  	}
   169  	return w, nil
   170  }
   171  
   172  type mutaterWorker struct {
   173  	catacomb catacomb.Catacomb
   174  
   175  	logger                     Logger
   176  	broker                     environs.LXDProfiler
   177  	facade                     InstanceMutaterAPI
   178  	machineWatcher             watcher.StringsWatcher
   179  	getRequiredLXDProfilesFunc RequiredLXDProfilesFunc
   180  	getRequiredContextFunc     RequiredMutaterContextFunc
   181  }
   182  
   183  func (w *mutaterWorker) loop() error {
   184  	var wg sync.WaitGroup
   185  	defer wg.Wait()
   186  	m := &mutater{
   187  		context:     w.getRequiredContextFunc(w),
   188  		logger:      w.logger,
   189  		wg:          &wg,
   190  		machines:    make(map[names.MachineTag]chan struct{}),
   191  		machineDead: make(chan instancemutater.MutaterMachine),
   192  	}
   193  	for {
   194  		select {
   195  		case <-m.context.dying():
   196  			return m.context.errDying()
   197  		case ids, ok := <-w.machineWatcher.Changes():
   198  			if !ok {
   199  				return errors.New("machines watcher closed")
   200  			}
   201  			tags := make([]names.MachineTag, len(ids))
   202  			for i := range ids {
   203  				tags[i] = names.NewMachineTag(ids[i])
   204  			}
   205  			if err := m.startMachines(tags); err != nil {
   206  				return err
   207  			}
   208  		case d := <-m.machineDead:
   209  			delete(m.machines, d.Tag())
   210  		}
   211  	}
   212  }
   213  
   214  // Kill implements worker.Worker.Kill.
   215  func (w *mutaterWorker) Kill() {
   216  	w.catacomb.Kill(nil)
   217  }
   218  
   219  // Wait implements worker.Worker.Wait.
   220  func (w *mutaterWorker) Wait() error {
   221  	return w.catacomb.Wait()
   222  }
   223  
   224  // Stop stops the mutaterWorker and returns any
   225  // error it encountered when running.
   226  func (w *mutaterWorker) Stop() error {
   227  	w.Kill()
   228  	return w.Wait()
   229  }
   230  
   231  // newMachineContext is part of the mutaterContext interface.
   232  func (w *mutaterWorker) newMachineContext() MachineContext {
   233  	return w.getRequiredContextFunc(w)
   234  }
   235  
   236  // getMachine is part of the MachineContext interface.
   237  func (w *mutaterWorker) getMachine(tag names.MachineTag) (instancemutater.MutaterMachine, error) {
   238  	m, err := w.facade.Machine(tag)
   239  	return m, err
   240  }
   241  
   242  // getBroker is part of the MachineContext interface.
   243  func (w *mutaterWorker) getBroker() environs.LXDProfiler {
   244  	return w.broker
   245  }
   246  
   247  // getRequiredLXDProfiles part of the MachineContext interface.
   248  func (w *mutaterWorker) getRequiredLXDProfiles(modelName string) []string {
   249  	return w.getRequiredLXDProfilesFunc(modelName)
   250  }
   251  
   252  // KillWithError is part of the lifetimeContext interface.
   253  func (w *mutaterWorker) KillWithError(err error) {
   254  	w.catacomb.Kill(err)
   255  }
   256  
   257  // dying is part of the lifetimeContext interface.
   258  func (w *mutaterWorker) dying() <-chan struct{} {
   259  	return w.catacomb.Dying()
   260  }
   261  
   262  // errDying is part of the lifetimeContext interface.
   263  func (w *mutaterWorker) errDying() error {
   264  	return w.catacomb.ErrDying()
   265  }
   266  
   267  // add is part of the lifetimeContext interface.
   268  func (w *mutaterWorker) add(new worker.Worker) error {
   269  	return w.catacomb.Add(new)
   270  }