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

     1  // Copyright 2015-2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package undertaker
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  
    10  	"github.com/juju/errors"
    11  	"gopkg.in/juju/worker.v1/catacomb"
    12  
    13  	"github.com/juju/juju/apiserver/params"
    14  	"github.com/juju/juju/core/status"
    15  	"github.com/juju/juju/core/watcher"
    16  	"github.com/juju/juju/environs"
    17  	"github.com/juju/juju/environs/context"
    18  	"github.com/juju/juju/worker/common"
    19  )
    20  
    21  // Facade covers the parts of the api/undertaker.UndertakerClient that we
    22  // need for the worker. It's more than a little raw, but we'll survive.
    23  type Facade interface {
    24  	ModelInfo() (params.UndertakerModelInfoResult, error)
    25  	WatchModelResources() (watcher.NotifyWatcher, error)
    26  	ProcessDyingModel() error
    27  	RemoveModel() error
    28  	SetStatus(status status.Status, message string, data map[string]interface{}) error
    29  }
    30  
    31  // Config holds the resources and configuration necessary to run an
    32  // undertaker worker.
    33  type Config struct {
    34  	Facade        Facade
    35  	Destroyer     environs.CloudDestroyer
    36  	CredentialAPI common.CredentialAPI
    37  }
    38  
    39  // Validate returns an error if the config cannot be expected to drive
    40  // a functional undertaker worker.
    41  func (config Config) Validate() error {
    42  	if config.Facade == nil {
    43  		return errors.NotValidf("nil Facade")
    44  	}
    45  	if config.CredentialAPI == nil {
    46  		return errors.NotValidf("nil CredentialAPI")
    47  	}
    48  	if config.Destroyer == nil {
    49  		return errors.NotValidf("nil Destroyer")
    50  	}
    51  	return nil
    52  }
    53  
    54  // NewUndertaker returns a worker which processes a dying model.
    55  func NewUndertaker(config Config) (*Undertaker, error) {
    56  	if err := config.Validate(); err != nil {
    57  		return nil, errors.Trace(err)
    58  	}
    59  
    60  	u := &Undertaker{
    61  		config: config,
    62  	}
    63  	err := catacomb.Invoke(catacomb.Plan{
    64  		Site: &u.catacomb,
    65  		Work: u.run,
    66  	})
    67  	if err != nil {
    68  		return nil, errors.Trace(err)
    69  	}
    70  	u.setCallCtx(common.NewCloudCallContext(config.CredentialAPI, u.catacomb.Dying))
    71  	return u, nil
    72  }
    73  
    74  type Undertaker struct {
    75  	lock     sync.Mutex
    76  	catacomb catacomb.Catacomb
    77  	config   Config
    78  
    79  	callCtx context.ProviderCallContext
    80  }
    81  
    82  func (u *Undertaker) getCallCtx() context.ProviderCallContext {
    83  	u.lock.Lock()
    84  	defer u.lock.Unlock()
    85  	ctx := u.callCtx
    86  	return ctx
    87  }
    88  
    89  func (u *Undertaker) setCallCtx(ctx context.ProviderCallContext) {
    90  	u.lock.Lock()
    91  	defer u.lock.Unlock()
    92  	u.callCtx = ctx
    93  }
    94  
    95  // Kill is part of the worker.Worker interface.
    96  func (u *Undertaker) Kill() {
    97  	u.catacomb.Kill(nil)
    98  }
    99  
   100  // Wait is part of the worker.Worker interface.
   101  func (u *Undertaker) Wait() error {
   102  	return u.catacomb.Wait()
   103  }
   104  
   105  func (u *Undertaker) run() error {
   106  	result, err := u.config.Facade.ModelInfo()
   107  	if err != nil {
   108  		return errors.Trace(err)
   109  	}
   110  	if result.Error != nil {
   111  		return errors.Trace(result.Error)
   112  	}
   113  	modelInfo := result.Result
   114  
   115  	if modelInfo.Life == params.Alive {
   116  		return errors.Errorf("model still alive")
   117  	}
   118  	if modelInfo.Life == params.Dying {
   119  		// TODO(axw) 2016-04-14 #1570285
   120  		// We should update status with information
   121  		// about the remaining resources here, and
   122  		// also make the worker responsible for
   123  		// checking the emptiness criteria before
   124  		// attempting to remove the model.
   125  		if err := u.setStatus(
   126  			status.Destroying,
   127  			"cleaning up cloud resources",
   128  		); err != nil {
   129  			return errors.Trace(err)
   130  		}
   131  		// Process the dying model. This blocks until the model
   132  		// is dead or the worker is stopped.
   133  		if err := u.processDyingModel(); err != nil {
   134  			return errors.Trace(err)
   135  		}
   136  	}
   137  
   138  	if modelInfo.IsSystem {
   139  		// Nothing to do. We don't destroy environ resources or
   140  		// delete model docs for a controller model, because we're
   141  		// running inside that controller and can't safely clean up
   142  		// our own infrastructure. (That'll be the client's job in
   143  		// the end, once we've reported that we've tidied up what we
   144  		// can, by returning nil here, indicating that we've set it
   145  		// to Dead -- implied by processDyingModel succeeding.)
   146  		return nil
   147  	}
   148  
   149  	// Now the model is known to be hosted and dying, we can tidy up any
   150  	// provider resources it might have used.
   151  	if err := u.setStatus(
   152  		status.Destroying, "tearing down cloud environment",
   153  	); err != nil {
   154  		return errors.Trace(err)
   155  	}
   156  	if err := u.config.Destroyer.Destroy(u.getCallCtx()); err != nil {
   157  		return errors.Trace(err)
   158  	}
   159  	// Finally, the model is going to be dead, and be removed.
   160  	if err := u.config.Facade.RemoveModel(); err != nil {
   161  		return errors.Annotate(err, "cannot remove model")
   162  	}
   163  	return nil
   164  }
   165  
   166  func (u *Undertaker) setStatus(modelStatus status.Status, message string) error {
   167  	return u.config.Facade.SetStatus(modelStatus, message, nil)
   168  }
   169  
   170  func (u *Undertaker) processDyingModel() error {
   171  	watcher, err := u.config.Facade.WatchModelResources()
   172  	if err != nil {
   173  		return errors.Trace(err)
   174  	}
   175  	if err := u.catacomb.Add(watcher); err != nil {
   176  		return errors.Trace(err)
   177  	}
   178  	defer watcher.Kill()
   179  	attempt := 1
   180  	for {
   181  		select {
   182  		case <-u.catacomb.Dying():
   183  			return u.catacomb.ErrDying()
   184  		case <-watcher.Changes():
   185  			err := u.config.Facade.ProcessDyingModel()
   186  			if err == nil {
   187  				// ProcessDyingModel succeeded. We're free to
   188  				// destroy any remaining environ resources.
   189  				return nil
   190  			}
   191  			if !params.IsCodeModelNotEmpty(err) && !params.IsCodeHasHostedModels(err) {
   192  				return errors.Trace(err)
   193  			}
   194  			// Retry once there are changes to the model's resources.
   195  			u.setStatus(
   196  				status.Destroying,
   197  				fmt.Sprintf("attempt %d to destroy model failed (will retry):  %v", attempt, err),
   198  			)
   199  		}
   200  		attempt++
   201  	}
   202  }