github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/deployer/deployer.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package deployer
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/collections/set"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names/v5"
    12  	"github.com/juju/utils/v3"
    13  	"github.com/juju/worker/v3"
    14  
    15  	"github.com/juju/juju/agent"
    16  	"github.com/juju/juju/core/life"
    17  	"github.com/juju/juju/core/status"
    18  	"github.com/juju/juju/core/watcher"
    19  	"github.com/juju/juju/rpc/params"
    20  )
    21  
    22  // logger is here to stop the desire of creating a package level logger.
    23  // Don't do this, instead pass one through as config to the worker.
    24  type logger interface{}
    25  
    26  var _ logger = struct{}{}
    27  
    28  // Deployer is responsible for deploying and recalling unit agents, according
    29  // to changes in a set of state units; and for the final removal of its agents'
    30  // units from state when they are no longer needed.
    31  type Deployer struct {
    32  	st       API
    33  	logger   Logger
    34  	ctx      Context
    35  	deployed set.Strings
    36  }
    37  
    38  // API is used to define the methods that the deployer makes.
    39  type API interface {
    40  	Machine(names.MachineTag) (Machine, error)
    41  	Unit(names.UnitTag) (Unit, error)
    42  }
    43  
    44  // Machine defines the methods that the deployer makes on a machine in
    45  // the model.
    46  type Machine interface {
    47  	WatchUnits() (watcher.StringsWatcher, error)
    48  }
    49  
    50  // Unit defines the methods that the deployer makes on a unit in the model.
    51  type Unit interface {
    52  	Life() life.Value
    53  	Name() string
    54  	Remove() error
    55  	SetPassword(password string) error
    56  	SetStatus(unitStatus status.Status, info string, data map[string]interface{}) error
    57  }
    58  
    59  // Context abstracts away the differences between different unit deployment
    60  // strategies; where a Deployer is responsible for what to deploy, a Context
    61  // is responsible for how to deploy.
    62  type Context interface {
    63  	worker.Worker
    64  
    65  	// DeployUnit causes the agent for the specified unit to be started and run
    66  	// continuously until further notice without further intervention. It will
    67  	// return an error if the agent is already deployed.
    68  	DeployUnit(unitName, initialPassword string) error
    69  
    70  	// RecallUnit causes the agent for the specified unit to be stopped, and
    71  	// the agent's data to be destroyed. It will return an error if the agent
    72  	// was not deployed by the manager.
    73  	RecallUnit(unitName string) error
    74  
    75  	// DeployedUnits returns the names of all units deployed by the manager.
    76  	DeployedUnits() ([]string, error)
    77  
    78  	// AgentConfig returns the agent config for the machine agent that is
    79  	// running the deployer.
    80  	AgentConfig() agent.Config
    81  
    82  	Report() map[string]interface{}
    83  }
    84  
    85  // NewDeployer returns a Worker that deploys and recalls unit agents
    86  // via ctx, taking a machine id to operate on.
    87  func NewDeployer(st API, logger Logger, ctx Context) (worker.Worker, error) {
    88  	d := &Deployer{
    89  		st:       st,
    90  		logger:   logger,
    91  		ctx:      ctx,
    92  		deployed: make(set.Strings),
    93  	}
    94  	w, err := watcher.NewStringsWorker(watcher.StringsConfig{
    95  		Handler: d,
    96  	})
    97  	if err != nil {
    98  		return nil, errors.Trace(err)
    99  	}
   100  	return w, nil
   101  }
   102  
   103  // Report is shown in the engine report.
   104  func (d *Deployer) Report() map[string]interface{} {
   105  	// Get the report from the context.
   106  	return d.ctx.Report()
   107  }
   108  
   109  // SetUp is called by the NewStringsWorker to create the watcher that drives the
   110  // worker.
   111  func (d *Deployer) SetUp() (watcher.StringsWatcher, error) {
   112  	d.logger.Tracef("SetUp")
   113  	tag := d.ctx.AgentConfig().Tag()
   114  	machineTag, ok := tag.(names.MachineTag)
   115  	if !ok {
   116  		return nil, errors.Errorf("expected names.MachineTag, got %T", tag)
   117  	}
   118  	d.logger.Tracef("getting Machine %s", machineTag)
   119  	machine, err := d.st.Machine(machineTag)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	d.logger.Tracef("getting units watcher")
   124  	machineUnitsWatcher, err := machine.WatchUnits()
   125  	if err != nil {
   126  		d.logger.Tracef("error: %v", err)
   127  		return nil, err
   128  	}
   129  	d.logger.Tracef("looking for deployed units")
   130  
   131  	deployed, err := d.ctx.DeployedUnits()
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  	d.logger.Tracef("deployed units: %v", deployed)
   136  	for _, unitName := range deployed {
   137  		d.deployed.Add(unitName)
   138  		if err := d.changed(unitName); err != nil {
   139  			return nil, err
   140  		}
   141  	}
   142  	return machineUnitsWatcher, nil
   143  }
   144  
   145  // Handle is called for new value in the StringsWatcher.
   146  func (d *Deployer) Handle(_ <-chan struct{}, unitNames []string) error {
   147  	d.logger.Tracef("Handle: %v", unitNames)
   148  	for _, unitName := range unitNames {
   149  		if err := d.changed(unitName); err != nil {
   150  			return err
   151  		}
   152  	}
   153  	return nil
   154  }
   155  
   156  // changed ensures that the named unit is deployed, recalled, or removed, as
   157  // indicated by its state.
   158  func (d *Deployer) changed(unitName string) error {
   159  	unitTag := names.NewUnitTag(unitName)
   160  	// Determine unit life state, and whether we're responsible for it.
   161  	d.logger.Infof("checking unit %q", unitName)
   162  	var unitLife life.Value
   163  	unit, err := d.st.Unit(unitTag)
   164  	if params.IsCodeNotFoundOrCodeUnauthorized(err) {
   165  		unitLife = life.Dead
   166  	} else if err != nil {
   167  		return err
   168  	} else {
   169  		unitLife = unit.Life()
   170  	}
   171  	// Deployed units must be removed if they're Dead, or if the deployer
   172  	// is no longer responsible for them.
   173  	if d.deployed.Contains(unitName) {
   174  		if unitLife == life.Dead {
   175  			if err := d.recall(unitName); err != nil {
   176  				return err
   177  			}
   178  		}
   179  	}
   180  	// The only units that should be deployed are those that (1) we are responsible
   181  	// for and (2) are Alive -- if we're responsible for a Dying unit that is not
   182  	// yet deployed, we should remove it immediately rather than undergo the hassle
   183  	// of deploying a unit agent purely so it can set itself to Dead.
   184  	if !d.deployed.Contains(unitName) {
   185  		if unitLife == life.Alive {
   186  			return d.deploy(unit)
   187  		} else if unit != nil {
   188  			return d.remove(unit)
   189  		}
   190  	}
   191  	return nil
   192  }
   193  
   194  // deploy will deploy the supplied unit with the deployer's manager. It will
   195  // panic if it observes inconsistent internal state.
   196  func (d *Deployer) deploy(unit Unit) error {
   197  	unitName := unit.Name()
   198  	if d.deployed.Contains(unit.Name()) {
   199  		panic("must not re-deploy a deployed unit")
   200  	}
   201  	if err := unit.SetStatus(status.Waiting, status.MessageInstallingAgent, nil); err != nil {
   202  		return errors.Trace(err)
   203  	}
   204  	d.logger.Infof("deploying unit %q", unitName)
   205  	initialPassword, err := utils.RandomPassword()
   206  	if err != nil {
   207  		return err
   208  	}
   209  	if err := unit.SetPassword(initialPassword); err != nil {
   210  		return fmt.Errorf("cannot set password for unit %q: %v", unitName, err)
   211  	}
   212  	if err := d.ctx.DeployUnit(unitName, initialPassword); err != nil {
   213  		return err
   214  	}
   215  	d.deployed.Add(unitName)
   216  	return nil
   217  }
   218  
   219  // recall will recall the named unit with the deployer's manager. It will
   220  // panic if it observes inconsistent internal state.
   221  func (d *Deployer) recall(unitName string) error {
   222  	if !d.deployed.Contains(unitName) {
   223  		panic("must not recall a unit that is not deployed")
   224  	}
   225  	d.logger.Infof("recalling unit %q", unitName)
   226  	if err := d.ctx.RecallUnit(unitName); err != nil {
   227  		return err
   228  	}
   229  	d.deployed.Remove(unitName)
   230  	return nil
   231  }
   232  
   233  // remove will remove the supplied unit from state. It will panic if it
   234  // observes inconsistent internal state.
   235  func (d *Deployer) remove(unit Unit) error {
   236  	unitName := unit.Name()
   237  	if d.deployed.Contains(unitName) {
   238  		panic("must not remove a deployed unit")
   239  	} else if unit.Life() == life.Alive {
   240  		panic("must not remove an Alive unit")
   241  	}
   242  	d.logger.Infof("removing unit %q", unitName)
   243  	return unit.Remove()
   244  }
   245  
   246  // TearDown stops the embedded context.
   247  func (d *Deployer) TearDown() error {
   248  	d.ctx.Kill()
   249  	return d.ctx.Wait()
   250  }