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

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package migration
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/version"
    11  	"gopkg.in/juju/charm.v6"
    12  	"gopkg.in/juju/names.v2"
    13  
    14  	"github.com/juju/juju/apiserver/common"
    15  	coremigration "github.com/juju/juju/core/migration"
    16  	"github.com/juju/juju/core/presence"
    17  	"github.com/juju/juju/core/status"
    18  	"github.com/juju/juju/resource"
    19  	"github.com/juju/juju/state"
    20  	"github.com/juju/juju/tools"
    21  )
    22  
    23  // PrecheckBackend defines the interface to query Juju's state
    24  // for migration prechecks.
    25  type PrecheckBackend interface {
    26  	AgentVersion() (version.Number, error)
    27  	NeedsCleanup() (bool, error)
    28  	Model() (PrecheckModel, error)
    29  	AllModelUUIDs() ([]string, error)
    30  	IsUpgrading() (bool, error)
    31  	IsMigrationActive(string) (bool, error)
    32  	AllMachines() ([]PrecheckMachine, error)
    33  	AllApplications() ([]PrecheckApplication, error)
    34  	AllRelations() ([]PrecheckRelation, error)
    35  	ControllerBackend() (PrecheckBackend, error)
    36  	CloudCredential(tag names.CloudCredentialTag) (state.Credential, error)
    37  	ListPendingResources(string) ([]resource.Resource, error)
    38  }
    39  
    40  // Pool defines the interface to a StatePool used by the migration
    41  // prechecks.
    42  type Pool interface {
    43  	GetModel(string) (PrecheckModel, func(), error)
    44  }
    45  
    46  // PrecheckModel describes the state interface a model as needed by
    47  // the migration prechecks.
    48  type PrecheckModel interface {
    49  	UUID() string
    50  	Name() string
    51  	Type() state.ModelType
    52  	Owner() names.UserTag
    53  	Life() state.Life
    54  	MigrationMode() state.MigrationMode
    55  	CloudCredential() (names.CloudCredentialTag, bool)
    56  }
    57  
    58  // PrecheckMachine describes the state interface for a machine needed
    59  // by migration prechecks.
    60  type PrecheckMachine interface {
    61  	Id() string
    62  	AgentTools() (*tools.Tools, error)
    63  	Life() state.Life
    64  	Status() (status.StatusInfo, error)
    65  	AgentPresence() (bool, error)
    66  	InstanceStatus() (status.StatusInfo, error)
    67  	ShouldRebootOrShutdown() (state.RebootAction, error)
    68  }
    69  
    70  // PrecheckApplication describes the state interface for an
    71  // application needed by migration prechecks.
    72  type PrecheckApplication interface {
    73  	Name() string
    74  	Life() state.Life
    75  	CharmURL() (*charm.URL, bool)
    76  	AllUnits() ([]PrecheckUnit, error)
    77  	MinUnits() int
    78  }
    79  
    80  // PrecheckUnit describes state interface for a unit needed by
    81  // migration prechecks.
    82  type PrecheckUnit interface {
    83  	Name() string
    84  	AgentTools() (*tools.Tools, error)
    85  	Life() state.Life
    86  	CharmURL() (*charm.URL, bool)
    87  	AgentStatus() (status.StatusInfo, error)
    88  	Status() (status.StatusInfo, error)
    89  	AgentPresence() (bool, error)
    90  	ShouldBeAssigned() bool
    91  }
    92  
    93  // PrecheckRelation describes the state interface for relations needed
    94  // for prechecks.
    95  type PrecheckRelation interface {
    96  	String() string
    97  	IsCrossModel() (bool, error)
    98  	Endpoints() []state.Endpoint
    99  	Unit(PrecheckUnit) (PrecheckRelationUnit, error)
   100  }
   101  
   102  // PrecheckRelationUnit describes the interface for relation units
   103  // needed for migration prechecks.
   104  type PrecheckRelationUnit interface {
   105  	Valid() (bool, error)
   106  	InScope() (bool, error)
   107  }
   108  
   109  // ModelPresence represents the API server connections for a model.
   110  type ModelPresence interface {
   111  	// For a given non controller agent, return the Status for that agent.
   112  	AgentStatus(agent string) (presence.Status, error)
   113  }
   114  
   115  // SourcePrecheck checks the state of the source controller to make
   116  // sure that the preconditions for model migration are met. The
   117  // backend provided must be for the model to be migrated.
   118  func SourcePrecheck(
   119  	backend PrecheckBackend,
   120  	modelPresence ModelPresence,
   121  	controllerPresence ModelPresence,
   122  ) error {
   123  	ctx := precheckContext{backend, modelPresence}
   124  	if err := ctx.checkModel(); err != nil {
   125  		return errors.Trace(err)
   126  	}
   127  
   128  	if err := ctx.checkMachines(); err != nil {
   129  		return errors.Trace(err)
   130  	}
   131  
   132  	appUnits, err := ctx.checkApplications()
   133  	if err != nil {
   134  		return errors.Trace(err)
   135  	}
   136  
   137  	if err := ctx.checkRelations(appUnits); err != nil {
   138  		return errors.Trace(err)
   139  	}
   140  
   141  	if cleanupNeeded, err := backend.NeedsCleanup(); err != nil {
   142  		return errors.Annotate(err, "checking cleanups")
   143  	} else if cleanupNeeded {
   144  		return errors.New("cleanup needed")
   145  	}
   146  
   147  	// Check the source controller.
   148  	controllerBackend, err := backend.ControllerBackend()
   149  	if err != nil {
   150  		return errors.Trace(err)
   151  	}
   152  	controllerCtx := precheckContext{controllerBackend, controllerPresence}
   153  	if err := controllerCtx.checkController(); err != nil {
   154  		return errors.Annotate(err, "controller")
   155  	}
   156  	return nil
   157  }
   158  
   159  type precheckContext struct {
   160  	backend  PrecheckBackend
   161  	presence ModelPresence
   162  }
   163  
   164  func (ctx *precheckContext) checkModel() error {
   165  	model, err := ctx.backend.Model()
   166  	if err != nil {
   167  		return errors.Annotate(err, "retrieving model")
   168  	}
   169  	if model.Life() != state.Alive {
   170  		return errors.Errorf("model is %s", model.Life())
   171  	}
   172  	if model.MigrationMode() == state.MigrationModeImporting {
   173  		return errors.New("model is being imported as part of another migration")
   174  	}
   175  	if credTag, found := model.CloudCredential(); found {
   176  		creds, err := ctx.backend.CloudCredential(credTag)
   177  		if err != nil {
   178  			return errors.Trace(err)
   179  		}
   180  		if creds.Revoked {
   181  			return errors.New("model has revoked credentials")
   182  		}
   183  	}
   184  	return nil
   185  }
   186  
   187  // TargetPrecheck checks the state of the target controller to make
   188  // sure that the preconditions for model migration are met. The
   189  // backend provided must be for the target controller.
   190  func TargetPrecheck(backend PrecheckBackend, pool Pool, modelInfo coremigration.ModelInfo, presence ModelPresence) error {
   191  	if err := modelInfo.Validate(); err != nil {
   192  		return errors.Trace(err)
   193  	}
   194  
   195  	// This check is necessary because there is a window between the
   196  	// REAP phase and then end of the DONE phase where a model's
   197  	// documents have been deleted but the migration isn't quite done
   198  	// yet. Migrating a model back into the controller during this
   199  	// window can upset the migrationmaster worker.
   200  	//
   201  	// See also https://lpad.tv/1611391
   202  	if migrating, err := backend.IsMigrationActive(modelInfo.UUID); err != nil {
   203  		return errors.Annotate(err, "checking for active migration")
   204  	} else if migrating {
   205  		return errors.New("model is being migrated out of target controller")
   206  	}
   207  
   208  	controllerVersion, err := backend.AgentVersion()
   209  	if err != nil {
   210  		return errors.Annotate(err, "retrieving model version")
   211  	}
   212  
   213  	if controllerVersion.Compare(modelInfo.AgentVersion) < 0 {
   214  		return errors.Errorf("model has higher version than target controller (%s > %s)",
   215  			modelInfo.AgentVersion, controllerVersion)
   216  	}
   217  
   218  	if !controllerVersionCompatible(modelInfo.ControllerAgentVersion, controllerVersion) {
   219  		return errors.Errorf("source controller has higher version than target controller (%s > %s)",
   220  			modelInfo.ControllerAgentVersion, controllerVersion)
   221  	}
   222  
   223  	controllerCtx := precheckContext{backend, presence}
   224  	if err := controllerCtx.checkController(); err != nil {
   225  		return errors.Trace(err)
   226  	}
   227  
   228  	// Check for conflicts with existing models
   229  	modelUUIDs, err := backend.AllModelUUIDs()
   230  	if err != nil {
   231  		return errors.Annotate(err, "retrieving models")
   232  	}
   233  	for _, modelUUID := range modelUUIDs {
   234  		model, release, err := pool.GetModel(modelUUID)
   235  		if err != nil {
   236  			return errors.Trace(err)
   237  		}
   238  		defer release()
   239  
   240  		// If the model is importing then it's probably left behind
   241  		// from a previous migration attempt. It will be removed
   242  		// before the next import.
   243  		if model.UUID() == modelInfo.UUID && model.MigrationMode() != state.MigrationModeImporting {
   244  			return errors.Errorf("model with same UUID already exists (%s)", modelInfo.UUID)
   245  		}
   246  		if model.Name() == modelInfo.Name && model.Owner() == modelInfo.Owner {
   247  			return errors.Errorf("model named %q already exists", model.Name())
   248  		}
   249  	}
   250  
   251  	return nil
   252  }
   253  
   254  func controllerVersionCompatible(sourceVersion, targetVersion version.Number) bool {
   255  	// Compare source controller version to target controller version, only
   256  	// considering major and minor version numbers. Downgrades between
   257  	// patch, build releases for a given major.minor release are
   258  	// ok. Tag differences are ok too.
   259  	sourceVersion = versionToMajMin(sourceVersion)
   260  	targetVersion = versionToMajMin(targetVersion)
   261  	return sourceVersion.Compare(targetVersion) <= 0
   262  }
   263  
   264  func versionToMajMin(ver version.Number) version.Number {
   265  	ver.Patch = 0
   266  	ver.Build = 0
   267  	ver.Tag = ""
   268  	return ver
   269  }
   270  
   271  func (ctx *precheckContext) checkController() error {
   272  	model, err := ctx.backend.Model()
   273  	if err != nil {
   274  		return errors.Annotate(err, "retrieving model")
   275  	}
   276  	if model.Life() != state.Alive {
   277  		return errors.Errorf("model is %s", model.Life())
   278  	}
   279  
   280  	if upgrading, err := ctx.backend.IsUpgrading(); err != nil {
   281  		return errors.Annotate(err, "checking for upgrades")
   282  	} else if upgrading {
   283  		return errors.New("upgrade in progress")
   284  	}
   285  
   286  	return errors.Trace(ctx.checkMachines())
   287  }
   288  
   289  func (ctx *precheckContext) checkMachines() error {
   290  	modelVersion, err := ctx.backend.AgentVersion()
   291  	if err != nil {
   292  		return errors.Annotate(err, "retrieving model version")
   293  	}
   294  
   295  	machines, err := ctx.backend.AllMachines()
   296  	if err != nil {
   297  		return errors.Annotate(err, "retrieving machines")
   298  	}
   299  	modelPresenceContext := common.ModelPresenceContext{ctx.presence}
   300  	for _, machine := range machines {
   301  		if machine.Life() != state.Alive {
   302  			return errors.Errorf("machine %s is %s", machine.Id(), machine.Life())
   303  		}
   304  
   305  		if statusInfo, err := machine.InstanceStatus(); err != nil {
   306  			return errors.Annotatef(err, "retrieving machine %s instance status", machine.Id())
   307  		} else if statusInfo.Status != status.Running {
   308  			return newStatusError("machine %s not running", machine.Id(), statusInfo.Status)
   309  		}
   310  
   311  		if statusInfo, err := modelPresenceContext.MachineStatus(machine); err != nil {
   312  			return errors.Annotatef(err, "retrieving machine %s status", machine.Id())
   313  		} else if statusInfo.Status != status.Started {
   314  			return newStatusError("machine %s agent not functioning at this time",
   315  				machine.Id(), statusInfo.Status)
   316  		}
   317  
   318  		if rebootAction, err := machine.ShouldRebootOrShutdown(); err != nil {
   319  			return errors.Annotatef(err, "retrieving machine %s reboot status", machine.Id())
   320  		} else if rebootAction != state.ShouldDoNothing {
   321  			return errors.Errorf("machine %s is scheduled to %s", machine.Id(), rebootAction)
   322  		}
   323  
   324  		if err := checkAgentTools(modelVersion, machine, "machine "+machine.Id()); err != nil {
   325  			return errors.Trace(err)
   326  		}
   327  	}
   328  	return nil
   329  }
   330  
   331  func (ctx *precheckContext) checkApplications() (map[string][]PrecheckUnit, error) {
   332  	modelVersion, err := ctx.backend.AgentVersion()
   333  	if err != nil {
   334  		return nil, errors.Annotate(err, "retrieving model version")
   335  	}
   336  	apps, err := ctx.backend.AllApplications()
   337  	if err != nil {
   338  		return nil, errors.Annotate(err, "retrieving applications")
   339  	}
   340  
   341  	model, err := ctx.backend.Model()
   342  	if err != nil {
   343  		return nil, errors.Annotate(err, "retrieving model")
   344  	}
   345  	appUnits := make(map[string][]PrecheckUnit, len(apps))
   346  	for _, app := range apps {
   347  		if app.Life() != state.Alive {
   348  			return nil, errors.Errorf("application %s is %s", app.Name(), app.Life())
   349  		}
   350  		units, err := app.AllUnits()
   351  		if err != nil {
   352  			return nil, errors.Annotatef(err, "retrieving units for %s", app.Name())
   353  		}
   354  		err = ctx.checkUnits(app, units, modelVersion, model.Type())
   355  		if err != nil {
   356  			return nil, errors.Trace(err)
   357  		}
   358  		appUnits[app.Name()] = units
   359  	}
   360  	return appUnits, nil
   361  }
   362  
   363  func (ctx *precheckContext) checkUnits(app PrecheckApplication, units []PrecheckUnit, modelVersion version.Number, modelType state.ModelType) error {
   364  	if len(units) < app.MinUnits() {
   365  		return errors.Errorf("application %s is below its minimum units threshold", app.Name())
   366  	}
   367  
   368  	appCharmURL, _ := app.CharmURL()
   369  
   370  	for _, unit := range units {
   371  		if unit.Life() != state.Alive {
   372  			return errors.Errorf("unit %s is %s", unit.Name(), unit.Life())
   373  		}
   374  
   375  		if err := ctx.checkUnitAgentStatus(unit); err != nil {
   376  			return errors.Trace(err)
   377  		}
   378  
   379  		if modelType == state.ModelTypeIAAS {
   380  			if err := checkAgentTools(modelVersion, unit, "unit "+unit.Name()); err != nil {
   381  				return errors.Trace(err)
   382  			}
   383  		}
   384  
   385  		unitCharmURL, _ := unit.CharmURL()
   386  		if appCharmURL.String() != unitCharmURL.String() {
   387  			return errors.Errorf("unit %s is upgrading", unit.Name())
   388  		}
   389  	}
   390  	return nil
   391  }
   392  
   393  func (ctx *precheckContext) checkUnitAgentStatus(unit PrecheckUnit) error {
   394  	modelPresenceContext := common.ModelPresenceContext{ctx.presence}
   395  	statusData, _ := modelPresenceContext.UnitStatus(unit)
   396  	if statusData.Err != nil {
   397  		return errors.Annotatef(statusData.Err, "retrieving unit %s status", unit.Name())
   398  	}
   399  	agentStatus := statusData.Status.Status
   400  	switch agentStatus {
   401  	case status.Idle, status.Executing:
   402  		// These two are fine.
   403  	default:
   404  		return newStatusError("unit %s not idle or executing", unit.Name(), agentStatus)
   405  	}
   406  	return nil
   407  }
   408  
   409  func checkAgentTools(modelVersion version.Number, agent agentToolsGetter, agentLabel string) error {
   410  	tools, err := agent.AgentTools()
   411  	if err != nil {
   412  		return errors.Annotatef(err, "retrieving agent binaries for %s", agentLabel)
   413  	}
   414  	agentVersion := tools.Version.Number
   415  	if agentVersion != modelVersion {
   416  		return errors.Errorf("%s agent binaries don't match model (%s != %s)",
   417  			agentLabel, agentVersion, modelVersion)
   418  	}
   419  	return nil
   420  }
   421  
   422  type agentToolsGetter interface {
   423  	AgentTools() (*tools.Tools, error)
   424  }
   425  
   426  func newStatusError(format, id string, s status.Status) error {
   427  	msg := fmt.Sprintf(format, id)
   428  	if s != status.Empty {
   429  		msg += fmt.Sprintf(" (%s)", s)
   430  	}
   431  	return errors.New(msg)
   432  }
   433  
   434  func (ctx *precheckContext) checkRelations(appUnits map[string][]PrecheckUnit) error {
   435  	relations, err := ctx.backend.AllRelations()
   436  	if err != nil {
   437  		return errors.Annotate(err, "retrieving model relations")
   438  	}
   439  	for _, rel := range relations {
   440  		// We expect a relationScope and settings for each of the
   441  		// units of the specified application, unless it is a
   442  		// remote application.
   443  		crossModel, err := rel.IsCrossModel()
   444  		if err != nil {
   445  			return errors.Annotatef(err, "checking whether relation %s is cross-model", rel)
   446  		}
   447  		if crossModel {
   448  			continue
   449  		}
   450  		for _, ep := range rel.Endpoints() {
   451  			for _, unit := range appUnits[ep.ApplicationName] {
   452  				ru, err := rel.Unit(unit)
   453  				if err != nil {
   454  					return errors.Trace(err)
   455  				}
   456  				valid, err := ru.Valid()
   457  				if err != nil {
   458  					return errors.Trace(err)
   459  				}
   460  				if !valid {
   461  					continue
   462  				}
   463  				inScope, err := ru.InScope()
   464  				if err != nil {
   465  					return errors.Trace(err)
   466  				}
   467  				if !inScope {
   468  					return errors.Errorf("unit %s hasn't joined relation %s yet", unit.Name(), rel)
   469  				}
   470  			}
   471  		}
   472  	}
   473  	return nil
   474  }