github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/migrationminion/worker.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package migrationminion
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/loggo"
     9  	"gopkg.in/juju/worker.v1"
    10  	"gopkg.in/juju/worker.v1/catacomb"
    11  
    12  	"github.com/juju/juju/agent"
    13  	"github.com/juju/juju/api"
    14  	"github.com/juju/juju/api/base"
    15  	"github.com/juju/juju/core/migration"
    16  	"github.com/juju/juju/core/watcher"
    17  	"github.com/juju/juju/network"
    18  	"github.com/juju/juju/worker/fortress"
    19  )
    20  
    21  var logger = loggo.GetLogger("juju.worker.migrationminion")
    22  
    23  // Facade exposes controller functionality to a Worker.
    24  type Facade interface {
    25  	Watch() (watcher.MigrationStatusWatcher, error)
    26  	Report(migrationId string, phase migration.Phase, success bool) error
    27  }
    28  
    29  // Config defines the operation of a Worker.
    30  type Config struct {
    31  	Agent             agent.Agent
    32  	Facade            Facade
    33  	Guard             fortress.Guard
    34  	APIOpen           func(*api.Info, api.DialOpts) (api.Connection, error)
    35  	ValidateMigration func(base.APICaller) error
    36  }
    37  
    38  // Validate returns an error if config cannot drive a Worker.
    39  func (config Config) Validate() error {
    40  	if config.Agent == nil {
    41  		return errors.NotValidf("nil Agent")
    42  	}
    43  	if config.Facade == nil {
    44  		return errors.NotValidf("nil Facade")
    45  	}
    46  	if config.Guard == nil {
    47  		return errors.NotValidf("nil Guard")
    48  	}
    49  	if config.APIOpen == nil {
    50  		return errors.NotValidf("nil APIOpen")
    51  	}
    52  	if config.ValidateMigration == nil {
    53  		return errors.NotValidf("nil ValidateMigration")
    54  	}
    55  	return nil
    56  }
    57  
    58  // New returns a Worker backed by config, or an error.
    59  func New(config Config) (worker.Worker, error) {
    60  	if err := config.Validate(); err != nil {
    61  		return nil, errors.Trace(err)
    62  	}
    63  	w := &Worker{config: config}
    64  	err := catacomb.Invoke(catacomb.Plan{
    65  		Site: &w.catacomb,
    66  		Work: w.loop,
    67  	})
    68  	if err != nil {
    69  		return nil, errors.Trace(err)
    70  	}
    71  	return w, nil
    72  }
    73  
    74  // Worker waits for a model migration to be active, then locks down the
    75  // configured fortress and implements the migration.
    76  type Worker struct {
    77  	catacomb catacomb.Catacomb
    78  	config   Config
    79  }
    80  
    81  // Kill implements worker.Worker.
    82  func (w *Worker) Kill() {
    83  	w.catacomb.Kill(nil)
    84  }
    85  
    86  // Wait implements worker.Worker.
    87  func (w *Worker) Wait() error {
    88  	return w.catacomb.Wait()
    89  }
    90  
    91  func (w *Worker) loop() error {
    92  	watcher, err := w.config.Facade.Watch()
    93  	if err != nil {
    94  		return errors.Annotate(err, "setting up watcher")
    95  	}
    96  	if err := w.catacomb.Add(watcher); err != nil {
    97  		return errors.Trace(err)
    98  	}
    99  
   100  	for {
   101  		select {
   102  		case <-w.catacomb.Dying():
   103  			return w.catacomb.ErrDying()
   104  		case status, ok := <-watcher.Changes():
   105  			if !ok {
   106  				return errors.New("watcher channel closed")
   107  			}
   108  			if err := w.handle(status); err != nil {
   109  				return errors.Trace(err)
   110  			}
   111  		}
   112  	}
   113  }
   114  
   115  func (w *Worker) handle(status watcher.MigrationStatus) error {
   116  	logger.Infof("migration phase is now: %s", status.Phase)
   117  
   118  	if !status.Phase.IsRunning() {
   119  		return w.config.Guard.Unlock()
   120  	}
   121  
   122  	// Ensure that all workers related to migration fortress have
   123  	// stopped and aren't allowed to restart.
   124  	err := w.config.Guard.Lockdown(w.catacomb.Dying())
   125  	if errors.Cause(err) == fortress.ErrAborted {
   126  		return w.catacomb.ErrDying()
   127  	} else if err != nil {
   128  		return errors.Trace(err)
   129  	}
   130  
   131  	switch status.Phase {
   132  	case migration.QUIESCE:
   133  		err = w.doQUIESCE(status)
   134  	case migration.VALIDATION:
   135  		err = w.doVALIDATION(status)
   136  	case migration.SUCCESS:
   137  		err = w.doSUCCESS(status)
   138  	default:
   139  		// The minion doesn't need to do anything for other
   140  		// migration phases.
   141  	}
   142  	return errors.Trace(err)
   143  }
   144  
   145  func (w *Worker) doQUIESCE(status watcher.MigrationStatus) error {
   146  	// Report that the minion is ready and that all workers that
   147  	// should be shut down have done so.
   148  	return w.report(status, true)
   149  }
   150  
   151  func (w *Worker) doVALIDATION(status watcher.MigrationStatus) error {
   152  	err := w.validate(status)
   153  	if err != nil {
   154  		// Don't return this error just log it and report to the
   155  		// migrationmaster that things didn't work out.
   156  		logger.Errorf("validation failed: %v", err)
   157  	}
   158  	return w.report(status, err == nil)
   159  }
   160  
   161  func (w *Worker) validate(status watcher.MigrationStatus) error {
   162  	agentConf := w.config.Agent.CurrentConfig()
   163  	apiInfo, ok := agentConf.APIInfo()
   164  	if !ok {
   165  		return errors.New("no API connection details")
   166  	}
   167  	apiInfo.Addrs = status.TargetAPIAddrs
   168  	apiInfo.CACert = status.TargetCACert
   169  	// Application agents (k8s) use old password.
   170  	if apiInfo.Password == "" {
   171  		apiInfo.Password = agentConf.OldPassword()
   172  	}
   173  
   174  	// Use zero DialOpts (no retries) because the worker must stay
   175  	// responsive to Kill requests. We don't want it to be blocked by
   176  	// a long set of retry attempts.
   177  	conn, err := w.config.APIOpen(apiInfo, api.DialOpts{})
   178  	if err != nil {
   179  		// Don't return this error just log it and report to the
   180  		// migrationmaster that things didn't work out.
   181  		return errors.Annotate(err, "failed to open API to target controller")
   182  	}
   183  	defer conn.Close()
   184  
   185  	// Ask the agent to confirm that things look ok.
   186  	err = w.config.ValidateMigration(conn)
   187  	return errors.Trace(err)
   188  }
   189  
   190  func (w *Worker) doSUCCESS(status watcher.MigrationStatus) error {
   191  	hps, err := apiAddrsToHostPorts(status.TargetAPIAddrs)
   192  	if err != nil {
   193  		return errors.Annotate(err, "converting API addresses")
   194  	}
   195  
   196  	// Report first because the config update that's about to happen
   197  	// will cause the API connection to drop. The SUCCESS phase is the
   198  	// point of no return anyway.
   199  	if err := w.report(status, true); err != nil {
   200  		return errors.Trace(err)
   201  	}
   202  
   203  	err = w.config.Agent.ChangeConfig(func(conf agent.ConfigSetter) error {
   204  		conf.SetAPIHostPorts(hps)
   205  		conf.SetCACert(status.TargetCACert)
   206  		return nil
   207  	})
   208  	return errors.Annotate(err, "setting agent config")
   209  }
   210  
   211  func (w *Worker) report(status watcher.MigrationStatus, success bool) error {
   212  	logger.Debugf("reporting back for phase %s: %v", status.Phase, success)
   213  	err := w.config.Facade.Report(status.MigrationId, status.Phase, success)
   214  	return errors.Annotate(err, "failed to report phase progress")
   215  }
   216  
   217  func apiAddrsToHostPorts(addrs []string) ([][]network.HostPort, error) {
   218  	hps, err := network.ParseHostPorts(addrs...)
   219  	if err != nil {
   220  		return nil, errors.Trace(err)
   221  	}
   222  	return [][]network.HostPort{hps}, nil
   223  }