github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/common/modeldestroy.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package common
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/clock"
    10  	"github.com/juju/errors"
    11  
    12  	"github.com/juju/juju/apiserver/facades/agent/metricsender"
    13  	"github.com/juju/juju/core/status"
    14  	"github.com/juju/juju/state"
    15  	stateerrors "github.com/juju/juju/state/errors"
    16  )
    17  
    18  var sendMetrics = func(st metricsender.ModelBackend) error {
    19  	ccfg, err := st.ControllerConfig()
    20  	if err != nil {
    21  		return errors.Annotate(err, "failed to get controller config")
    22  	}
    23  	meteringURL := ccfg.MeteringURL()
    24  	cfg, err := st.ModelConfig()
    25  	if err != nil {
    26  		return errors.Annotatef(err, "failed to get model config for %s", st.ModelTag())
    27  	}
    28  
    29  	err = metricsender.SendMetrics(
    30  		st,
    31  		metricsender.DefaultSenderFactory()(meteringURL),
    32  		clock.WallClock,
    33  		metricsender.DefaultMaxBatchesPerSend(),
    34  		cfg.TransmitVendorMetrics(),
    35  	)
    36  	return errors.Trace(err)
    37  }
    38  
    39  // DestroyController sets the controller model to Dying and, if requested,
    40  // schedules cleanups so that all of the hosted models are destroyed, or
    41  // otherwise returns an error indicating that there are hosted models
    42  // remaining.
    43  func DestroyController(
    44  	st ModelManagerBackend,
    45  	destroyHostedModels bool,
    46  	destroyStorage *bool,
    47  	force *bool,
    48  	maxWait *time.Duration,
    49  	modelTimeout *time.Duration,
    50  ) error {
    51  	modelTag := st.ModelTag()
    52  	controllerModelTag := st.ControllerModelTag()
    53  	if modelTag != controllerModelTag {
    54  		return errors.Errorf(
    55  			"expected state for controller model UUID %v, got %v",
    56  			controllerModelTag.Id(),
    57  			modelTag.Id(),
    58  		)
    59  	}
    60  	if destroyHostedModels {
    61  		uuids, err := st.AllModelUUIDs()
    62  		if err != nil {
    63  			return errors.Trace(err)
    64  		}
    65  		for _, uuid := range uuids {
    66  			modelSt, release, err := st.GetBackend(uuid)
    67  			if err != nil {
    68  				if errors.IsNotFound(err) {
    69  					// Model is already in the process of being destroyed.
    70  					continue
    71  				}
    72  				return errors.Trace(err)
    73  			}
    74  			defer release()
    75  
    76  			check := NewBlockChecker(modelSt)
    77  			if err = check.DestroyAllowed(); err != nil {
    78  				return errors.Trace(err)
    79  			}
    80  			err = sendMetrics(modelSt)
    81  			if err != nil {
    82  				logger.Errorf("failed to send leftover metrics: %v", err)
    83  			}
    84  		}
    85  	}
    86  	return destroyModel(st, state.DestroyModelParams{
    87  		DestroyHostedModels: destroyHostedModels,
    88  		DestroyStorage:      destroyStorage,
    89  		Force:               force,
    90  		MaxWait:             MaxWait(maxWait),
    91  		Timeout:             modelTimeout,
    92  	})
    93  }
    94  
    95  // DestroyModel sets the model to Dying, such that the model's resources will
    96  // be destroyed and the model removed from the controller.
    97  func DestroyModel(
    98  	st ModelManagerBackend,
    99  	destroyStorage *bool,
   100  	force *bool,
   101  	maxWait *time.Duration,
   102  	timeout *time.Duration,
   103  ) error {
   104  	return destroyModel(st, state.DestroyModelParams{
   105  		DestroyStorage: destroyStorage,
   106  		Force:          force,
   107  		MaxWait:        MaxWait(maxWait),
   108  		Timeout:        timeout,
   109  	})
   110  }
   111  
   112  func destroyModel(st ModelManagerBackend, args state.DestroyModelParams) error {
   113  	check := NewBlockChecker(st)
   114  	if err := check.DestroyAllowed(); err != nil {
   115  		return errors.Trace(err)
   116  	}
   117  
   118  	model, err := st.Model()
   119  	if err != nil {
   120  		return errors.Trace(err)
   121  	}
   122  	notForcing := args.Force == nil || !*args.Force
   123  	if notForcing {
   124  		// If model status is suspended, then model's cloud credential is invalid.
   125  		modelStatus, err := model.Status()
   126  		if err != nil {
   127  			return errors.Trace(err)
   128  		}
   129  		if modelStatus.Status == status.Suspended {
   130  			return errors.Errorf("invalid cloud credential, use --force")
   131  		}
   132  	}
   133  	if err := model.Destroy(args); err != nil {
   134  		if notForcing {
   135  			return errors.Trace(err)
   136  		}
   137  		logger.Warningf("failed destroying model %v: %v", model.UUID(), err)
   138  		if err := filterNonCriticalErrorForForce(err); err != nil {
   139  			return errors.Trace(err)
   140  		}
   141  	}
   142  
   143  	err = sendMetrics(st)
   144  	if err != nil {
   145  		logger.Errorf("failed to send leftover metrics: %v", err)
   146  	}
   147  
   148  	// Return to the caller. If it's the CLI, it will finish up by calling the
   149  	// provider's Destroy method, which will destroy the controllers, any
   150  	// straggler instances, and other provider-specific resources. Once all
   151  	// resources are torn down, the Undertaker worker handles the removal of
   152  	// the model.
   153  	return nil
   154  }
   155  
   156  func filterNonCriticalErrorForForce(err error) error {
   157  	if errors.Is(err, stateerrors.PersistentStorageError) {
   158  		return err
   159  	}
   160  	return nil
   161  }