Skip to content

Commit

Permalink
refactor(application): remove RunAtomic from SetApplicationLife
Browse files Browse the repository at this point in the history
Starting off with a simple example for reviewers.

Refactor SetApplicationLife to not be a RunAtomic method.
  • Loading branch information
jack-w-shaw committed Jan 30, 2025
1 parent 6565883 commit 9fb5a62
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 66 deletions.
52 changes: 25 additions & 27 deletions domain/application/service/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,6 @@ type AtomicApplicationState interface {
// found.
GetApplicationLife(ctx domain.AtomicContext, appName string) (coreapplication.ID, life.Life, error)

// SetApplicationLife sets the life of the specified application.
SetApplicationLife(domain.AtomicContext, coreapplication.ID, life.Life) error

// GetApplicationScaleState looks up the scale state of the specified
// application, returning an error satisfying
// [applicationerrors.ApplicationNotFound] if the application is not found.
Expand Down Expand Up @@ -202,6 +199,9 @@ type ApplicationState interface {
// application; the application name is used to filter.
GetApplicationUnitLife(ctx context.Context, appName string, unitUUIDs ...coreunit.UUID) (map[coreunit.UUID]life.Life, error)

// SetApplicationLife sets the life of the specified application.
SetApplicationLife(context.Context, coreapplication.ID, life.Life) error

// GetCharmByApplicationID returns the charm, charm origin and charm
// platform for the specified application ID.
//
Expand Down Expand Up @@ -1027,35 +1027,33 @@ func (s *Service) deleteApplication(ctx domain.AtomicContext, name string) ([]fu
// returning an error satisfying [applicationerrors.ApplicationNotFoundError]
// if the application doesn't exist.
func (s *Service) DestroyApplication(ctx context.Context, appName string) error {
appID, err := s.st.GetApplicationIDByName(ctx, appName)
if errors.Is(err, applicationerrors.ApplicationNotFound) {
return nil
} else if err != nil {
return internalerrors.Errorf("getting application ID: %w", err)
}
// For now, all we do is advance the application's life to Dying.
err := s.st.RunAtomic(ctx, func(ctx domain.AtomicContext) error {
appID, err := s.st.GetApplicationID(ctx, appName)
if errors.Is(err, applicationerrors.ApplicationNotFound) {
return nil
}
if err != nil {
return errors.Trace(err)
}
return s.st.SetApplicationLife(ctx, appID, life.Dying)
})
return errors.Annotatef(err, "destroying application %q", appName)
err = s.st.SetApplicationLife(ctx, appID, life.Dying)
if err != nil {
return internalerrors.Errorf("destroying application %q: %w", appName, err)
}
return nil
}

// EnsureApplicationDead is called by the cleanup worker if a mongo
// MarkApplicationDead is called by the cleanup worker if a mongo
// destroy operation sets the application to dead.
// TODO(units): remove when everything is in dqlite.
func (s *Service) EnsureApplicationDead(ctx context.Context, appName string) error {
err := s.st.RunAtomic(ctx, func(ctx domain.AtomicContext) error {
appID, err := s.st.GetApplicationID(ctx, appName)
if errors.Is(err, applicationerrors.ApplicationNotFound) {
return nil
}
if err != nil {
return errors.Trace(err)
}
return s.st.SetApplicationLife(ctx, appID, life.Dead)
})
return errors.Annotatef(err, "setting application %q life to Dead", appName)
func (s *Service) MarkApplicationDead(ctx context.Context, appName string) error {
appID, err := s.st.GetApplicationIDByName(ctx, appName)
if err != nil {
return internalerrors.Errorf("getting application ID: %w", err)
}
err = s.st.SetApplicationLife(ctx, appID, life.Dead)
if err != nil {
return internalerrors.Errorf("setting application %q life to Dead: %w", appName, err)
}
return nil
}

// UpdateApplicationCharm sets a new charm for the application, validating that aspects such
Expand Down
6 changes: 3 additions & 3 deletions domain/application/service/package_mock_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 8 additions & 7 deletions domain/application/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/juju/juju/domain/application/service"
"github.com/juju/juju/domain/application/state"
"github.com/juju/juju/domain/ipaddress"
domainlife "github.com/juju/juju/domain/life"
"github.com/juju/juju/domain/schema/testing"
domainsecret "github.com/juju/juju/domain/secret"
secretstate "github.com/juju/juju/domain/secret/state"
Expand Down Expand Up @@ -181,16 +182,16 @@ func (s *serviceSuite) TestDeleteApplicationNotFound(c *gc.C) {
c.Assert(err, jc.ErrorIs, applicationerrors.ApplicationNotFound)
}

func (s *serviceSuite) TestEnsureApplicationDead(c *gc.C) {
func (s *serviceSuite) TestMarkApplicationDead(c *gc.C) {
ctrl := gomock.NewController(c)
defer ctrl.Finish()

s.createApplication(c, "foo")

err := s.svc.EnsureApplicationDead(context.Background(), "foo")
err := s.svc.MarkApplicationDead(context.Background(), "foo")
c.Assert(err, jc.ErrorIsNil)

var gotLife int
var gotLife domainlife.Life
err = s.TxnRunner().StdTxn(context.Background(), func(ctx context.Context, tx *sql.Tx) error {
err := tx.QueryRowContext(ctx, "SELECT life_id FROM application WHERE name = ?", "foo").
Scan(&gotLife)
Expand All @@ -200,15 +201,15 @@ func (s *serviceSuite) TestEnsureApplicationDead(c *gc.C) {
return nil
})
c.Assert(err, jc.ErrorIsNil)
c.Assert(gotLife, gc.Equals, 2)
c.Assert(gotLife, gc.Equals, domainlife.Dead)
}

func (s *serviceSuite) TestEnsureApplicationDeadNotFound(c *gc.C) {
func (s *serviceSuite) TestMarkApplicationDeadNotFound(c *gc.C) {
ctrl := gomock.NewController(c)
defer ctrl.Finish()

err := s.svc.EnsureApplicationDead(context.Background(), "foo")
c.Assert(err, jc.ErrorIsNil)
err := s.svc.MarkApplicationDead(context.Background(), "foo")
c.Assert(err, jc.ErrorIs, applicationerrors.ApplicationNotFound)
}

func (s *serviceSuite) TestGetUnitLife(c *gc.C) {
Expand Down
18 changes: 13 additions & 5 deletions domain/application/state/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -1259,7 +1259,12 @@ WHERE name = $applicationDetails.name
}

// SetApplicationLife sets the life of the specified application.
func (st *State) SetApplicationLife(ctx domain.AtomicContext, appUUID coreapplication.ID, l life.Life) error {
func (st *State) SetApplicationLife(ctx context.Context, appUUID coreapplication.ID, l life.Life) error {
db, err := st.DB()
if err != nil {
return errors.Trace(err)
}

lifeQuery := `
UPDATE application
SET life_id = $applicationIDAndLife.life_id
Expand All @@ -1273,11 +1278,14 @@ AND life_id <= $applicationIDAndLife.life_id
return errors.Trace(err)
}

err = domain.Run(ctx, func(ctx context.Context, tx *sqlair.TX) error {
err := tx.Query(ctx, lifeStmt, app).Run()
return errors.Trace(err)
err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error {
err = tx.Query(ctx, lifeStmt, app).Run()
if err != nil {
return internalerrors.Errorf("updating application life for %q: %w", appUUID, err)
}
return nil
})
return errors.Annotatef(err, "updating application life for %q", appUUID)
return errors.Trace(err)
}

// SetDesiredApplicationScale updates the desired scale of the specified
Expand Down
13 changes: 4 additions & 9 deletions domain/application/state/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1546,6 +1546,7 @@ func (s *applicationStateSuite) TestSetApplicationScalingState(c *gc.C) {

func (s *applicationStateSuite) TestSetApplicationLife(c *gc.C) {
appID := s.createApplication(c, "foo", life.Alive)
ctx := context.Background()

checkResult := func(want life.Life) {
var gotLife life.Life
Expand All @@ -1558,22 +1559,16 @@ func (s *applicationStateSuite) TestSetApplicationLife(c *gc.C) {
c.Assert(gotLife, jc.DeepEquals, want)
}

err := s.state.RunAtomic(context.Background(), func(ctx domain.AtomicContext) error {
return s.state.SetApplicationLife(ctx, appID, life.Dying)
})
err := s.state.SetApplicationLife(ctx, appID, life.Dying)
c.Assert(err, jc.ErrorIsNil)
checkResult(life.Dying)

err = s.state.RunAtomic(context.Background(), func(ctx domain.AtomicContext) error {
return s.state.SetApplicationLife(ctx, appID, life.Dead)
})
err = s.state.SetApplicationLife(ctx, appID, life.Dead)
c.Assert(err, jc.ErrorIsNil)
checkResult(life.Dead)

// Can't go backwards.
err = s.state.RunAtomic(context.Background(), func(ctx domain.AtomicContext) error {
return s.state.SetApplicationLife(ctx, appID, life.Dying)
})
err = s.state.SetApplicationLife(ctx, appID, life.Dying)
c.Assert(err, jc.ErrorIsNil)
checkResult(life.Dead)
}
Expand Down
18 changes: 7 additions & 11 deletions domain/application/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -1306,33 +1306,29 @@ WHERE name = $applicationDetails.name
// returned.
func (st *State) checkApplicationExists(ctx context.Context, tx *sqlair.TX, ident applicationID) error {
query := `
SELECT &applicationIDAndLife.*
SELECT &applicationLife.*
FROM application
WHERE uuid = $applicationID.uuid;
`
stmt, err := st.Prepare(query, ident, applicationIDAndLife{})
stmt, err := st.Prepare(query, ident, applicationLife{})
if err != nil {
return internalerrors.Errorf("preparing query for application %q: %w", ident.ID, err)
}

var result applicationIDAndLife
var result applicationLife
err = tx.Query(ctx, stmt, ident).Get(&result)
if errors.Is(err, sql.ErrNoRows) {
return applicationerrors.ApplicationNotFound
} else if err != nil {
return internalerrors.Errorf("checking application %q exists: %w", ident.ID, err)
}

// If we're not alive or dying, then the application is dead.
if result.LifeID > domainlife.Dying {
switch result.LifeID {
case domainlife.Dead:
return applicationerrors.ApplicationIsDead
default:
return nil
}

if err := result.ID.Validate(); err != nil {
return internalerrors.Errorf("invalid application ID %q: %w", result.ID, err)
}

return nil
}

func decodeCharmState(state charmState) (charm.Charm, error) {
Expand Down
9 changes: 7 additions & 2 deletions domain/application/state/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,22 @@ type KeyValue struct {
Value string `db:"value"`
}

// applicationID is used to get the ID (and life) of an application.
// applicationID is used to get the ID of an application.
type applicationID struct {
ID coreapplication.ID `db:"uuid"`
}

// applicationIDAndLife is used to get the ID (and life) of an application.
// applicationIDAndLife is used to get the ID and life of an application.
type applicationIDAndLife struct {
ID coreapplication.ID `db:"uuid"`
LifeID life.Life `db:"life_id"`
}

// applicationLife is used to get the life of an application.
type applicationLife struct {
LifeID life.Life `db:"life_id"`
}

type applicationChannel struct {
ApplicationID coreapplication.ID `db:"application_uuid"`
Track string `db:"track"`
Expand Down
4 changes: 2 additions & 2 deletions state/cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ type MachineRemover interface {
type ApplicationAndUnitRemover interface {
DestroyApplication(context.Context, string) error
DeleteApplication(context.Context, string) error
EnsureApplicationDead(ctx context.Context, appName string) error
MarkApplicationDead(ctx context.Context, appName string) error
EnsureUnitDead(context.Context, coreunit.Name, leadership.Revoker) error
DestroyUnit(context.Context, coreunit.Name) error
DeleteUnit(context.Context, coreunit.Name) error
Expand Down Expand Up @@ -629,7 +629,7 @@ func (st *State) cleanupApplication(ctx context.Context, store objectstore.Objec
err = appService.DeleteApplication(ctx, appName)
}
if op.PostDestroyAppLife == Dead {
err = appService.EnsureApplicationDead(ctx, appName)
err = appService.MarkApplicationDead(ctx, appName)
}
}
return err
Expand Down

0 comments on commit 9fb5a62

Please sign in to comment.