Skip to content

Commit

Permalink
Rework how resources are calculated (#3238)
Browse files Browse the repository at this point in the history
  • Loading branch information
aruiz14 authored Jan 27, 2025
1 parent 608330e commit 2515936
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 135 deletions.
226 changes: 92 additions & 134 deletions internal/resourcestatus/resourcekey.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (
)

func SetResources(list *fleet.BundleDeploymentList, status *fleet.StatusBase) {
r, errors := fromResources(list)
byCluster, errors := fromResources(list)
status.ResourceErrors = errors
status.Resources = merge(r)
status.Resources = aggregateResourceStatesClustersMap(byCluster)
status.ResourceCounts = sumResourceCounts(list)
status.PerClusterResourceCounts = resourceCountsPerCluster(list)
}
Expand All @@ -21,32 +21,6 @@ func SetClusterResources(list *fleet.BundleDeploymentList, cluster *fleet.Cluste
cluster.Status.ResourceCounts = sumResourceCounts(list)
}

// merge takes a list of GitRepo resources and deduplicates resources deployed to multiple clusters,
// ensuring that for such resources, the output contains a single resource entry with a field summarizing
// its status on each cluster.
func merge(resources []fleet.Resource) []fleet.Resource {
merged := map[string]fleet.Resource{}
for _, resource := range resources {
key := key(resource)
if existing, ok := merged[key]; ok {
existing.PerClusterState = append(existing.PerClusterState, resource.PerClusterState...)
merged[key] = existing
} else {
merged[key] = resource
}
}

var result []fleet.Resource
for _, resource := range merged {
result = append(result, resource)
}

sort.Slice(result, func(i, j int) bool {
return key(result[i]) < key(result[j])
})
return result
}

func key(resource fleet.Resource) string {
return resource.Type + "/" + resource.ID
}
Expand All @@ -63,124 +37,94 @@ func resourceCountsPerCluster(list *fleet.BundleDeploymentList) map[string]*flee
return res
}

// fromResources inspects all bundledeployments for this GitRepo and returns a list of
// Resources and error messages.
//
// It populates gitrepo status resources from bundleDeployments. BundleDeployment.Status.Resources is the list of deployed resources.
func fromResources(list *fleet.BundleDeploymentList) ([]fleet.Resource, []string) {
type resourceStateEntry struct {
fleet.ResourcePerClusterState
incomplete bool
}

type resourceStatesByResourceKey map[fleet.ResourceKey][]resourceStateEntry

// fromResources inspects a list of BundleDeployments and returns a list of per-cluster states per resource keys.
// It also returns a list of errors messages produced that may have occurred during processing
func fromResources(list *fleet.BundleDeploymentList) (resourceStatesByResourceKey, []string) {
var (
resources []fleet.Resource
errors []string
resources = make(resourceStatesByResourceKey)
)

for _, bd := range list.Items {
state := summary.GetDeploymentState(&bd)
bdResources := bundleDeploymentResources(bd)
incomplete, err := addState(bd, bdResources)
clusterID := bd.Labels[fleet.ClusterNamespaceLabel] + "/" + bd.Labels[fleet.ClusterLabel]

if len(err) > 0 {
incomplete = true
for _, err := range err {
bdResources, errs := bundleDeploymentResources(bd)
if len(errs) > 0 {
for _, err := range errs {
errors = append(errors, err.Error())
}
}

for k, perCluster := range bdResources {
resource := toResourceState(k, perCluster, incomplete, string(state))
resources = append(resources, resource)
for key, state := range bdResources {
state.ClusterID = clusterID
resources[key] = append(resources[key], resourceStateEntry{state, bd.Status.IncompleteState})
}
}
for _, entries := range resources {
sort.Slice(entries, func(i, j int) bool {
return entries[i].ClusterID < entries[j].ClusterID
})
}

sort.Strings(errors)

return resources, errors
}

func toResourceState(k fleet.ResourceKey, perCluster []fleet.ResourcePerClusterState, incomplete bool, bdState string) fleet.Resource {
resource := fleet.Resource{
APIVersion: k.APIVersion,
Kind: k.Kind,
Namespace: k.Namespace,
Name: k.Name,
IncompleteState: incomplete,
PerClusterState: perCluster,
}
resource.Type, resource.ID = toType(resource)

for _, state := range perCluster {
resource.State = state.State
resource.Error = state.Error
resource.Transitioning = state.Transitioning
resource.Message = state.Message
break
func resourceId(namespace, name string) string {
if namespace != "" {
return namespace + "/" + name
}

// fallback to state from gitrepo summary
if resource.State == "" {
if resource.IncompleteState {
if bdState != "" {
resource.State = bdState
} else {
resource.State = "Unknown"
}
} else if bdState != "" {
resource.State = bdState
} else {
resource.State = "Ready"
}
}

sort.Slice(perCluster, func(i, j int) bool {
return perCluster[i].ClusterID < perCluster[j].ClusterID
})
return resource
return name
}

func toType(resource fleet.Resource) (string, string) {
group := strings.Split(resource.APIVersion, "/")[0]
func toType(apiVersion, kind string) string {
group := strings.Split(apiVersion, "/")[0]
if group == "v1" {
group = ""
} else if len(group) > 0 {
group += "."
}
t := group + strings.ToLower(resource.Kind)
if resource.Namespace == "" {
return t, resource.Name
}
return t, resource.Namespace + "/" + resource.Name
return group + strings.ToLower(kind)
}

// addState adds per-cluster state information for nonReady and modified resources in a bundleDeployment.
// It only adds up to 10 entries to not overwhelm the status.
// It mutates resources and returns whether the reported state is incomplete and any errors encountered.
func addState(bd fleet.BundleDeployment, resources map[fleet.ResourceKey][]fleet.ResourcePerClusterState) (bool, []error) {
var (
incomplete bool
errors []error
)
func bundleDeploymentResources(bd fleet.BundleDeployment) (map[fleet.ResourceKey]fleet.ResourcePerClusterState, []error) {
defaultState := string(summary.GetDeploymentState(&bd))

if len(bd.Status.NonReadyStatus) >= 10 || len(bd.Status.ModifiedStatus) >= 10 {
incomplete = true
resources := make(map[fleet.ResourceKey]fleet.ResourcePerClusterState, len(bd.Status.Resources))
for _, bdResource := range bd.Status.Resources {
resourceKey := fleet.ResourceKey{
Kind: bdResource.Kind,
APIVersion: bdResource.APIVersion,
Name: bdResource.Name,
Namespace: bdResource.Namespace,
}
resources[resourceKey] = fleet.ResourcePerClusterState{
State: defaultState,
}
}

cluster := bd.Labels[fleet.ClusterNamespaceLabel] + "/" + bd.Labels[fleet.ClusterLabel]
for _, nonReady := range bd.Status.NonReadyStatus {
key := fleet.ResourceKey{
resourceKey := fleet.ResourceKey{
Kind: nonReady.Kind,
APIVersion: nonReady.APIVersion,
Namespace: nonReady.Namespace,
Name: nonReady.Name,
}
state := fleet.ResourcePerClusterState{
resources[resourceKey] = fleet.ResourcePerClusterState{
State: nonReady.Summary.State,
Error: nonReady.Summary.Error,
Transitioning: nonReady.Summary.Transitioning,
Message: strings.Join(nonReady.Summary.Message, "; "),
ClusterID: cluster,
}
appendState(resources, key, state)
}

var errors []error
for _, modified := range bd.Status.ModifiedStatus {
key := fleet.ResourceKey{
Kind: modified.Kind,
Expand All @@ -189,54 +133,68 @@ func addState(bd fleet.BundleDeployment, resources map[fleet.ResourceKey][]fleet
Name: modified.Name,
}
state := fleet.ResourcePerClusterState{
State: "Modified",
ClusterID: cluster,
State: "Modified",
}
if modified.Delete {
state.State = "Orphaned"
} else if modified.Create {
state.State = "Missing"
} else if len(modified.Patch) > 0 {
state.Patch = &fleet.GenericMap{}
err := json.Unmarshal([]byte(modified.Patch), state.Patch)
if err != nil {
if err := json.Unmarshal([]byte(modified.Patch), state.Patch); err != nil {
errors = append(errors, err)
}
}
appendState(resources, key, state)
resources[key] = state
}
return incomplete, errors

return resources, errors
}

func appendState(states map[fleet.ResourceKey][]fleet.ResourcePerClusterState, key fleet.ResourceKey, state fleet.ResourcePerClusterState) {
if existing, ok := states[key]; ok || key.Namespace != "" {
states[key] = append(existing, state)
return
}
func aggregateResourceStatesClustersMap(resourceKeyStates resourceStatesByResourceKey) []fleet.Resource {
byResourceKey := make(map[fleet.ResourceKey]*fleet.Resource)
for resourceKey, entries := range resourceKeyStates {
if _, ok := byResourceKey[resourceKey]; !ok {
byResourceKey[resourceKey] = &fleet.Resource{
Kind: resourceKey.Kind,
APIVersion: resourceKey.APIVersion,
Namespace: resourceKey.Namespace,
Name: resourceKey.Name,
State: "Ready",
Type: toType(resourceKey.APIVersion, resourceKey.Kind),
ID: resourceId(resourceKey.Namespace, resourceKey.Name),
}
}
resource := byResourceKey[resourceKey]

for _, entry := range entries {
if entry.incomplete {
resource.IncompleteState = true
}

for k, existing := range states {
if k.Name == key.Name &&
k.APIVersion == key.APIVersion &&
k.Kind == key.Kind {
delete(states, key)
k.Namespace = ""
states[k] = append(existing, state)
// "Ready" states are currently omitted
if entry.State == "Ready" {
continue
}

resource.PerClusterState = append(resource.PerClusterState, entry.ResourcePerClusterState)

// top-level state is set from first non "Ready" per-cluster state
if resource.State == "Ready" {
resource.State = entry.State
}
}
}
}

func bundleDeploymentResources(bd fleet.BundleDeployment) map[fleet.ResourceKey][]fleet.ResourcePerClusterState {
bdResources := map[fleet.ResourceKey][]fleet.ResourcePerClusterState{}
for _, resource := range bd.Status.Resources {
key := fleet.ResourceKey{
Kind: resource.Kind,
APIVersion: resource.APIVersion,
Name: resource.Name,
Namespace: resource.Namespace,
}
bdResources[key] = []fleet.ResourcePerClusterState{}
result := make([]fleet.Resource, 0, len(byResourceKey))
for _, resource := range byResourceKey {
result = append(result, *resource)
}
return bdResources
sort.Slice(result, func(i, j int) bool {
return key(result[i]) < key(result[j])
})

return result
}

func sumResourceCounts(list *fleet.BundleDeploymentList) fleet.ResourceCounts {
Expand Down
19 changes: 18 additions & 1 deletion internal/resourcestatus/resourcekey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ func TestSetResources(t *testing.T) {
DeploymentID: "id2",
},
Status: fleet.BundleDeploymentStatus{
Ready: false,
NonModified: true,
AppliedDeploymentID: "id1",
Resources: []fleet.BundleDeploymentResource{
{
Expand Down Expand Up @@ -59,6 +61,8 @@ func TestSetResources(t *testing.T) {
},
},
Status: fleet.BundleDeploymentStatus{
Ready: true,
NonModified: true,
Resources: []fleet.BundleDeploymentResource{
{
Kind: "Deployment",
Expand All @@ -83,6 +87,8 @@ func TestSetResources(t *testing.T) {
},
},
Status: fleet.BundleDeploymentStatus{
Ready: true,
NonModified: true,
Resources: []fleet.BundleDeploymentResource{
{
Kind: "ConfigMap",
Expand Down Expand Up @@ -110,6 +116,8 @@ func TestSetResources(t *testing.T) {
DeploymentID: "id2",
},
Status: fleet.BundleDeploymentStatus{
Ready: false,
NonModified: true,
AppliedDeploymentID: "id1",
NonReadyStatus: []fleet.NonReadyStatus{
{
Expand Down Expand Up @@ -161,6 +169,10 @@ func TestSetResources(t *testing.T) {
Transitioning: false,
Message: "",
PerClusterState: []fleet.ResourcePerClusterState{
{
State: "WaitApplied",
ClusterID: "c-ns1/cluster1",
},
{
State: "Pending",
ClusterID: "c-ns2/cluster1",
Expand All @@ -185,7 +197,12 @@ func TestSetResources(t *testing.T) {
Error: false,
Transitioning: false,
Message: "",
PerClusterState: []fleet.ResourcePerClusterState{},
PerClusterState: []fleet.ResourcePerClusterState{
{
State: "WaitApplied",
ClusterID: "c-ns1/cluster1",
},
},
})

assert.Empty(t, status.ResourceErrors)
Expand Down

0 comments on commit 2515936

Please sign in to comment.