github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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-unstable"
    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/state"
    17  	"github.com/juju/juju/status"
    18  	"github.com/juju/juju/tools"
    19  )
    20  
    21  // PrecheckBackend defines the interface to query Juju's state
    22  // for migration prechecks.
    23  type PrecheckBackend interface {
    24  	AgentVersion() (version.Number, error)
    25  	NeedsCleanup() (bool, error)
    26  	Model() (PrecheckModel, error)
    27  	AllModels() ([]PrecheckModel, error)
    28  	IsUpgrading() (bool, error)
    29  	IsMigrationActive(string) (bool, error)
    30  	AllMachines() ([]PrecheckMachine, error)
    31  	AllApplications() ([]PrecheckApplication, error)
    32  	ControllerBackend() (PrecheckBackend, error)
    33  }
    34  
    35  // PrecheckModel describes the state interface a model as needed by
    36  // the migration prechecks.
    37  type PrecheckModel interface {
    38  	UUID() string
    39  	Name() string
    40  	Owner() names.UserTag
    41  	Life() state.Life
    42  	MigrationMode() state.MigrationMode
    43  }
    44  
    45  // PrecheckMachine describes the state interface for a machine needed
    46  // by migration prechecks.
    47  type PrecheckMachine interface {
    48  	Id() string
    49  	AgentTools() (*tools.Tools, error)
    50  	Life() state.Life
    51  	Status() (status.StatusInfo, error)
    52  	AgentPresence() (bool, error)
    53  	InstanceStatus() (status.StatusInfo, error)
    54  	ShouldRebootOrShutdown() (state.RebootAction, error)
    55  }
    56  
    57  // PrecheckApplication describes the state interface for an
    58  // application needed by migration prechecks.
    59  type PrecheckApplication interface {
    60  	Name() string
    61  	Life() state.Life
    62  	CharmURL() (*charm.URL, bool)
    63  	AllUnits() ([]PrecheckUnit, error)
    64  	MinUnits() int
    65  }
    66  
    67  // PrecheckUnit describes state interface for a unit needed by
    68  // migration prechecks.
    69  type PrecheckUnit interface {
    70  	Name() string
    71  	AgentTools() (*tools.Tools, error)
    72  	Life() state.Life
    73  	CharmURL() (*charm.URL, bool)
    74  	AgentStatus() (status.StatusInfo, error)
    75  	Status() (status.StatusInfo, error)
    76  	AgentPresence() (bool, error)
    77  }
    78  
    79  // SourcePrecheck checks the state of the source controller to make
    80  // sure that the preconditions for model migration are met. The
    81  // backend provided must be for the model to be migrated.
    82  func SourcePrecheck(backend PrecheckBackend) error {
    83  	if err := checkModel(backend); err != nil {
    84  		return errors.Trace(err)
    85  	}
    86  
    87  	if err := checkMachines(backend); err != nil {
    88  		return errors.Trace(err)
    89  	}
    90  
    91  	if err := checkApplications(backend); err != nil {
    92  		return errors.Trace(err)
    93  	}
    94  
    95  	if cleanupNeeded, err := backend.NeedsCleanup(); err != nil {
    96  		return errors.Annotate(err, "checking cleanups")
    97  	} else if cleanupNeeded {
    98  		return errors.New("cleanup needed")
    99  	}
   100  
   101  	// Check the source controller.
   102  	controllerBackend, err := backend.ControllerBackend()
   103  	if err != nil {
   104  		return errors.Trace(err)
   105  	}
   106  	if err := checkController(controllerBackend); err != nil {
   107  		return errors.Annotate(err, "controller")
   108  	}
   109  	return nil
   110  }
   111  
   112  func checkModel(backend PrecheckBackend) error {
   113  	model, err := backend.Model()
   114  	if err != nil {
   115  		return errors.Annotate(err, "retrieving model")
   116  	}
   117  	if model.Life() != state.Alive {
   118  		return errors.Errorf("model is %s", model.Life())
   119  	}
   120  	if model.MigrationMode() == state.MigrationModeImporting {
   121  		return errors.New("model is being imported as part of another migration")
   122  	}
   123  	return nil
   124  }
   125  
   126  // TargetPrecheck checks the state of the target controller to make
   127  // sure that the preconditions for model migration are met. The
   128  // backend provided must be for the target controller.
   129  func TargetPrecheck(backend PrecheckBackend, modelInfo coremigration.ModelInfo) error {
   130  	if err := modelInfo.Validate(); err != nil {
   131  		return errors.Trace(err)
   132  	}
   133  
   134  	// This check is necessary because there is a window between the
   135  	// REAP phase and then end of the DONE phase where a model's
   136  	// documents have been deleted but the migration isn't quite done
   137  	// yet. Migrating a model back into the controller during this
   138  	// window can upset the migrationmaster worker.
   139  	//
   140  	// See also https://lpad.tv/1611391
   141  	if migrating, err := backend.IsMigrationActive(modelInfo.UUID); err != nil {
   142  		return errors.Annotate(err, "checking for active migration")
   143  	} else if migrating {
   144  		return errors.New("model is being migrated out of target controller")
   145  	}
   146  
   147  	controllerVersion, err := backend.AgentVersion()
   148  	if err != nil {
   149  		return errors.Annotate(err, "retrieving model version")
   150  	}
   151  
   152  	if controllerVersion.Compare(modelInfo.AgentVersion) < 0 {
   153  		return errors.Errorf("model has higher version than target controller (%s > %s)",
   154  			modelInfo.AgentVersion, controllerVersion)
   155  	}
   156  
   157  	if err := checkController(backend); err != nil {
   158  		return errors.Trace(err)
   159  	}
   160  
   161  	// Check for conflicts with existing models
   162  	models, err := backend.AllModels()
   163  	if err != nil {
   164  		return errors.Annotate(err, "retrieving models")
   165  	}
   166  	canonicalOwner := modelInfo.Owner.Canonical()
   167  	for _, model := range models {
   168  		// If the model is importing then it's probably left behind
   169  		// from a previous migration attempt. It will be removed
   170  		// before the next import.
   171  		if model.UUID() == modelInfo.UUID && model.MigrationMode() != state.MigrationModeImporting {
   172  			return errors.Errorf("model with same UUID already exists (%s)", modelInfo.UUID)
   173  		}
   174  		if model.Name() == modelInfo.Name && model.Owner().Canonical() == canonicalOwner {
   175  			return errors.Errorf("model named %q already exists", model.Name())
   176  		}
   177  	}
   178  
   179  	return nil
   180  }
   181  
   182  func checkController(backend PrecheckBackend) error {
   183  	model, err := backend.Model()
   184  	if err != nil {
   185  		return errors.Annotate(err, "retrieving model")
   186  	}
   187  	if model.Life() != state.Alive {
   188  		return errors.Errorf("model is %s", model.Life())
   189  	}
   190  
   191  	if upgrading, err := backend.IsUpgrading(); err != nil {
   192  		return errors.Annotate(err, "checking for upgrades")
   193  	} else if upgrading {
   194  		return errors.New("upgrade in progress")
   195  	}
   196  
   197  	err = checkMachines(backend)
   198  	return errors.Trace(err)
   199  }
   200  
   201  func checkMachines(backend PrecheckBackend) error {
   202  	modelVersion, err := backend.AgentVersion()
   203  	if err != nil {
   204  		return errors.Annotate(err, "retrieving model version")
   205  	}
   206  
   207  	machines, err := backend.AllMachines()
   208  	if err != nil {
   209  		return errors.Annotate(err, "retrieving machines")
   210  	}
   211  	for _, machine := range machines {
   212  		if machine.Life() != state.Alive {
   213  			return errors.Errorf("machine %s is %s", machine.Id(), machine.Life())
   214  		}
   215  
   216  		if statusInfo, err := machine.InstanceStatus(); err != nil {
   217  			return errors.Annotatef(err, "retrieving machine %s instance status", machine.Id())
   218  		} else if statusInfo.Status != status.Running {
   219  			return newStatusError("machine %s not running", machine.Id(), statusInfo.Status)
   220  		}
   221  
   222  		if statusInfo, err := common.MachineStatus(machine); err != nil {
   223  			return errors.Annotatef(err, "retrieving machine %s status", machine.Id())
   224  		} else if statusInfo.Status != status.Started {
   225  			return newStatusError("machine %s agent not functioning at this time",
   226  				machine.Id(), statusInfo.Status)
   227  		}
   228  
   229  		if rebootAction, err := machine.ShouldRebootOrShutdown(); err != nil {
   230  			return errors.Annotatef(err, "retrieving machine %s reboot status", machine.Id())
   231  		} else if rebootAction != state.ShouldDoNothing {
   232  			return errors.Errorf("machine %s is scheduled to %s", machine.Id(), rebootAction)
   233  		}
   234  
   235  		if err := checkAgentTools(modelVersion, machine, "machine "+machine.Id()); err != nil {
   236  			return errors.Trace(err)
   237  		}
   238  	}
   239  	return nil
   240  }
   241  
   242  func checkApplications(backend PrecheckBackend) error {
   243  	modelVersion, err := backend.AgentVersion()
   244  	if err != nil {
   245  		return errors.Annotate(err, "retrieving model version")
   246  	}
   247  	apps, err := backend.AllApplications()
   248  	if err != nil {
   249  		return errors.Annotate(err, "retrieving applications")
   250  	}
   251  	for _, app := range apps {
   252  		if app.Life() != state.Alive {
   253  			return errors.Errorf("application %s is %s", app.Name(), app.Life())
   254  		}
   255  		err := checkUnits(app, modelVersion)
   256  		if err != nil {
   257  			return errors.Trace(err)
   258  		}
   259  	}
   260  	return nil
   261  }
   262  
   263  func checkUnits(app PrecheckApplication, modelVersion version.Number) error {
   264  	units, err := app.AllUnits()
   265  	if err != nil {
   266  		return errors.Annotatef(err, "retrieving units for %s", app.Name())
   267  	}
   268  	if len(units) < app.MinUnits() {
   269  		return errors.Errorf("application %s is below its minimum units threshold", app.Name())
   270  	}
   271  
   272  	appCharmURL, _ := app.CharmURL()
   273  
   274  	for _, unit := range units {
   275  		if unit.Life() != state.Alive {
   276  			return errors.Errorf("unit %s is %s", unit.Name(), unit.Life())
   277  		}
   278  
   279  		if err := checkUnitAgentStatus(unit); err != nil {
   280  			return errors.Trace(err)
   281  		}
   282  
   283  		if err := checkAgentTools(modelVersion, unit, "unit "+unit.Name()); err != nil {
   284  			return errors.Trace(err)
   285  		}
   286  
   287  		unitCharmURL, _ := unit.CharmURL()
   288  		if appCharmURL.String() != unitCharmURL.String() {
   289  			return errors.Errorf("unit %s is upgrading", unit.Name())
   290  		}
   291  	}
   292  	return nil
   293  }
   294  
   295  func checkUnitAgentStatus(unit PrecheckUnit) error {
   296  	statusData, _ := common.UnitStatus(unit)
   297  	if statusData.Err != nil {
   298  		return errors.Annotatef(statusData.Err, "retrieving unit %s status", unit.Name())
   299  	}
   300  	agentStatus := statusData.Status.Status
   301  	if agentStatus != status.Idle {
   302  		return newStatusError("unit %s not idle", unit.Name(), agentStatus)
   303  	}
   304  	return nil
   305  }
   306  
   307  func checkAgentTools(modelVersion version.Number, agent agentToolsGetter, agentLabel string) error {
   308  	tools, err := agent.AgentTools()
   309  	if err != nil {
   310  		return errors.Annotatef(err, "retrieving tools for %s", agentLabel)
   311  	}
   312  	agentVersion := tools.Version.Number
   313  	if agentVersion != modelVersion {
   314  		return errors.Errorf("%s tools don't match model (%s != %s)",
   315  			agentLabel, agentVersion, modelVersion)
   316  	}
   317  	return nil
   318  }
   319  
   320  type agentToolsGetter interface {
   321  	AgentTools() (*tools.Tools, error)
   322  }
   323  
   324  func newStatusError(format, id string, s status.Status) error {
   325  	msg := fmt.Sprintf(format, id)
   326  	if s != status.Empty {
   327  		msg += fmt.Sprintf(" (%s)", s)
   328  	}
   329  	return errors.New(msg)
   330  }