Skip to content

Commit

Permalink
Merge pull request juju#18991 from jack-w-shaw/JUJU-7587_implement_st…
Browse files Browse the repository at this point in the history
…ate_get_cloud_container

juju#18991

We recently added application service methods GetUnitDisplayStatus and GetApplicationDisplayStatus, which were backed by the stub state methods GetUnitCloudContainerStatus and GetUnitCloudContainerStatusesForApplication

Implement these methods.

This required one very small change to the service layer.

Related: juju#18977
There's a bug in GetUnitWorkloadStatus addressed in this PR, but there was some disagreement over best way to resolve it. Until we have. Leave TODOs here to remind us to come back and solve it

## QA steps

Not wired up yet. Unit test pass
  • Loading branch information
jujubot authored Feb 23, 2025
2 parents 8951305 + 7fe9936 commit fb89aa4
Show file tree
Hide file tree
Showing 5 changed files with 299 additions and 34 deletions.
29 changes: 19 additions & 10 deletions domain/application/service/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,28 +74,35 @@ type ApplicationState interface {
// SetUnitPassword updates the password for the specified unit UUID.
SetUnitPassword(context.Context, coreunit.UUID, application.PasswordInfo) error

// GetUnitWorkloadStatus returns the workload status of the specified unit, returning an
// error satisfying [applicationerrors.UnitNotFound] if the unit doesn't exist.
// GetUnitWorkloadStatus returns the workload status of the specified unit, returning:
// - an error satisfying [applicationerrors.UnitNotFound] if the unit doesn't exist or;
// - an error satisfying [applicationerrors.UnitStatusNotFound] if the status is not set.
GetUnitWorkloadStatus(context.Context, coreunit.UUID) (*application.StatusInfo[application.WorkloadStatusType], error)

// SetUnitWorkloadStatus sets the workload status of the specified unit, returning an
// error satisfying [applicationerrors.UnitNotFound] if the unit doesn't exist.
SetUnitWorkloadStatus(context.Context, coreunit.UUID, *application.StatusInfo[application.WorkloadStatusType]) error

// GetUnitCloudContainerStatus returns the cloud container status of the specified
// unit, returning an error satisfying [applicationerrors.UnitNotFound] if the unit
// doesn't exist.
// unit. It returns;
// - an error satisfying [applicationerrors.UnitNotFound] if the unit doesn't exist or;
// - an error satisfying [applicationerrors.UnitStatusNotFound] if the status is not set.
GetUnitCloudContainerStatus(context.Context, coreunit.UUID) (*application.StatusInfo[application.CloudContainerStatusType], error)

// GetUnitWorkloadStatusesForApplication returns the workload statuses for all units
// of the specified application, indexed by unit name, returning an error satisfying
// [applicationerrors.ApplicationNotFound] if the application doesn't exist.
// of the specified application, returning:
// - an error satisfying [applicationerrors.ApplicationNotFound] if the application
// doesn't exist or;
// - error satisfying [applicationerrors.ApplicationIsDead] if the application
// is dead.
GetUnitWorkloadStatusesForApplication(context.Context, coreapplication.ID) (map[coreunit.Name]application.StatusInfo[application.WorkloadStatusType], error)

// GetUnitCloudContainerStatusesForApplication returns the cloud container
// statuses for all units of the specified application, indexed by unit name,
// returning an error satisfying [applicationerrors.ApplicationNotFound] if
// the application doesn't exist.
// statuses for all units of the specified application, returning:
// - an error satisfying [applicationerrors.ApplicationNotFound] if the application
// doesn't exist or;
// - an error satisfying [applicationerrors.ApplicationIsDead] if the application
// is dead.
GetUnitCloudContainerStatusesForApplication(context.Context, coreapplication.ID) (map[coreunit.Name]application.StatusInfo[application.CloudContainerStatusType], error)

// DeleteUnit deletes the specified unit.
Expand Down Expand Up @@ -945,7 +952,9 @@ func (s *Service) GetUnitDisplayStatus(ctx context.Context, unitName coreunit.Na
return nil, errors.Trace(err)
}
containerStatus, err := s.st.GetUnitCloudContainerStatus(ctx, unitUUID)
if err != nil {
if errors.Is(err, applicationerrors.UnitStatusNotFound) {
return unitDisplayStatus(workloadStatus, nil)
} else if err != nil {
return nil, errors.Trace(err)
}
return unitDisplayStatus(workloadStatus, containerStatus)
Expand Down
2 changes: 1 addition & 1 deletion domain/application/service/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1489,7 +1489,7 @@ func (s *applicationServiceSuite) TestGetUnitDisplayStatusNoContainer(c *gc.C) {
Since: &now,
}, nil)

s.state.EXPECT().GetUnitCloudContainerStatus(gomock.Any(), unitUUID).Return(nil, nil)
s.state.EXPECT().GetUnitCloudContainerStatus(gomock.Any(), unitUUID).Return(nil, applicationerrors.UnitStatusNotFound)

obtained, err := s.service.GetUnitDisplayStatus(context.Background(), coreunit.Name("foo/666"))
c.Assert(err, jc.ErrorIsNil)
Expand Down
141 changes: 118 additions & 23 deletions domain/application/state/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -554,8 +554,9 @@ WHERE uuid = $unitPassword.uuid
return nil
}

// GetUnitWorkloadStatus returns the workload status of the specified unit, returning an error
// satisfying [applicationerrors.UnitNotFound] if the unit doesn't exist.
// GetUnitWorkloadStatus returns the workload status of the specified unit, returning:
// - an error satisfying [applicationerrors.UnitNotFound] if the unit doesn't exist or;
// - an error satisfying [applicationerrors.UnitStatusNotFound] if the status is not set.
func (st *State) GetUnitWorkloadStatus(ctx context.Context, uuid coreunit.UUID) (*application.StatusInfo[application.WorkloadStatusType], error) {
db, err := st.DB()
if err != nil {
Expand All @@ -572,6 +573,7 @@ SELECT &statusInfo.* FROM unit_workload_status WHERE unit_uuid = $unitUUID.uuid

var unitStatusInfo statusInfo
err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error {
// TODO(jack-w-shaw): Check unit exists
err := tx.Query(ctx, getUnitStatusStmt, unitUUID).Get(&unitStatusInfo)
if errors.Is(err, sql.ErrNoRows) {
return errors.Errorf("workload status for unit %q not found%w", unitUUID, jujuerrors.Hide(applicationerrors.UnitStatusNotFound))
Expand Down Expand Up @@ -613,18 +615,57 @@ func (st *State) SetUnitWorkloadStatus(ctx context.Context, unitUUID coreunit.UU
}

// GetUnitCloudContainerStatus returns the cloud container status of the specified
// unit, returning an error satisfying [applicationerrors.UnitNotFound] if the unit
// doesn't exist.
//
// TODO(jack-w-shaw): Implement me!
// unit. It returns;
// - an error satisfying [applicationerrors.UnitNotFound] if the unit doesn't exist or;
// - an error satisfying [applicationerrors.UnitStatusNotFound] if the status is not set.
func (st *State) GetUnitCloudContainerStatus(ctx context.Context, uuid coreunit.UUID) (*application.StatusInfo[application.CloudContainerStatusType], error) {
return nil, nil
db, err := st.DB()
if err != nil {
return nil, jujuerrors.Trace(err)
}

unitUUID := unitUUID{UnitUUID: uuid}
getUnitStatusStmt, err := st.Prepare(`
SELECT &statusInfo.*
FROM k8s_pod_status
WHERE unit_uuid = $unitUUID.uuid
`, statusInfo{}, unitUUID)
if err != nil {
return nil, jujuerrors.Trace(err)
}

var containerStatusInfo statusInfo
err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error {
// TODO(jack-w-shaw): Check unit exists
err = tx.Query(ctx, getUnitStatusStmt, unitUUID).Get(&containerStatusInfo)
if errors.Is(err, sql.ErrNoRows) {
return errors.Errorf("workload status for unit %q not found%w", unitUUID, jujuerrors.Hide(applicationerrors.UnitStatusNotFound))
}
return errors.Capture(err)
})
if err != nil {
return nil, errors.Errorf("getting cloud container status for unit %q: %w", unitUUID, err)
}

statusID, err := decodeCloudContainerStatus(containerStatusInfo.StatusID)
if err != nil {
return nil, errors.Errorf("decoding cloud container status ID for unit %q: %w", uuid, err)
}
return &application.StatusInfo[application.CloudContainerStatusType]{
Status: statusID,
Message: containerStatusInfo.Message,
Data: containerStatusInfo.Data,
Since: containerStatusInfo.UpdatedAt,
}, nil
}

// GetUnitWorkloadStatusesForApplication returns the workload statuses for all units
// of the specified application, indexed by unit name, returning an error satisfying
// [applicationerrors.ApplicationNotFound] if the application doesn't exist.
func (st *State) GetUnitWorkloadStatusesForApplication(ctx context.Context, appId coreapplication.ID) (map[coreunit.Name]application.StatusInfo[application.WorkloadStatusType], error) {
// of the specified application, returning:
// - an error satisfying [applicationerrors.ApplicationNotFound] if the application
// doesn't exist or;
// - error satisfying [applicationerrors.ApplicationIsDead] if the application
// is dead.
func (st *State) GetUnitWorkloadStatusesForApplication(ctx context.Context, appID coreapplication.ID) (map[coreunit.Name]application.StatusInfo[application.WorkloadStatusType], error) {
type statusInfoAndUnitName struct {
UnitName coreunit.Name `db:"name"`
StatusID int `db:"status_id"`
Expand All @@ -638,7 +679,7 @@ func (st *State) GetUnitWorkloadStatusesForApplication(ctx context.Context, appI
return nil, jujuerrors.Trace(err)
}

ident := applicationID{ID: appId}
ident := applicationID{ID: appID}
getUnitStatusesStmt, err := st.Prepare(`
SELECT &statusInfoAndUnitName.*
FROM unit_workload_status
Expand All @@ -652,17 +693,17 @@ WHERE unit.application_uuid = $applicationID.uuid
var unitStatuses []statusInfoAndUnitName
err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error {
err := st.checkApplicationExists(ctx, tx, ident)
if err != nil && !errors.Is(err, applicationerrors.ApplicationIsDead) {
return err
if err != nil {
return errors.Capture(err)
}
err = tx.Query(ctx, getUnitStatusesStmt, ident).GetAll(&unitStatuses)
if errors.Is(err, sql.ErrNoRows) {
return nil
}
return err
return errors.Capture(err)
})
if err != nil {
return nil, errors.Errorf("getting workload statuses for application %q: %w", appId, err)
return nil, errors.Errorf("getting workload statuses for application %q: %w", appID, err)
}

statuses := make(map[coreunit.Name]application.StatusInfo[application.WorkloadStatusType], len(unitStatuses))
Expand All @@ -683,13 +724,67 @@ WHERE unit.application_uuid = $applicationID.uuid
}

// GetUnitCloudContainerStatusesForApplication returns the cloud container
// statuses for all units of the specified application, returning an error
// satisfying [applicationerrors.ApplicationNotFound] if the application
// doesn't exist.
//
// TODO(jack-w-shaw): Implement me!
func (st *State) GetUnitCloudContainerStatusesForApplication(context.Context, coreapplication.ID) (map[coreunit.Name]application.StatusInfo[application.CloudContainerStatusType], error) {
return nil, nil
// statuses for all units of the specified application, returning:
// - an error satisfying [applicationerrors.ApplicationNotFound] if the application
// doesn't exist or;
// - an error satisfying [applicationerrors.ApplicationIsDead] if the application
// is dead.
func (st *State) GetUnitCloudContainerStatusesForApplication(ctx context.Context, appID coreapplication.ID) (map[coreunit.Name]application.StatusInfo[application.CloudContainerStatusType], error) {
type statusInfoAndUnitName struct {
UnitName coreunit.Name `db:"name"`
StatusID int `db:"status_id"`
Message string `db:"message"`
Data []byte `db:"data"`
UpdatedAt *time.Time `db:"updated_at"`
}

db, err := st.DB()
if err != nil {
return nil, jujuerrors.Trace(err)
}

ident := applicationID{ID: appID}
getContainerStatusesStmt, err := st.Prepare(`
SELECT &statusInfoAndUnitName.*
FROM k8s_pod_status
JOIN unit ON unit.uuid = k8s_pod_status.unit_uuid
WHERE unit.application_uuid = $applicationID.uuid
`, statusInfoAndUnitName{}, ident)
if err != nil {
return nil, jujuerrors.Trace(err)
}

var containerStatuses []statusInfoAndUnitName
err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error {
err := st.checkApplicationExists(ctx, tx, ident)
if err != nil {
return errors.Capture(err)
}
err = tx.Query(ctx, getContainerStatusesStmt, ident).GetAll(&containerStatuses)
if errors.Is(err, sql.ErrNoRows) {
return nil
}
return errors.Capture(err)
})
if err != nil {
return nil, errors.Errorf("getting cloud container statuses for application %q: %w", appID, err)
}

statuses := make(map[coreunit.Name]application.StatusInfo[application.CloudContainerStatusType], len(containerStatuses))
for _, containerStatus := range containerStatuses {
statusID, err := decodeCloudContainerStatus(containerStatus.StatusID)
if err != nil {
return nil, errors.Errorf("decoding cloud container status ID for unit %q: %w", containerStatus.UnitName, err)
}
statuses[containerStatus.UnitName] = application.StatusInfo[application.CloudContainerStatusType]{
Status: statusID,
Message: containerStatus.Message,
Data: containerStatus.Data,
Since: containerStatus.UpdatedAt,
}
}

return statuses, nil
}

func makeCloudContainerArg(unitName coreunit.Name, cloudContainer application.CloudContainerParams) *application.CloudContainer {
Expand Down Expand Up @@ -1490,7 +1585,7 @@ func (st *State) GetUnitLife(ctx context.Context, unitName coreunit.Name) (life.
err = db.Txn(ctx, func(ctx context.Context, tx *sqlair.TX) error {
var err error
life, err = st.getUnitLife(ctx, tx, unitName)
return err
return errors.Capture(err)
})
if err != nil {
return 0, errors.Errorf("querying unit %q life: %w", unitName, err)
Expand Down
Loading

0 comments on commit fb89aa4

Please sign in to comment.