Skip to content

Commit

Permalink
Merge pull request #8864 from ExternalReality/ensure_upgrade_series_h…
Browse files Browse the repository at this point in the history
…ooks_run

#8864

## Description of change

When a `upgrade-series prepare` is initiated hooks need to be run.
  • Loading branch information
jujubot committed Jul 13, 2018
2 parents 48b5ea4 + 8cc0cae commit 3560f1e
Show file tree
Hide file tree
Showing 26 changed files with 652 additions and 54 deletions.
70 changes: 70 additions & 0 deletions api/uniter/unit.go
Expand Up @@ -642,6 +642,76 @@ func (u *Unit) WatchActionNotifications() (watcher.StringsWatcher, error) {
return w, nil
}

// WatchActionNotifications returns a StringsWatcher for observing the state of
// a series upgrade.
func (u *Unit) WatchUpgradeSeriesNotifications() (watcher.NotifyWatcher, error) {
var results params.NotifyWatchResults
args := params.Entities{
Entities: []params.Entity{{Tag: u.tag.String()}},
}
err := u.st.facade.FacadeCall("WatchUpgradeSeriesNotifications", args, &results)
if err != nil {
return nil, err
}
if len(results.Results) != 1 {
return nil, errors.Errorf("expected 1 result, got %d", len(results.Results))
}
result := results.Results[0]
if result.Error != nil {
return nil, result.Error
}
w := apiwatcher.NewNotifyWatcher(u.st.facade.RawAPICaller(), result)
return w, nil
}

// UpgradeSeriesStatus returns the upgrade series status of a unit from remote state
func (u *Unit) UpgradeSeriesStatus() (string, error) {
var results params.UpgradeSeriesStatusResults
args := params.Entities{
Entities: []params.Entity{{Tag: u.tag.String()}},
}
err := u.st.facade.FacadeCall("UpgradeSeriesStatus", args, &results)
if err != nil {
return "", err
}
if len(results.Results) != 1 {
return "", errors.Errorf("expected 1 result, got %d", len(results.Results))
}
result := results.Results[0]
if result.Error != nil {
//TODO (externalreality) The code to do convert api errors (with
//error codes) back to normal Go errors is in bad spot and
//causes import cycles which is why we don't use it here and may
//be the reason why it has few uses despite being useful.
if params.IsCodeNotFound(result.Error) {
return "", errors.NewNotFound(result.Error, "")
}
return "", result.Error
}
return result.Status, nil
}

// UpgradeSeriesStatus sets the upgrade series status of the unit in the remote state
func (u *Unit) SetUpgradeSeriesStatus(status string) error {
var results params.ErrorResults
args := params.SetUpgradeSeriesStatusParams{
Entities: []params.Entity{{Tag: u.tag.String()}},
Status: []string{status},
}
err := u.st.facade.FacadeCall("SetUpgradeSeriesStatus", args, &results)
if err != nil {
return err
}
if len(results.Results) != 1 {
return errors.Errorf("expected 1 result, got %d", len(results.Results))
}
result := results.Results[0]
if result.Error != nil {
return result.Error
}
return nil
}

// RequestReboot sets the reboot flag for its machine agent
func (u *Unit) RequestReboot() error {
machineId, err := u.AssignedMachine()
Expand Down
87 changes: 87 additions & 0 deletions api/uniter/unit_test.go
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/juju/juju/apiserver/common"
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/core/application"
"github.com/juju/juju/core/model"
jujutesting "github.com/juju/juju/juju/testing"
"github.com/juju/juju/network"
"github.com/juju/juju/state"
Expand Down Expand Up @@ -684,6 +685,92 @@ func (s *unitSuite) TestWatchActionNotificationsMoreResults(c *gc.C) {
c.Assert(err.Error(), gc.Equals, "expected 1 result, got 2")
}

func (s *unitSuite) TestWatchUpgradeSeriesNotifications(c *gc.C) {
watcher, err := s.apiUnit.WatchUpgradeSeriesNotifications()
c.Assert(err, jc.ErrorIsNil)

notifyWatcher := watchertest.NewNotifyWatcherC(c, watcher, s.BackingState.StartSync)
defer notifyWatcher.AssertStops()

notifyWatcher.AssertOneChange()

s.CreateUpgradeSeriesLock(c)

// Expect a notification that the document was created (i.e. a lock was placed)
notifyWatcher.AssertOneChange()

err = s.wordpressMachine.RemoveUpgradeSeriesLock()
c.Assert(err, jc.ErrorIsNil)

// A notification that the document was removed (i.e. the lock was released)
notifyWatcher.AssertOneChange()
}

func (s *unitSuite) TestUpgradeSeriesStatusIsInitializedToUnitStarted(c *gc.C) {
// First we create the prepare lock
s.CreateUpgradeSeriesLock(c)

// Then we check to see the status of our upgrade. We note that creating
// the lock essentially kicks off an upgrade from the perspective of
// assigned units.
status, err := s.apiUnit.UpgradeSeriesStatus()
c.Assert(err, jc.ErrorIsNil)
c.Assert(status, gc.Equals, string(model.UnitStarted))
}

func (s *unitSuite) TestSetUpgradeSeriesStatusFailsIfNoLockExists(c *gc.C) {
arbitaryStatus := string(model.UnitNotStarted)

err := s.apiUnit.SetUpgradeSeriesStatus(arbitaryStatus)
c.Assert(err, gc.ErrorMatches, "machine \"[0-9]*\" is not locked for upgrade")
}

func (s *unitSuite) TestSetUpgradeSeriesStatusUpdatesStatus(c *gc.C) {
arbitaryNonDefaultStatus := string(model.UnitNotStarted)

// First we create the prepare lock or the required state will not exists
s.CreateUpgradeSeriesLock(c)

// Change the state to something other than the default remote state of UnitStarted
err := s.apiUnit.SetUpgradeSeriesStatus(arbitaryNonDefaultStatus)
c.Assert(err, jc.ErrorIsNil)

// Check to see that the upgrade status has been set appropriately
status, err := s.apiUnit.UpgradeSeriesStatus()
c.Assert(err, jc.ErrorIsNil)
c.Assert(status, gc.Equals, arbitaryNonDefaultStatus)
}

func (s *unitSuite) TestSetUpgradeSeriesStatusShouldOnlySetSpecifiedUnit(c *gc.C) {
// add another unit
unit2, err := s.wordpressApplication.AddUnit(state.AddUnitParams{})
c.Assert(err, jc.ErrorIsNil)

err = unit2.AssignToMachine(s.wordpressMachine)
c.Assert(err, jc.ErrorIsNil)

// Creating a lock for the machine transitions all units to started state
s.CreateUpgradeSeriesLock(c, unit2.Name())

// Complete one unit
err = unit2.SetUpgradeSeriesStatus(model.UnitCompleted)
c.Assert(err, jc.ErrorIsNil)

// The other unit should still be in the started state
status, err := s.wordpressUnit.UpgradeSeriesStatus()
c.Assert(err, jc.ErrorIsNil)
c.Assert(status, gc.Equals, model.UnitStarted)
}

func (s *unitSuite) CreateUpgradeSeriesLock(c *gc.C, additionalUnits ...string) {
unitNames := additionalUnits
unitNames = append(unitNames, s.wordpressUnit.Name())
series := "trust"

err := s.wordpressMachine.CreateUpgradeSeriesLock(unitNames, series)
c.Assert(err, jc.ErrorIsNil)
}

func (s *unitSuite) TestApplicationNameAndTag(c *gc.C) {
c.Assert(s.apiUnit.ApplicationName(), gc.Equals, s.wordpressApplication.Name())
c.Assert(s.apiUnit.ApplicationTag(), gc.Equals, s.wordpressApplication.Tag())
Expand Down
113 changes: 113 additions & 0 deletions apiserver/facades/agent/uniter/uniter.go
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/caas"
"github.com/juju/juju/core/leadership"
"github.com/juju/juju/core/model"
"github.com/juju/juju/network"
"github.com/juju/juju/state"
"github.com/juju/juju/state/multiwatcher"
Expand Down Expand Up @@ -984,6 +985,118 @@ func (u *UniterAPI) WatchActionNotifications(args params.Entities) (params.Strin
return common.WatchActionNotifications(args, canAccess, watchOne), nil
}

// WatchUpgradeSeriesNotifications returns a NotifyWatcher for observing changes to upgrade series locks.
func (u *UniterAPI) WatchUpgradeSeriesNotifications(args params.Entities) (params.NotifyWatchResults, error) {
result := params.NotifyWatchResults{
Results: make([]params.NotifyWatchResult, len(args.Entities)),
}
canAccess, err := u.accessUnit()
if err != nil {
return params.NotifyWatchResults{}, err
}
for i, entity := range args.Entities {
tag, err := names.ParseUnitTag(entity.Tag)
if err != nil {
result.Results[i].Error = common.ServerError(common.ErrPerm)
continue
}
watcherId := ""
if !canAccess(tag) {
result.Results[i].Error = common.ServerError(common.ErrPerm)
continue
}
unit, err := u.getUnit(tag)
if err != nil {
result.Results[i].Error = common.ServerError(err)
continue
}
w, err := unit.WatchUpgradeSeriesNotifications()
if err != nil {
result.Results[i].Error = common.ServerError(err)
continue
}
watcherId = u.resources.Register(w)
result.Results[i].NotifyWatcherId = watcherId
}
return result, nil
}

// UpgradeSeriesStatus returns the current state of series upgrading
// unit. If no upgrade is in progress an error is returned instead.
func (u *UniterAPI) UpgradeSeriesStatus(args params.Entities) (params.UpgradeSeriesStatusResults, error) {
result := params.UpgradeSeriesStatusResults{
Results: make([]params.UpgradeSeriesStatusResult, len(args.Entities)),
}
canAccess, err := u.accessUnit()
if err != nil {
return params.UpgradeSeriesStatusResults{}, err
}
for i, entity := range args.Entities {
tag, err := names.ParseUnitTag(entity.Tag)
if err != nil {
result.Results[i].Error = common.ServerError(common.ErrPerm)
continue
}

if !canAccess(tag) {
result.Results[i].Error = common.ServerError(common.ErrPerm)
continue
}
unit, err := u.getUnit(tag)
if err != nil {
result.Results[i].Error = common.ServerError(err)
continue
}
status, err := unit.UpgradeSeriesStatus()
if err != nil {
result.Results[i].Error = common.ServerError(err)
continue
}
result.Results[i].Status = string(status)
}

return result, nil
}

func (u *UniterAPI) SetUpgradeSeriesStatus(args params.SetUpgradeSeriesStatusParams) (params.ErrorResults, error) {
result := params.ErrorResults{
Results: make([]params.ErrorResult, len(args.Entities)),
}
canAccess, err := u.accessUnit()
if err != nil {
return params.ErrorResults{}, err
}
for i, entity := range args.Entities {
//TODO[externalreality] refactor all of this, its being copied often.
tag, err := names.ParseUnitTag(entity.Tag)
if err != nil {
result.Results[i].Error = common.ServerError(common.ErrPerm)
continue
}
if !canAccess(tag) {
result.Results[i].Error = common.ServerError(common.ErrPerm)
continue
}
unit, err := u.getUnit(tag)
if err != nil {
result.Results[i].Error = common.ServerError(err)
continue
}
status, err := model.ValidateUnitSeriesUpgradeStatus(args.Status[i])
if err != nil {
result.Results[i].Error = common.ServerError(err)
continue
}
err = unit.SetUpgradeSeriesStatus(status)
if err != nil {
result.Results[i].Error = common.ServerError(err)
continue
}
}

return result, nil
}

// ConfigSettings returns the complete set of application charm config
// settings available to each given unit.
func (u *UniterAPI) ConfigSettings(args params.Entities) (params.ConfigSettingsResults, error) {
Expand Down
14 changes: 14 additions & 0 deletions apiserver/params/params.go
Expand Up @@ -1277,3 +1277,17 @@ type DumpModelRequest struct {
Entities []Entity `json:"entities"`
Simplified bool `json:"simplified"`
}

type UpgradeSeriesStatusResult struct {
Error *Error `json:"error,omitempty"`
Status string `json:"status,omitempty"`
}

type UpgradeSeriesStatusResults struct {
Results []UpgradeSeriesStatusResult `json:"results,omitempty"`
}

type SetUpgradeSeriesStatusParams struct {
Entities []Entity `json:"entities"`
Status []string `json:"statuses"`
}
2 changes: 1 addition & 1 deletion cmd/juju/commands/debughooks_test.go
Expand Up @@ -121,7 +121,7 @@ var debugHooksTests = []struct {
}, {
info: `invalid hook`,
args: []string{"mysql/0", "invalid-hook"},
error: `unit "mysql/0" contains neither hook nor action "invalid-hook", valid actions are [fakeaction] and valid hooks are [collect-metrics config-changed install juju-info-relation-broken juju-info-relation-changed juju-info-relation-departed juju-info-relation-joined leader-deposed leader-elected leader-settings-changed meter-status-changed server-admin-relation-broken server-admin-relation-changed server-admin-relation-departed server-admin-relation-joined server-relation-broken server-relation-changed server-relation-departed server-relation-joined start stop update-status upgrade-charm]`,
error: `unit "mysql/0" contains neither hook nor action "invalid-hook", valid actions are [fakeaction] and valid hooks are [collect-metrics config-changed install juju-info-relation-broken juju-info-relation-changed juju-info-relation-departed juju-info-relation-joined leader-deposed leader-elected leader-settings-changed meter-status-changed post-series-upgrade pre-series-upgrade server-admin-relation-broken server-admin-relation-changed server-admin-relation-departed server-admin-relation-joined server-relation-broken server-relation-changed server-relation-departed server-relation-joined start stop update-status upgrade-charm]`,
}, {
info: `no args at all`,
args: nil,
Expand Down
41 changes: 41 additions & 0 deletions core/model/upgradeseries.go
@@ -0,0 +1,41 @@
// Copyright 2018 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package model

import (
"github.com/juju/errors"
)

//MachineSeriesUpgradeStatus is the current status a machine series upgrade
type MachineSeriesUpgradeStatus string

const (
MachineSeriesUpgradeNotStarted MachineSeriesUpgradeStatus = "NotStarted"
MachineSeriesUpgradeStarted MachineSeriesUpgradeStatus = "Started"
MachineSeriesUpgradeUnitsRunning MachineSeriesUpgradeStatus = "UnitsRunning"
MachineSeriesUpgradeJujuComplete MachineSeriesUpgradeStatus = "JujuComplete"
MachineSeriesUpgradeAgentsStopped MachineSeriesUpgradeStatus = "AgentsStopped"
MachineSeriesUpgradeComplete MachineSeriesUpgradeStatus = "Complete"
)

//UnitSeriesUpgradeStatus is the current status of a series upgrade for units
type UnitSeriesUpgradeStatus string

const (
UnitNotStarted UnitSeriesUpgradeStatus = "NotStarted"
UnitStarted UnitSeriesUpgradeStatus = "Started"
UnitErrored UnitSeriesUpgradeStatus = "Errored"
UnitCompleted UnitSeriesUpgradeStatus = "Completed"
)

// Validates a string returning an UpgradeSeriesStatus, if valid, or an error.
func ValidateUnitSeriesUpgradeStatus(series string) (UnitSeriesUpgradeStatus, error) {
unCheckedStatus := UnitSeriesUpgradeStatus(series)
switch unCheckedStatus {
case UnitNotStarted, UnitStarted, UnitErrored, UnitCompleted:
return unCheckedStatus, nil
}

return UnitNotStarted, errors.Errorf("encountered invalid unit upgrade series status of %q", series)
}
2 changes: 1 addition & 1 deletion dependencies.tsv
Expand Up @@ -111,7 +111,7 @@ gopkg.in/goose.v2 git 36df5d12fc6d0ef1c102e1c18a22ddf345b18821 2018-03-22T12:54:
gopkg.in/httprequest.v1 git 1a21782420ea13c3c6fb1d03578f446b3248edb1 2018-03-08T16:26:44Z
gopkg.in/ini.v1 git 776aa739ce9373377cd16f526cdf06cb4c89b40f 2016-02-22T23:24:41Z
gopkg.in/juju/blobstore.v2 git 51fa6e26128d74e445c72d3a91af555151cc3654 2016-01-25T02:37:03Z
gopkg.in/juju/charm.v6 git 5fbb467f7f759a4a2890fc5da0abe4b9b1df20fa 2018-06-29T06:08:46Z
gopkg.in/juju/charm.v6 git e6a4837dfe5ac4394d9861a97c45a887448bdc3c 2018-07-09T02:22:58Z
gopkg.in/juju/charmrepo.v2 git 653bbd81990d2d7d48e0fb931a3b0f52c694572f 2017-11-14T18:40:45Z
gopkg.in/juju/charmrepo.v3 git 8bb46aa94f3c679069e80fe35aac6d0581096d65 2018-06-29T07:07:28Z
gopkg.in/juju/charmstore.v5 git d93d0fd2b81b8230bdf9b1be91b50538dd8782fa 2018-06-28T08:11:03Z
Expand Down

0 comments on commit 3560f1e

Please sign in to comment.