Skip to content

Commit

Permalink
Fixes updates to controller's UnavailableSince field.
Browse files Browse the repository at this point in the history
  • Loading branch information
alesstimec committed Jun 24, 2024
1 parent d77ce65 commit fecbb65
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 28 deletions.
54 changes: 28 additions & 26 deletions internal/jimm/watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,27 +130,39 @@ func (w *Watcher) WatchAllModelSummaries(ctx context.Context, interval time.Dura
}
}

func updateControllerAvailability(ctx context.Context, database db.Database, ctl *dbmodel.Controller, err error, controllerUnavailableChannel chan error) {
if err != nil && !ctl.UnavailableSince.Valid {
ctl.UnavailableSince = db.Now()
var err error
if err = database.UpdateController(ctx, ctl); err != nil {
zapctx.Error(ctx, "cannot set controller unavailable", zap.Error(err))
}
}
if err == nil && ctl.UnavailableSince.Valid {
ctl.UnavailableSince = sql.NullTime{}
if err = database.UpdateController(ctx, ctl); err != nil {
zapctx.Error(ctx, "cannot set controller available", zap.Error(err))
}
}
// Note (alesstimec) This channel is only available in tests.
if controllerUnavailableChannel != nil {
select {
case controllerUnavailableChannel <- err:
default:
}
}
}

func (w *Watcher) dialController(ctx context.Context, ctl *dbmodel.Controller) (API, error) {
const op = errors.Op("jimm.dialController")

// connect to the controller
api, err := w.Dialer.Dial(ctx, ctl, names.ModelTag{}, nil)
if err != nil {
if !ctl.UnavailableSince.Valid {
ctl.UnavailableSince = db.Now()
var err error
if err = w.Database.UpdateController(ctx, ctl); err != nil {
zapctx.Error(ctx, "cannot set controller unavailable", zap.Error(err))
}
if w.controllerUnavailableChan != nil {
select {
case w.controllerUnavailableChan <- err:
default:
}
}
}
updateControllerAvailability(ctx, w.Database, ctl, err, w.controllerUnavailableChan)
return nil, errors.E(op, err)
}
updateControllerAvailability(ctx, w.Database, ctl, nil, w.controllerUnavailableChan)
return api, nil
}

Expand Down Expand Up @@ -350,23 +362,13 @@ func (w *Watcher) watchAllModelSummaries(ctx context.Context, ctl *dbmodel.Contr
// connect to the controller
api, err := w.dialController(ctx, ctl)
if err != nil {
if !ctl.UnavailableSince.Valid {
ctl.UnavailableSince = db.Now()
var err error
if err = w.Database.UpdateController(ctx, ctl); err != nil {
zapctx.Error(ctx, "cannot set controller unavailable", zap.Error(err))
}
if w.controllerUnavailableChan != nil {
select {
case w.controllerUnavailableChan <- err:
default:
}
}
}
updateControllerAvailability(ctx, w.Database, ctl, err, w.controllerUnavailableChan)
return errors.E(op, err)
}
defer api.Close()

updateControllerAvailability(ctx, w.Database, ctl, nil, w.controllerUnavailableChan)

if !api.SupportsModelSummaryWatcher() {
return errors.E(op, errors.CodeNotSupported)
}
Expand Down
74 changes: 72 additions & 2 deletions internal/jimm/watcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -805,13 +805,83 @@ func TestWatcherSetsControllerUnavailable(t *testing.T) {
err = w.Database.GetController(ctx, &ctl)
c.Assert(err, qt.IsNil)
c.Check(ctl.UnavailableSince.Valid, qt.Equals, true)
c.Logf("%v %v", ctl.UnavailableSince.Time, time.Now())
c.Check(ctl.UnavailableSince.Time.After(time.Now().Add(-10*time.Millisecond)), qt.Equals, true)
}
cancel()
wg.Wait()
}

func TestWatcherClearsControllerUnavailable(t *testing.T) {
c := qt.New(t)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

controllerUnavailableChannel := make(chan error, 1)
w := jimm.NewWatcherWithControllerUnavailableChan(
db.Database{
DB: jimmtest.PostgresDB(c, nil),
},
&jimmtest.Dialer{
API: &jimmtest.API{
AllModelWatcherNext_: func(_ context.Context, _ string) ([]jujuparams.Delta, error) {
cancel()
<-ctx.Done()
return nil, ctx.Err()
},
ModelInfo_: func(_ context.Context, info *jujuparams.ModelInfo) error {
switch info.UUID {
default:
c.Errorf("unexpected model uuid: %s", info.UUID)
case "00000002-0000-0000-0000-000000000002":
case "00000002-0000-0000-0000-000000000003":
}
return errors.E(errors.CodeNotFound)
},
WatchAllModels_: func(ctx context.Context) (string, error) {
return "1234", nil
},
},
},
&testPublisher{},
controllerUnavailableChannel,
)

env := jimmtest.ParseEnvironment(c, testWatcherEnv)
err := w.Database.Migrate(ctx, false)
c.Assert(err, qt.IsNil)
env.PopulateDB(c, w.Database)

// update controller's UnavailableSince field
ctl := dbmodel.Controller{
Name: "controller-1",
}
err = w.Database.GetController(ctx, &ctl)
c.Assert(err, qt.IsNil)
ctl.UnavailableSince = sql.NullTime{
Time: time.Now(),
Valid: true,
}
err = w.Database.UpdateController(ctx, &ctl)
c.Assert(err, qt.IsNil)

// start the watcher
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
err := w.Watch(ctx, time.Millisecond)
checkIfContextCanceled(c, ctx, err)
}()
wg.Wait()

// check that the unavailable since time has been cleared
cerr := <-controllerUnavailableChannel
c.Assert(cerr, qt.IsNil)

cancel()
wg.Wait()
}

func TestWatcherRemoveDyingModelsOnStartup(t *testing.T) {
c := qt.New(t)

Expand Down

0 comments on commit fecbb65

Please sign in to comment.