Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions pkg/services/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@ func (s *sqlClusterService) UpdateClusterStatusFromAdapters(
}

// ProcessAdapterStatus handles the business logic for adapter status:
// - If Available condition is "Unknown": returns (nil, nil) indicating no-op
// - If Available is "Unknown" and a status already exists: returns (nil, nil) as no-op
// - If Available is "Unknown" and no status exists (first report): upserts the status
// - Otherwise: upserts the status and triggers aggregation
func (s *sqlClusterService) ProcessAdapterStatus(
ctx context.Context, clusterID string, adapterStatus *api.AdapterStatus,
Expand Down Expand Up @@ -272,8 +273,13 @@ func (s *sqlClusterService) ProcessAdapterStatus(

hasAvailableCondition = true
if cond.Status == api.AdapterConditionUnknown {
// Available condition is "Unknown", return nil to indicate no-op
return nil, nil
if existingStatus != nil {
// Available condition is "Unknown" and a status already exists, return nil to indicate no-op
return nil, nil
}
// First report from this adapter: allow storing even with Available=Unknown
// but skip aggregation since Unknown should not affect cluster-level conditions
hasAvailableCondition = false
}
}

Expand Down
141 changes: 131 additions & 10 deletions pkg/services/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ func (d *mockAdapterStatusDao) All(ctx context.Context) (api.AdapterStatusList,

var _ dao.AdapterStatusDao = &mockAdapterStatusDao{}

// TestProcessAdapterStatus_UnknownCondition tests that Unknown Available condition returns nil (no-op)
func TestProcessAdapterStatus_UnknownCondition(t *testing.T) {
// TestProcessAdapterStatus_FirstUnknownCondition tests that the first Unknown Available condition is stored
func TestProcessAdapterStatus_FirstUnknownCondition(t *testing.T) {
RegisterTestingT(t)

clusterDao := newMockClusterDao()
Expand All @@ -186,21 +186,72 @@ func TestProcessAdapterStatus_UnknownCondition(t *testing.T) {
}
conditionsJSON, _ := json.Marshal(conditions)

now := time.Now()
adapterStatus := &api.AdapterStatus{
ResourceType: "Cluster",
ResourceID: clusterID,
Adapter: "test-adapter",
Conditions: conditionsJSON,
CreatedTime: &now,
}

result, err := service.ProcessAdapterStatus(ctx, clusterID, adapterStatus)

Expect(err).To(BeNil())
Expect(result).To(BeNil(), "ProcessAdapterStatus should return nil for Unknown status")
Expect(result).ToNot(BeNil(), "First report with Available=Unknown should be stored")
Expect(result.Adapter).To(Equal("test-adapter"))

// Verify nothing was stored
// Verify the status was stored
storedStatuses, _ := adapterStatusDao.FindByResource(ctx, "Cluster", clusterID)
Expect(len(storedStatuses)).To(Equal(0), "No status should be stored for Unknown")
Expect(len(storedStatuses)).To(Equal(1), "First Unknown status should be stored")
}

// TestProcessAdapterStatus_SubsequentUnknownCondition tests that subsequent Unknown Available conditions are discarded
func TestProcessAdapterStatus_SubsequentUnknownCondition(t *testing.T) {
RegisterTestingT(t)

clusterDao := newMockClusterDao()
adapterStatusDao := newMockAdapterStatusDao()

config := testAdapterConfig()
service := NewClusterService(clusterDao, adapterStatusDao, config)

ctx := context.Background()
clusterID := testClusterID

// Pre-populate an existing adapter status to simulate a previously stored report
conditions := []api.AdapterCondition{
{
Type: conditionTypeAvailable,
Status: api.AdapterConditionUnknown,
LastTransitionTime: time.Now(),
},
}
conditionsJSON, _ := json.Marshal(conditions)

now := time.Now()
existingStatus := &api.AdapterStatus{
ResourceType: "Cluster",
ResourceID: clusterID,
Adapter: "test-adapter",
Conditions: conditionsJSON,
CreatedTime: &now,
}
_, _ = adapterStatusDao.Upsert(ctx, existingStatus)

// Now send another Unknown status report
newAdapterStatus := &api.AdapterStatus{
ResourceType: "Cluster",
ResourceID: clusterID,
Adapter: "test-adapter",
Conditions: conditionsJSON,
CreatedTime: &now,
}

result, err := service.ProcessAdapterStatus(ctx, clusterID, newAdapterStatus)

Expect(err).To(BeNil())
Expect(result).To(BeNil(), "Subsequent Unknown status should be discarded")
}

// TestProcessAdapterStatus_TrueCondition tests that True Available condition upserts and aggregates
Expand Down Expand Up @@ -383,8 +434,9 @@ func TestProcessAdapterStatus_NoAvailableCondition(t *testing.T) {
"Cluster status conditions should not be overwritten when adapter status lacks Available")
}

// TestProcessAdapterStatus_MultipleConditions_AvailableUnknown tests multiple conditions with Available=Unknown
func TestProcessAdapterStatus_MultipleConditions_AvailableUnknown(t *testing.T) {
// TestProcessAdapterStatus_FirstMultipleConditions_AvailableUnknown tests that the first report with
// multiple conditions including Available=Unknown is stored
func TestProcessAdapterStatus_FirstMultipleConditions_AvailableUnknown(t *testing.T) {
RegisterTestingT(t)

clusterDao := newMockClusterDao()
Expand Down Expand Up @@ -416,21 +468,90 @@ func TestProcessAdapterStatus_MultipleConditions_AvailableUnknown(t *testing.T)
}
conditionsJSON, _ := json.Marshal(conditions)

now := time.Now()
adapterStatus := &api.AdapterStatus{
ResourceType: "Cluster",
ResourceID: clusterID,
Adapter: "test-adapter",
Conditions: conditionsJSON,
CreatedTime: &now,
}

result, err := service.ProcessAdapterStatus(ctx, clusterID, adapterStatus)

Expect(err).To(BeNil())
Expect(result).To(BeNil(), "ProcessAdapterStatus should return nil when Available=Unknown")
Expect(result).ToNot(BeNil(), "First report with Available=Unknown should be stored")

// Verify nothing was stored
// Verify the status was stored
storedStatuses, _ := adapterStatusDao.FindByResource(ctx, "Cluster", clusterID)
Expect(len(storedStatuses)).To(Equal(0), "No status should be stored for Unknown")
Expect(len(storedStatuses)).To(Equal(1), "First Unknown status should be stored")
}

// TestProcessAdapterStatus_SubsequentMultipleConditions_AvailableUnknown tests that subsequent reports
// with multiple conditions including Available=Unknown are discarded
func TestProcessAdapterStatus_SubsequentMultipleConditions_AvailableUnknown(t *testing.T) {
RegisterTestingT(t)

clusterDao := newMockClusterDao()
adapterStatusDao := newMockAdapterStatusDao()

config := testAdapterConfig()
service := NewClusterService(clusterDao, adapterStatusDao, config)

ctx := context.Background()
clusterID := testClusterID

// Pre-populate an existing adapter status
existingConditions := []api.AdapterCondition{
{
Type: conditionTypeAvailable,
Status: api.AdapterConditionUnknown,
LastTransitionTime: time.Now(),
},
}
existingConditionsJSON, _ := json.Marshal(existingConditions)

now := time.Now()
existingStatus := &api.AdapterStatus{
ResourceType: "Cluster",
ResourceID: clusterID,
Adapter: "test-adapter",
Conditions: existingConditionsJSON,
CreatedTime: &now,
}
_, _ = adapterStatusDao.Upsert(ctx, existingStatus)

// Now send another report with multiple conditions including Available=Unknown
conditions := []api.AdapterCondition{
{
Type: "Ready",
Status: api.AdapterConditionTrue,
LastTransitionTime: time.Now(),
},
{
Type: conditionTypeAvailable,
Status: api.AdapterConditionUnknown,
LastTransitionTime: time.Now(),
},
{
Type: "Progressing",
Status: api.AdapterConditionTrue,
LastTransitionTime: time.Now(),
},
}
conditionsJSON, _ := json.Marshal(conditions)

adapterStatus := &api.AdapterStatus{
ResourceType: "Cluster",
ResourceID: clusterID,
Adapter: "test-adapter",
Conditions: conditionsJSON,
}

result, err := service.ProcessAdapterStatus(ctx, clusterID, adapterStatus)

Expect(err).To(BeNil())
Expect(result).To(BeNil(), "Subsequent Available=Unknown should be discarded")
}

func TestClusterAvailableReadyTransitions(t *testing.T) {
Expand Down
12 changes: 9 additions & 3 deletions pkg/services/node_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ func (s *sqlNodePoolService) UpdateNodePoolStatusFromAdapters(
}

// ProcessAdapterStatus handles the business logic for adapter status:
// - If Available condition is "Unknown": returns (nil, nil) indicating no-op
// - If Available is "Unknown" and a status already exists: returns (nil, nil) as no-op
// - If Available is "Unknown" and no status exists (first report): upserts the status
// - Otherwise: upserts the status and triggers aggregation
func (s *sqlNodePoolService) ProcessAdapterStatus(
ctx context.Context, nodePoolID string, adapterStatus *api.AdapterStatus,
Expand Down Expand Up @@ -273,8 +274,13 @@ func (s *sqlNodePoolService) ProcessAdapterStatus(

hasAvailableCondition = true
if cond.Status == api.AdapterConditionUnknown {
// Available condition is "Unknown", return nil to indicate no-op
return nil, nil
if existingStatus != nil {
// Available condition is "Unknown" and a status already exists, return nil to indicate no-op
return nil, nil
}
// First report from this adapter: allow storing even with Available=Unknown
// but skip aggregation since Unknown should not affect nodepool-level conditions
hasAvailableCondition = false
}
}

Expand Down
Loading