github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  
    10  	"github.com/juju/juju/agent"
    11  	"github.com/juju/juju/core/migration"
    12  	"github.com/juju/juju/network"
    13  	"github.com/juju/juju/watcher"
    14  	"github.com/juju/juju/worker"
    15  	"github.com/juju/juju/worker/catacomb"
    16  	"github.com/juju/juju/worker/fortress"
    17  )
    18  
    19  var logger = loggo.GetLogger("juju.worker.migrationminion")
    20  
    21  // Facade exposes controller functionality to a Worker.
    22  type Facade interface {
    23  
    24  	// Watch returns a watcher which reports when the status changes
    25  	// for the migration for the model associated with the API
    26  	// connection.
    27  	Watch() (watcher.MigrationStatusWatcher, error)
    28  }
    29  
    30  // Config defines the operation of a Worker.
    31  type Config struct {
    32  	Agent  agent.Agent
    33  	Facade Facade
    34  	Guard  fortress.Guard
    35  }
    36  
    37  // Validate returns an error if config cannot drive a Worker.
    38  func (config Config) Validate() error {
    39  	if config.Agent == nil {
    40  		return errors.NotValidf("nil Agent")
    41  	}
    42  	if config.Facade == nil {
    43  		return errors.NotValidf("nil Facade")
    44  	}
    45  	if config.Guard == nil {
    46  		return errors.NotValidf("nil Guard")
    47  	}
    48  	return nil
    49  }
    50  
    51  // New returns a Worker backed by config, or an error.
    52  func New(config Config) (worker.Worker, error) {
    53  	if err := config.Validate(); err != nil {
    54  		return nil, errors.Trace(err)
    55  	}
    56  	w := &Worker{config: config}
    57  	err := catacomb.Invoke(catacomb.Plan{
    58  		Site: &w.catacomb,
    59  		Work: w.loop,
    60  	})
    61  	if err != nil {
    62  		return nil, errors.Trace(err)
    63  	}
    64  	return w, nil
    65  }
    66  
    67  // Worker waits for a model migration to be active, then locks down the
    68  // configured fortress and implements the migration.
    69  type Worker struct {
    70  	catacomb catacomb.Catacomb
    71  	config   Config
    72  }
    73  
    74  // Kill implements worker.Worker.
    75  func (w *Worker) Kill() {
    76  	w.catacomb.Kill(nil)
    77  }
    78  
    79  // Wait implements worker.Worker.
    80  func (w *Worker) Wait() error {
    81  	return w.catacomb.Wait()
    82  }
    83  
    84  func (w *Worker) loop() error {
    85  	watcher, err := w.config.Facade.Watch()
    86  	if err != nil {
    87  		return errors.Annotate(err, "setting up watcher")
    88  	}
    89  	if err := w.catacomb.Add(watcher); err != nil {
    90  		return errors.Trace(err)
    91  	}
    92  
    93  	for {
    94  		select {
    95  		case <-w.catacomb.Dying():
    96  			return w.catacomb.ErrDying()
    97  		case status, ok := <-watcher.Changes():
    98  			if !ok {
    99  				return errors.New("watcher channel closed")
   100  			}
   101  			if err := w.handle(status); err != nil {
   102  				return errors.Trace(err)
   103  			}
   104  		}
   105  	}
   106  }
   107  
   108  func (w *Worker) handle(status watcher.MigrationStatus) error {
   109  	logger.Infof("migration phase is now: %s", status.Phase)
   110  
   111  	if status.Phase == migration.NONE {
   112  		return w.config.Guard.Unlock()
   113  	}
   114  
   115  	err := w.config.Guard.Lockdown(w.catacomb.Dying())
   116  	if errors.Cause(err) == fortress.ErrAborted {
   117  		return w.catacomb.ErrDying()
   118  	} else if err != nil {
   119  		return errors.Trace(err)
   120  	}
   121  
   122  	switch status.Phase {
   123  	case migration.QUIESCE:
   124  		// TODO(mjs) - once Will's stable mode work comes
   125  		// together this worker will only start up when a
   126  		// migration is active. Here the minion should report
   127  		// to the controller that it is running so that the
   128  		// migration can progress to READONLY.
   129  	case migration.VALIDATION:
   130  		// TODO(mjs) - check connection to the target
   131  		// controller here and report success/failure.
   132  	case migration.SUCCESS:
   133  		err := w.doSUCCESS(status.TargetAPIAddrs, status.TargetCACert)
   134  		if err != nil {
   135  			return errors.Trace(err)
   136  		}
   137  	case migration.ABORT:
   138  		// TODO(mjs) - exit here once Will's stable mode work
   139  		// comes together. The minion is done if these phases
   140  		// are reached.
   141  	default:
   142  		// The minion doesn't need to do anything for other
   143  		// migration phases.
   144  	}
   145  	return nil
   146  }
   147  
   148  func (w *Worker) doSUCCESS(targetAddrs []string, caCert string) error {
   149  	hps, err := apiAddrsToHostPorts(targetAddrs)
   150  	if err != nil {
   151  		return errors.Annotate(err, "converting API addresses")
   152  	}
   153  	err = w.config.Agent.ChangeConfig(func(conf agent.ConfigSetter) error {
   154  		conf.SetAPIHostPorts(hps)
   155  		conf.SetCACert(caCert)
   156  		return nil
   157  	})
   158  	return errors.Annotate(err, "setting agent config")
   159  }
   160  
   161  func apiAddrsToHostPorts(addrs []string) ([][]network.HostPort, error) {
   162  	hps, err := network.ParseHostPorts(addrs...)
   163  	if err != nil {
   164  		return nil, errors.Trace(err)
   165  	}
   166  	return [][]network.HostPort{hps}, nil
   167  }