Skip to content

Commit

Permalink
Merge pull request #11846 from howbazaar/2.8-watch-models-application…
Browse files Browse the repository at this point in the history
…-status

#11846

If an application status hasn't been explicitly set by a unit its status
is derived from the units themselves. This derivation code was moved into
the model cache, but the all watcher translation later itself was overlooked.

This branch uses the cached controller information to determine the application
status if it is Unset, like the status code does.

There were no tests around the all watcher code on the apiserver side,
so added some to test the conversion behaviour.

## QA steps

```sh
juju bootstrap lxd test
juju deploy ~jameinel/ubuntu-lite ubuntu
juju db # using juju-db plugin to get mongo prompt
db.statuses.find().pretty()
# check for _id: <uuid>:a#ubuntu
# "status" : "unset",
# run pylibjuju example to look at the watcher
tox -e example -- examples/allwatcher.py 
# will see a line like:
['application', 'change', {'model-uuid': '11252a44-d946-4305-8292-a650cfa4b138', 'name': 'ubuntu', 'exposed': False, 'charm-url': 'cs:~jameinel/ubuntu-lite-7', 'owner-tag': '', 'life': 'alive', 'min-units': 0, 'constraints': {}, 'subordinate': False, 'status': {'current': 'waiting', 'message': 'waiting for machine', 'since': '2020-07-21T05:42:48.050629314Z', 'version': ''}, 'workload-version': ''}]
# confirm that status isn't 'unset'
```
  • Loading branch information
jujubot committed Jul 21, 2020
2 parents b627cb6 + b67d593 commit 2bf2712
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 4 deletions.
25 changes: 21 additions & 4 deletions apiserver/watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/juju/juju/core/migration"
"github.com/juju/juju/core/multiwatcher"
"github.com/juju/juju/core/network"
"github.com/juju/juju/core/status"
"github.com/juju/juju/state"
)

Expand Down Expand Up @@ -53,16 +54,18 @@ func NewAllWatcher(context facade.Context) (facade.Facade, error) {
}

type watcherCommon struct {
id string
resources facade.Resources
dispose func()
id string
resources facade.Resources
dispose func()
controller *cache.Controller
}

func newWatcherCommon(context facade.Context) watcherCommon {
return watcherCommon{
context.ID(),
context.Resources(),
context.Dispose,
context.Controller(),
}
}

Expand Down Expand Up @@ -176,6 +179,20 @@ func (aw *SrvAllWatcher) translateApplication(info multiwatcher.EntityInfo) para
logger.Criticalf("consistency error: %s", pretty.Sprint(info))
return nil
}

// Get the application status from the cache if it is unset.
applicationStatus := multiwatcher.StatusInfo{Current: status.Unknown}
if orig.Status.Current == status.Unset {
if model, err := aw.controller.Model(orig.ModelUUID); err == nil {
cachedApp, err := model.Application(orig.Name)
if err == nil {
applicationStatus = multiwatcher.NewStatusInfo(cachedApp.Status(), nil)
}
}
} else {
applicationStatus = orig.Status
}

return &params.ApplicationInfo{
ModelUUID: orig.ModelUUID,
Name: orig.Name,
Expand All @@ -187,7 +204,7 @@ func (aw *SrvAllWatcher) translateApplication(info multiwatcher.EntityInfo) para
Constraints: orig.Constraints,
Config: orig.Config,
Subordinate: orig.Subordinate,
Status: aw.translateStatus(orig.Status),
Status: aw.translateStatus(applicationStatus),
WorkloadVersion: orig.WorkloadVersion,
}
}
Expand Down
135 changes: 135 additions & 0 deletions apiserver/watcher_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright 2020 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package apiserver

import (
"time"

"github.com/juju/worker/v2/workertest"
gc "gopkg.in/check.v1"

"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/core/cache"
"github.com/juju/juju/core/life"
"github.com/juju/juju/core/multiwatcher"
"github.com/juju/juju/core/status"
"github.com/juju/juju/testing"
jc "github.com/juju/testing/checkers"
)

type allWatcherSuite struct {
testing.BaseSuite
}

var _ = gc.Suite(&allWatcherSuite{})

func (s *allWatcherSuite) watcher() *SrvAllWatcher {
// We explicitly don't have a real watcher here as the tests
// are for the translation of types.
return &SrvAllWatcher{}
}

func (s *allWatcherSuite) TestTranslateApplicationWithStatus(c *gc.C) {
w := s.watcher()
input := &multiwatcher.ApplicationInfo{
ModelUUID: testing.ModelTag.Id(),
Name: "test-app",
CharmURL: "test-app",
Life: life.Alive,
Status: multiwatcher.StatusInfo{
Current: status.Active,
},
}
output := w.translateApplication(input)
c.Assert(output, jc.DeepEquals, &params.ApplicationInfo{
ModelUUID: input.ModelUUID,
Name: input.Name,
CharmURL: input.CharmURL,
Life: input.Life,
Status: params.StatusInfo{
Current: status.Active,
},
})
}

func (s *allWatcherSuite) setupCache(c *gc.C) *cache.Controller {
changes := make(chan interface{})
handled := make(chan interface{})
notify := func(evt interface{}) {
c.Logf("%#v", evt)
select {
case handled <- evt:
case <-time.After(testing.LongWait):
c.Fatalf("handled notify not retrieved")
}
}
sendEvent := func(event interface{}) {
select {
case changes <- event:
case <-time.After(testing.LongWait):
c.Fatal("cache did not accept event")
}
select {
case <-handled:
case <-time.After(testing.LongWait):
c.Fatal("cache did not handle event")
}
}

controller, err := cache.NewController(cache.ControllerConfig{
Changes: changes,
Notify: notify,
})
c.Assert(err, jc.ErrorIsNil)
s.AddCleanup(func(c *gc.C) { workertest.CleanKill(c, controller) })

sendEvent(cache.ModelChange{
ModelUUID: testing.ModelTag.Id(),
// Defaults for everything else.
})
sendEvent(cache.ApplicationChange{
ModelUUID: testing.ModelTag.Id(),
Name: "test-app",
Status: status.StatusInfo{
Status: status.Unset,
},
// Defaults for everything else.
})
sendEvent(cache.UnitChange{
ModelUUID: testing.ModelTag.Id(),
Name: "test-app/0",
Application: "test-app",
WorkloadStatus: status.StatusInfo{
Status: status.Active,
},
// Defaults for everything else.
})

return controller
}

func (s *allWatcherSuite) TestTranslateApplicationStatusUnset(c *gc.C) {
controller := s.setupCache(c)
w := s.watcher()
w.controller = controller
input := &multiwatcher.ApplicationInfo{
ModelUUID: testing.ModelTag.Id(),
Name: "test-app",
CharmURL: "test-app",
Life: life.Alive,
Status: multiwatcher.StatusInfo{
Current: status.Unset,
},
}
output := w.translateApplication(input)
c.Assert(output, jc.DeepEquals, &params.ApplicationInfo{
ModelUUID: input.ModelUUID,
Name: input.Name,
CharmURL: input.CharmURL,
Life: input.Life,
Status: params.StatusInfo{
Current: status.Active,
},
})
}

0 comments on commit 2bf2712

Please sign in to comment.