// RestoreError makes a best effort at converting the given error // back into an error originally converted by ServerError(). If the // error could not be converted then false is returned. func RestoreError(err error) (error, bool) { err = errors.Cause(err) if apiErr, ok := err.(*params.Error); !ok { return err, false } else if apiErr == nil { return nil, true } if params.ErrCode(err) == "" { return err, false } msg := err.Error() if singleton, ok := singletonError(err); ok { return singleton, true } // TODO(ericsnow) Support the other error types handled by ServerError(). switch { case params.IsCodeUnauthorized(err): return errors.NewUnauthorized(nil, msg), true case params.IsCodeNotFound(err): // TODO(ericsnow) UnknownModelError should be handled here too. // ...by parsing msg? return errors.NewNotFound(nil, msg), true case params.IsCodeAlreadyExists(err): return errors.NewAlreadyExists(nil, msg), true case params.IsCodeNotAssigned(err): return errors.NewNotAssigned(nil, msg), true case params.IsCodeHasAssignedUnits(err): // TODO(ericsnow) Handle state.HasAssignedUnitsError here. // ...by parsing msg? return err, false case params.IsCodeNoAddressSet(err): // TODO(ericsnow) Handle isNoAddressSetError here. // ...by parsing msg? return err, false case params.IsCodeNotProvisioned(err): return errors.NewNotProvisioned(nil, msg), true case params.IsCodeUpgradeInProgress(err): // TODO(ericsnow) Handle state.UpgradeInProgressError here. // ...by parsing msg? return err, false case params.IsCodeMachineHasAttachedStorage(err): // TODO(ericsnow) Handle state.HasAttachmentsError here. // ...by parsing msg? return err, false case params.IsCodeNotSupported(err): return errors.NewNotSupported(nil, msg), true case params.IsBadRequest(err): return errors.NewBadRequest(nil, msg), true case params.IsMethodNotAllowed(err): return errors.NewMethodNotAllowed(nil, msg), true case params.ErrCode(err) == params.CodeDischargeRequired: // TODO(ericsnow) Handle DischargeRequiredError here. return err, false default: return err, false } }
// restore is responsible for triggering the whole restore process in a remote // machine. The backup information for the process should already be in the // server and loaded in the backup storage under the backupId id. // It takes backupId as the identifier for the remote backup file and a // client connection factory newClient (newClient should no longer be // necessary when lp:1399722 is sorted out). func (c *Client) restore(backupId string, newClient ClientConnection) error { var err, remoteError error // Restore restoreArgs := params.RestoreArgs{ BackupId: backupId, } cleanExit := false for a := restoreStrategy.Start(); a.Next(); { logger.Debugf("Attempting Restore of %q", backupId) var restoreClient *Client restoreClient, err = newClient() if err != nil { return errors.Trace(err) } err, remoteError = restoreAttempt(restoreClient, restoreArgs) // A ShutdownErr signals that Restore almost certainly finished and // triggered Exit. if (err == nil || rpc.IsShutdownErr(err)) && remoteError == nil { cleanExit = true break } if !params.IsCodeUpgradeInProgress(err) || remoteError != nil { finishErr := finishRestore(newClient) logger.Errorf("could not clean up after failed restore attempt: %v", finishErr) return errors.Annotatef(err, "cannot perform restore: %v", remoteError) } } if !cleanExit { finishErr := finishRestore(newClient) if finishErr != nil { logger.Errorf("could not clean up failed restore: %v", finishErr) } return errors.Annotatef(err, "cannot perform restore: %v", remoteError) } err = finishRestore(newClient) if err != nil { return errors.Annotatef(err, "could not finish restore process: %v", remoteError) } return nil }
// finishRestore since Restore call will end up with a reset // state server, finish restore will check that the the newly // placed state server has the mark of restore complete. // upstart should have restarted the api server so we reconnect. func finishRestore(newClient ClientConnection) error { var err, remoteError error for a := restoreStrategy.Start(); a.Next(); { logger.Debugf("Attempting finishRestore") finishClient, finishClientCloser, err := newClient() if err != nil { return errors.Trace(err) } if err, remoteError = finishAttempt(finishClient, finishClientCloser); err == nil { return nil } if !params.IsCodeUpgradeInProgress(remoteError) { return errors.Annotatef(err, "cannot complete restore: %v", remoteError) } } return errors.Annotatef(err, "cannot complete restore: %v", remoteError) }
// restore is responsible for triggering the whole restore process in a remote // machine. The backup information for the process should already be in the // server and loaded in the backup storage under the backupId id. // It takes backupId as the identifier for the remote backup file and a // client connection factory newClient (newClient should no longer be // necessary when lp:1399722 is sorted out). func (c *Client) restore(backupId string, newClient ClientConnection) error { var err, remoteError error // Restore restoreArgs := params.RestoreArgs{ BackupId: backupId, } for a := restoreStrategy.Start(); a.Next(); { logger.Debugf("Attempting Restore of %q", backupId) restoreClient, restoreClientCloser, err := newClient() if err != nil { return errors.Trace(err) } err, remoteError = restoreAttempt(restoreClient, restoreClientCloser, restoreArgs) // This signals that Restore almost certainly finished and // triggered Exit. if err == rpc.ErrShutdown && remoteError == nil { break } if err != nil && !params.IsCodeUpgradeInProgress(remoteError) { finishErr := finishRestore(newClient) logger.Errorf("could not exit restoring status: %v", finishErr) return errors.Annotatef(err, "cannot perform restore: %v", remoteError) } } if err != rpc.ErrShutdown { finishErr := finishRestore(newClient) if finishErr != nil { logger.Errorf("could not exit restoring status: %v", finishErr) } return errors.Annotatef(err, "cannot perform restore: %v", remoteError) } err = finishRestore(newClient) if err != nil { return errors.Annotatef(err, "could not finish restore process: %v", remoteError) } return nil }
func prepareRestore(newClient ClientConnection) error { var err, remoteError error // PrepareRestore puts the server into a state that only allows // for restore to be called. This is to avoid the data loss if // users try to perform actions that are going to be overwritten // by restore. for a := restoreStrategy.Start(); a.Next(); { logger.Debugf("Will attempt to call 'PrepareRestore'") client, clientCloser, clientErr := newClient() if clientErr != nil { return errors.Trace(clientErr) } if err, remoteError = prepareAttempt(client, clientCloser); err == nil { return nil } if !params.IsCodeUpgradeInProgress(remoteError) { return errors.Annotatef(err, "could not start prepare restore mode, server returned: %v", remoteError) } } return errors.Annotatef(err, "could not start restore process: %v", remoteError) }
func (s *upgradeSuite) checkLoginToAPIAsUser(c *gc.C, conf agent.Config, expectFullApi bool) { var err error // Multiple attempts may be necessary because there is a small gap // between the post-upgrade version being written to the agent's // config (as observed by waitForUpgradeToFinish) and the end of // "upgrade mode" (i.e. when the agent's UpgradeComplete channel // is closed). Without this tests that call checkLoginToAPIAsUser // can occasionally fail. for a := coretesting.LongAttempt.Start(); a.Next(); { err = s.attemptRestrictedAPIAsUser(c, conf) switch expectFullApi { case FullAPIExposed: if err == nil { return } case RestrictedAPIExposed: if params.IsCodeUpgradeInProgress(err) { return } } } c.Fatalf("timed out waiting for expected API behaviour. last error was: %v", err) }
func (s *clientSuite) TestSetModelAgentVersionDuringUpgrade(c *gc.C) { // This is an integration test which ensure that a test with the // correct error code is seen by the client from the // SetModelAgentVersion call when an upgrade is in progress. envConfig, err := s.State.ModelConfig() c.Assert(err, jc.ErrorIsNil) agentVersion, ok := envConfig.AgentVersion() c.Assert(ok, jc.IsTrue) machine := s.Factory.MakeMachine(c, &factory.MachineParams{ Jobs: []state.MachineJob{state.JobManageModel}, }) err = machine.SetAgentVersion(version.MustParseBinary(agentVersion.String() + "-quantal-amd64")) c.Assert(err, jc.ErrorIsNil) nextVersion := version.MustParse("9.8.7") _, err = s.State.EnsureUpgradeInfo(machine.Id(), agentVersion, nextVersion) c.Assert(err, jc.ErrorIsNil) err = s.APIState.Client().SetModelAgentVersion(nextVersion) // Expect an error with a error code that indicates this specific // situation. The client needs to be able to reliably identify // this error and handle it differently to other errors. c.Assert(params.IsCodeUpgradeInProgress(err), jc.IsTrue) }
// Run changes the version proposed for the juju envtools. func (c *upgradeJujuCommand) Run(ctx *cmd.Context) (err error) { client, err := getUpgradeJujuAPI(c) if err != nil { return err } defer client.Close() defer func() { if err == errUpToDate { ctx.Infof(err.Error()) err = nil } }() // Determine the version to upgrade to, uploading tools if necessary. attrs, err := client.ModelGet() if err != nil { return err } cfg, err := config.New(config.NoDefaults, attrs) if err != nil { return err } agentVersion, ok := cfg.AgentVersion() if !ok { // Can't happen. In theory. return fmt.Errorf("incomplete model configuration") } if c.UploadTools && c.Version == version.Zero { // Currently, uploading tools assumes the version to be // the same as jujuversion.Current if not specified with // --version. c.Version = jujuversion.Current } warnCompat := false switch { case !canUpgradeRunningVersion(agentVersion): // This version of upgrade-juju cannot upgrade the running // environment version (can't guarantee API compatibility). return fmt.Errorf("cannot upgrade a %s model with a %s client", agentVersion, jujuversion.Current) case c.Version != version.Zero && c.Version.Major < agentVersion.Major: // The specified version would downgrade the environment. // Don't upgrade and return an error. return fmt.Errorf(downgradeErrMsg, agentVersion, c.Version) case agentVersion.Major != jujuversion.Current.Major: // Running environment is the previous major version (a higher major // version wouldn't have passed the check in canUpgradeRunningVersion). if c.Version == version.Zero || c.Version.Major == agentVersion.Major { // Not requesting an upgrade across major release boundary. // Warn of incompatible CLI and filter on the prior major version // when searching for available tools. // TODO(cherylj) Add in a suggestion to upgrade to 2.0 if // no matching tools are found (bug 1532670) warnCompat = true break } // User requested an upgrade to the next major version. // Fallthrough to the next case to verify that the upgrade // conditions are met. fallthrough case c.Version.Major > agentVersion.Major: // User is requesting an upgrade to a new major number // Only upgrade to a different major number if: // 1 - Explicitly requested with --version or using --upload-tools, and // 2 - The environment is running a valid version to upgrade from, and // 3 - The upgrade is to a minor version of 0. minVer, ok := c.minMajorUpgradeVersion[c.Version.Major] if !ok { return errors.Errorf("unknown version %q", c.Version) } retErr := false if c.Version.Minor != 0 { ctx.Infof("upgrades to %s must first go through juju %d.0", c.Version, c.Version.Major) retErr = true } if comp := agentVersion.Compare(minVer); comp < 0 { ctx.Infof("upgrades to a new major version must first go through %s", minVer) retErr = true } if retErr { return fmt.Errorf("unable to upgrade to requested version") } } context, err := c.initVersions(client, cfg, agentVersion, warnCompat) if err != nil { return err } if c.UploadTools && !c.DryRun { if err := context.uploadTools(); err != nil { return block.ProcessBlockedError(err, block.BlockChange) } } if err := context.validate(); err != nil { return err } // TODO(fwereade): this list may be incomplete, pending envtools.Upload change. ctx.Infof("available tools:\n%s", formatTools(context.tools)) ctx.Infof("best version:\n %s", context.chosen) if warnCompat { logger.Warningf("version %s incompatible with this client (%s)", context.chosen, jujuversion.Current) } if c.DryRun { ctx.Infof("upgrade to this version by running\n juju upgrade-juju --version=\"%s\"\n", context.chosen) } else { if c.ResetPrevious { if ok, err := c.confirmResetPreviousUpgrade(ctx); !ok || err != nil { const message = "previous upgrade not reset and no new upgrade triggered" if err != nil { return errors.Annotate(err, message) } return errors.New(message) } if err := client.AbortCurrentUpgrade(); err != nil { return block.ProcessBlockedError(err, block.BlockChange) } } if err := client.SetModelAgentVersion(context.chosen); err != nil { if params.IsCodeUpgradeInProgress(err) { return errors.Errorf("%s\n\n"+ "Please wait for the upgrade to complete or if there was a problem with\n"+ "the last upgrade that has been resolved, consider running the\n"+ "upgrade-juju command with the --reset-previous-upgrade flag.", err, ) } else { return block.ProcessBlockedError(err, block.BlockChange) } } logger.Infof("started upgrade to %s", context.chosen) } return nil }
// Run changes the version proposed for the juju envtools. func (c *upgradeJujuCommand) Run(ctx *cmd.Context) (err error) { if len(c.Series) > 0 { fmt.Fprintln(ctx.Stderr, "Use of --series is obsolete. --upload-tools now expands to all supported series of the same operating system.") } client, err := getUpgradeJujuAPI(c) if err != nil { return err } defer client.Close() defer func() { if err == errUpToDate { ctx.Infof(err.Error()) err = nil } }() // Determine the version to upgrade to, uploading tools if necessary. attrs, err := client.EnvironmentGet() if err != nil { return err } cfg, err := config.New(config.NoDefaults, attrs) if err != nil { return err } context, err := c.initVersions(client, cfg) if err != nil { return err } if c.UploadTools && !c.DryRun { if err := context.uploadTools(); err != nil { return block.ProcessBlockedError(err, block.BlockChange) } } if err := context.validate(); err != nil { return err } // TODO(fwereade): this list may be incomplete, pending envtools.Upload change. ctx.Infof("available tools:\n%s", formatTools(context.tools)) ctx.Infof("best version:\n %s", context.chosen) if c.DryRun { ctx.Infof("upgrade to this version by running\n juju upgrade-juju --version=\"%s\"\n", context.chosen) } else { if c.ResetPrevious { if ok, err := c.confirmResetPreviousUpgrade(ctx); !ok || err != nil { const message = "previous upgrade not reset and no new upgrade triggered" if err != nil { return errors.Annotate(err, message) } return errors.New(message) } if err := client.AbortCurrentUpgrade(); err != nil { return block.ProcessBlockedError(err, block.BlockChange) } } if err := client.SetEnvironAgentVersion(context.chosen); err != nil { if params.IsCodeUpgradeInProgress(err) { return errors.Errorf("%s\n\n"+ "Please wait for the upgrade to complete or if there was a problem with\n"+ "the last upgrade that has been resolved, consider running the\n"+ "upgrade-juju command with the --reset-previous-upgrade flag.", err, ) } else { return block.ProcessBlockedError(err, block.BlockChange) } } logger.Infof("started upgrade to %s", context.chosen) } return nil }
// Run changes the version proposed for the juju envtools. func (c *upgradeJujuCommand) Run(ctx *cmd.Context) (err error) { client, err := getUpgradeJujuAPI(c) if err != nil { return err } defer client.Close() modelConfigClient, err := getModelConfigAPI(c) if err != nil { return err } defer modelConfigClient.Close() controllerClient, err := getControllerAPI(c) if err != nil { return err } defer controllerClient.Close() defer func() { if err == errUpToDate { ctx.Infof(err.Error()) err = nil } }() // Determine the version to upgrade to, uploading tools if necessary. attrs, err := modelConfigClient.ModelGet() if err != nil { return err } cfg, err := config.New(config.NoDefaults, attrs) if err != nil { return err } controllerModelConfig, err := controllerClient.ModelConfig() if err != nil { return err } isControllerModel := cfg.UUID() == controllerModelConfig[config.UUIDKey] if c.BuildAgent && !isControllerModel { // For UploadTools, model must be the "controller" model, // that is, modelUUID == controllerUUID return errors.Errorf("--build-agent can only be used with the controller model") } agentVersion, ok := cfg.AgentVersion() if !ok { // Can't happen. In theory. return errors.New("incomplete model configuration") } if c.BuildAgent && c.Version == version.Zero { // Currently, uploading tools assumes the version to be // the same as jujuversion.Current if not specified with // --agent-version. c.Version = jujuversion.Current } warnCompat := false switch { case !canUpgradeRunningVersion(agentVersion): // This version of upgrade-juju cannot upgrade the running // environment version (can't guarantee API compatibility). return errors.Errorf("cannot upgrade a %s model with a %s client", agentVersion, jujuversion.Current) case c.Version != version.Zero && c.Version.Major < agentVersion.Major: // The specified version would downgrade the environment. // Don't upgrade and return an error. return errors.Errorf(downgradeErrMsg, agentVersion, c.Version) case agentVersion.Major != jujuversion.Current.Major: // Running environment is the previous major version (a higher major // version wouldn't have passed the check in canUpgradeRunningVersion). if c.Version == version.Zero || c.Version.Major == agentVersion.Major { // Not requesting an upgrade across major release boundary. // Warn of incompatible CLI and filter on the prior major version // when searching for available tools. // TODO(cherylj) Add in a suggestion to upgrade to 2.0 if // no matching tools are found (bug 1532670) warnCompat = true break } // User requested an upgrade to the next major version. // Fallthrough to the next case to verify that the upgrade // conditions are met. fallthrough case c.Version.Major > agentVersion.Major: // User is requesting an upgrade to a new major number // Only upgrade to a different major number if: // 1 - Explicitly requested with --agent-version or using --build-agent, and // 2 - The environment is running a valid version to upgrade from, and // 3 - The upgrade is to a minor version of 0. minVer, ok := c.minMajorUpgradeVersion[c.Version.Major] if !ok { return errors.Errorf("unknown version %q", c.Version) } retErr := false if c.Version.Minor != 0 { ctx.Infof("upgrades to %s must first go through juju %d.0", c.Version, c.Version.Major) retErr = true } if comp := agentVersion.Compare(minVer); comp < 0 { ctx.Infof("upgrades to a new major version must first go through %s", minVer) retErr = true } if retErr { return errors.New("unable to upgrade to requested version") } } context, err := c.initVersions(client, cfg, agentVersion, warnCompat) if err != nil { return err } // If we're running a custom build or the user has asked for a new agent // to be built, upload a local jujud binary if possible. uploadLocalBinary := isControllerModel && c.Version == version.Zero && tryImplicitUpload(agentVersion) if !warnCompat && (uploadLocalBinary || c.BuildAgent) && !c.DryRun { if err := context.uploadTools(c.BuildAgent); err != nil { // If we've explicitly asked to build an agent binary, or the upload failed // because changes were blocked, we'll return an error. if err2 := block.ProcessBlockedError(err, block.BlockChange); c.BuildAgent || err2 == cmd.ErrSilent { return err2 } } builtMsg := "" if c.BuildAgent { builtMsg = " (built from source)" } fmt.Fprintf(ctx.Stdout, "no prepackaged tools available, using local agent binary %v%s\n", context.chosen, builtMsg) } // If there was an error implicitly uploading a binary, we'll still look for any packaged binaries // since there may still be a valid upgrade and the user didn't ask for any local binary. if err := context.validate(); err != nil { return err } // TODO(fwereade): this list may be incomplete, pending envtools.Upload change. ctx.Verbosef("available tools:\n%s", formatTools(context.tools)) ctx.Verbosef("best version:\n %s", context.chosen) if warnCompat { fmt.Fprintf(ctx.Stderr, "version %s incompatible with this client (%s)\n", context.chosen, jujuversion.Current) } if c.DryRun { fmt.Fprintf(ctx.Stderr, "upgrade to this version by running\n juju upgrade-juju --agent-version=\"%s\"\n", context.chosen) } else { if c.ResetPrevious { if ok, err := c.confirmResetPreviousUpgrade(ctx); !ok || err != nil { const message = "previous upgrade not reset and no new upgrade triggered" if err != nil { return errors.Annotate(err, message) } return errors.New(message) } if err := client.AbortCurrentUpgrade(); err != nil { return block.ProcessBlockedError(err, block.BlockChange) } } if err := client.SetModelAgentVersion(context.chosen); err != nil { if params.IsCodeUpgradeInProgress(err) { return errors.Errorf("%s\n\n"+ "Please wait for the upgrade to complete or if there was a problem with\n"+ "the last upgrade that has been resolved, consider running the\n"+ "upgrade-juju command with the --reset-previous-upgrade flag.", err, ) } else { return block.ProcessBlockedError(err, block.BlockChange) } } fmt.Fprintf(ctx.Stdout, "started upgrade to %s\n", context.chosen) } return nil }