diff --git a/charts/fleet-crd/templates/crds.yaml b/charts/fleet-crd/templates/crds.yaml index 5e85d769c4..a6d752fe99 100644 --- a/charts/fleet-crd/templates/crds.yaml +++ b/charts/fleet-crd/templates/crds.yaml @@ -6622,6 +6622,46 @@ spec: except for changes to .metadata or .status.' format: int64 type: integer + perClusterResourceCounts: + additionalProperties: + description: ResourceCounts contains the number of resources in + each state. + properties: + desiredReady: + description: DesiredReady is the number of resources that + should be ready. + type: integer + missing: + description: Missing is the number of missing resources. + type: integer + modified: + description: Modified is the number of resources that have + been modified. + type: integer + notReady: + description: 'NotReady is the number of not ready resources. + Resources are not + + ready if they do not match any other state.' + type: integer + orphaned: + description: Orphaned is the number of orphaned resources. + type: integer + ready: + description: Ready is the number of ready resources. + type: integer + unknown: + description: Unknown is the number of resources in an unknown + state. + type: integer + waitApplied: + description: WaitApplied is the number of resources that are + waiting to be applied. + type: integer + type: object + description: PerClusterResourceCounts contains the number of resources + in each state over all bundles, per cluster. + type: object readyClusters: description: 'ReadyClusters is the lowest number of clusters that are ready over @@ -8417,6 +8457,46 @@ spec: BundleState according to StateRank.' type: string type: object + perClusterResourceCounts: + additionalProperties: + description: ResourceCounts contains the number of resources in + each state. + properties: + desiredReady: + description: DesiredReady is the number of resources that + should be ready. + type: integer + missing: + description: Missing is the number of missing resources. + type: integer + modified: + description: Modified is the number of resources that have + been modified. + type: integer + notReady: + description: 'NotReady is the number of not ready resources. + Resources are not + + ready if they do not match any other state.' + type: integer + orphaned: + description: Orphaned is the number of orphaned resources. + type: integer + ready: + description: Ready is the number of ready resources. + type: integer + unknown: + description: Unknown is the number of resources in an unknown + state. + type: integer + waitApplied: + description: WaitApplied is the number of resources that are + waiting to be applied. + type: integer + type: object + description: PerClusterResourceCounts contains the number of resources + in each state over all bundles, per cluster. + type: object readyClusters: description: 'ReadyClusters is the lowest number of clusters that are ready over diff --git a/internal/resourcestatus/resourcekey.go b/internal/resourcestatus/resourcekey.go index 4bbbe634a6..9c4126ea5c 100644 --- a/internal/resourcestatus/resourcekey.go +++ b/internal/resourcestatus/resourcekey.go @@ -5,21 +5,20 @@ import ( "sort" "strings" + "github.com/rancher/fleet/internal/cmd/controller/summary" fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1" ) func SetResources(list *fleet.BundleDeploymentList, status *fleet.StatusBase) { - s := summaryState(status.Summary) - r, errors := fromResources(list, s) + r, errors := fromResources(list) status.ResourceErrors = errors - status.ResourceCounts = countResources(r) status.Resources = merge(r) + status.ResourceCounts = sumResourceCounts(list) + status.PerClusterResourceCounts = resourceCountsPerCluster(list) } func SetClusterResources(list *fleet.BundleDeploymentList, cluster *fleet.Cluster) { - s := summaryState(cluster.Status.Summary) - r, _ := fromResources(list, s) - cluster.Status.ResourceCounts = countResources(r) + cluster.Status.ResourceCounts = sumResourceCounts(list) } // merge takes a list of GitRepo resources and deduplicates resources deployed to multiple clusters, @@ -52,27 +51,30 @@ func key(resource fleet.Resource) string { return resource.Type + "/" + resource.ID } -func summaryState(summary fleet.BundleSummary) string { - if summary.WaitApplied > 0 { - return "WaitApplied" - } - if summary.ErrApplied > 0 { - return "ErrApplied" +func resourceCountsPerCluster(list *fleet.BundleDeploymentList) map[string]*fleet.ResourceCounts { + res := make(map[string]*fleet.ResourceCounts) + for _, bd := range list.Items { + clusterID := bd.Labels[fleet.ClusterNamespaceLabel] + "/" + bd.Labels[fleet.ClusterLabel] + if _, ok := res[clusterID]; !ok { + res[clusterID] = &fleet.ResourceCounts{} + } + summary.IncrementResourceCounts(res[clusterID], bd.Status.ResourceCounts) } - return "" + 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, summaryState string) ([]fleet.Resource, []string) { +func fromResources(list *fleet.BundleDeploymentList) ([]fleet.Resource, []string) { var ( resources []fleet.Resource errors []string ) for _, bd := range list.Items { + state := summary.GetDeploymentState(&bd) bdResources := bundleDeploymentResources(bd) incomplete, err := addState(bd, bdResources) @@ -84,7 +86,7 @@ func fromResources(list *fleet.BundleDeploymentList, summaryState string) ([]fle } for k, perCluster := range bdResources { - resource := toResourceState(k, perCluster, incomplete, summaryState) + resource := toResourceState(k, perCluster, incomplete, string(state)) resources = append(resources, resource) } } @@ -94,7 +96,7 @@ func fromResources(list *fleet.BundleDeploymentList, summaryState string) ([]fle return resources, errors } -func toResourceState(k fleet.ResourceKey, perCluster []fleet.ResourcePerClusterState, incomplete bool, summaryState string) fleet.Resource { +func toResourceState(k fleet.ResourceKey, perCluster []fleet.ResourcePerClusterState, incomplete bool, bdState string) fleet.Resource { resource := fleet.Resource{ APIVersion: k.APIVersion, Kind: k.Kind, @@ -116,13 +118,13 @@ func toResourceState(k fleet.ResourceKey, perCluster []fleet.ResourcePerClusterS // fallback to state from gitrepo summary if resource.State == "" { if resource.IncompleteState { - if summaryState != "" { - resource.State = summaryState + if bdState != "" { + resource.State = bdState } else { resource.State = "Unknown" } - } else if summaryState != "" { - resource.State = summaryState + } else if bdState != "" { + resource.State = bdState } else { resource.State = "Ready" } @@ -237,28 +239,10 @@ func bundleDeploymentResources(bd fleet.BundleDeployment) map[fleet.ResourceKey] return bdResources } -func countResources(resources []fleet.Resource) fleet.ResourceCounts { - counts := fleet.ResourceCounts{} - - for _, resource := range resources { - counts.DesiredReady++ - switch resource.State { - case "Ready": - counts.Ready++ - case "WaitApplied": - counts.WaitApplied++ - case "Modified": - counts.Modified++ - case "Orphan": - counts.Orphaned++ - case "Missing": - counts.Missing++ - case "Unknown": - counts.Unknown++ - default: - counts.NotReady++ - } +func sumResourceCounts(list *fleet.BundleDeploymentList) fleet.ResourceCounts { + var res fleet.ResourceCounts + for _, bd := range list.Items { + summary.IncrementResourceCounts(&res, bd.Status.ResourceCounts) } - - return counts + return res } diff --git a/internal/resourcestatus/resourcekey_test.go b/internal/resourcestatus/resourcekey_test.go index 00cc3f21a9..991acf72f6 100644 --- a/internal/resourcestatus/resourcekey_test.go +++ b/internal/resourcestatus/resourcekey_test.go @@ -12,28 +12,22 @@ import ( ) func TestSetResources(t *testing.T) { - gitrepo := &fleet.GitRepo{ - Status: fleet.GitRepoStatus{ - StatusBase: fleet.StatusBase{ - Summary: fleet.BundleSummary{ - Ready: 2, - WaitApplied: 1, - }, - }, - }, - } list := &fleet.BundleDeploymentList{ Items: []fleet.BundleDeployment{ { ObjectMeta: metav1.ObjectMeta{ - Name: "bd1", + Name: "bd1", + Namespace: "ns1-cluster1-ns", Labels: map[string]string{ - fleet.RepoLabel: "gitrepo1", fleet.ClusterLabel: "cluster1", fleet.ClusterNamespaceLabel: "c-ns1", }, }, + Spec: fleet.BundleDeploymentSpec{ + DeploymentID: "id2", + }, Status: fleet.BundleDeploymentStatus{ + AppliedDeploymentID: "id1", Resources: []fleet.BundleDeploymentResource{ { Kind: "Deployment", @@ -49,13 +43,17 @@ func TestSetResources(t *testing.T) { Namespace: "default", }, }, + ResourceCounts: fleet.ResourceCounts{ + DesiredReady: 2, + WaitApplied: 2, + }, }, }, { ObjectMeta: metav1.ObjectMeta{ - Name: "bd1", + Name: "bd1", + Namespace: "ns1-cluster2-ns", Labels: map[string]string{ - fleet.RepoLabel: "gitrepo1", fleet.ClusterLabel: "cluster2", fleet.ClusterNamespaceLabel: "c-ns1", }, @@ -69,18 +67,50 @@ func TestSetResources(t *testing.T) { Namespace: "default", }, }, + ResourceCounts: fleet.ResourceCounts{ + DesiredReady: 1, + Ready: 1, + }, }, }, { ObjectMeta: metav1.ObjectMeta{ - Name: "bd1", + Name: "bd2", + Namespace: "ns1-cluster2-ns", + Labels: map[string]string{ + fleet.ClusterLabel: "cluster2", + fleet.ClusterNamespaceLabel: "c-ns1", + }, + }, + Status: fleet.BundleDeploymentStatus{ + Resources: []fleet.BundleDeploymentResource{ + { + Kind: "ConfigMap", + APIVersion: "v1", + Name: "cm-web", + Namespace: "default", + }, + }, + ResourceCounts: fleet.ResourceCounts{ + DesiredReady: 1, + Ready: 1, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "bd1", + Namespace: "ns2-cluster1", Labels: map[string]string{ - fleet.RepoLabel: "gitrepo1", fleet.ClusterLabel: "cluster1", fleet.ClusterNamespaceLabel: "c-ns2", }, }, + Spec: fleet.BundleDeploymentSpec{ + DeploymentID: "id2", + }, Status: fleet.BundleDeploymentStatus{ + AppliedDeploymentID: "id1", NonReadyStatus: []fleet.NonReadyStatus{ { Kind: "Deployment", @@ -103,15 +133,20 @@ func TestSetResources(t *testing.T) { Namespace: "default", }, }, + ResourceCounts: fleet.ResourceCounts{ + DesiredReady: 1, + NotReady: 1, + }, }, }, }, } - SetResources(list, &gitrepo.Status.StatusBase) + var status fleet.GitRepoStatus + SetResources(list, &status.StatusBase) - assert.Len(t, gitrepo.Status.Resources, 2) - assert.Contains(t, gitrepo.Status.Resources, fleet.Resource{ + assert.Len(t, status.Resources, 3) + assert.Contains(t, status.Resources, fleet.Resource{ APIVersion: "v1", Kind: "Deployment", Type: "deployment", @@ -136,7 +171,7 @@ func TestSetResources(t *testing.T) { }, }, }) - assert.Contains(t, gitrepo.Status.Resources, fleet.Resource{ + assert.Contains(t, status.Resources, fleet.Resource{ APIVersion: "v1", Kind: "Service", Type: "service", @@ -153,14 +188,28 @@ func TestSetResources(t *testing.T) { PerClusterState: []fleet.ResourcePerClusterState{}, }) - assert.Empty(t, gitrepo.Status.ResourceErrors) + assert.Empty(t, status.ResourceErrors) + + assert.Equal(t, fleet.ResourceCounts{ + Ready: 2, + DesiredReady: 5, + WaitApplied: 2, + NotReady: 1, + }, status.ResourceCounts) + + assert.Equal(t, map[string]*fleet.ResourceCounts{ + "c-ns1/cluster1": { + DesiredReady: 2, + WaitApplied: 2, + }, + "c-ns1/cluster2": { + DesiredReady: 2, + Ready: 2, + }, + "c-ns2/cluster1": { + DesiredReady: 1, + NotReady: 1, + }, + }, status.PerClusterResourceCounts) - assert.Equal(t, gitrepo.Status.ResourceCounts.Ready, 0) - assert.Equal(t, gitrepo.Status.ResourceCounts.DesiredReady, 4) - assert.Equal(t, gitrepo.Status.ResourceCounts.WaitApplied, 3) - assert.Equal(t, gitrepo.Status.ResourceCounts.Modified, 0) - assert.Equal(t, gitrepo.Status.ResourceCounts.Orphaned, 0) - assert.Equal(t, gitrepo.Status.ResourceCounts.Missing, 0) - assert.Equal(t, gitrepo.Status.ResourceCounts.Unknown, 0) - assert.Equal(t, gitrepo.Status.ResourceCounts.NotReady, 1) } diff --git a/pkg/apis/fleet.cattle.io/v1alpha1/status.go b/pkg/apis/fleet.cattle.io/v1alpha1/status.go index 0f03dd9dcb..1866992dd1 100644 --- a/pkg/apis/fleet.cattle.io/v1alpha1/status.go +++ b/pkg/apis/fleet.cattle.io/v1alpha1/status.go @@ -23,6 +23,8 @@ type StatusBase struct { ResourceCounts ResourceCounts `json:"resourceCounts,omitempty"` // ResourceErrors is a sorted list of errors from the resources. ResourceErrors []string `json:"resourceErrors,omitempty"` + // PerClusterResourceCounts contains the number of resources in each state over all bundles, per cluster. + PerClusterResourceCounts map[string]*ResourceCounts `json:"perClusterResourceCounts,omitempty"` } type StatusDisplay struct { diff --git a/pkg/apis/fleet.cattle.io/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/fleet.cattle.io/v1alpha1/zz_generated.deepcopy.go index 5836a0279c..78dd5f628a 100644 --- a/pkg/apis/fleet.cattle.io/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/fleet.cattle.io/v1alpha1/zz_generated.deepcopy.go @@ -2202,6 +2202,22 @@ func (in *StatusBase) DeepCopyInto(out *StatusBase) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.PerClusterResourceCounts != nil { + in, out := &in.PerClusterResourceCounts, &out.PerClusterResourceCounts + *out = make(map[string]*ResourceCounts, len(*in)) + for key, val := range *in { + var outVal *ResourceCounts + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = new(ResourceCounts) + **out = **in + } + (*out)[key] = outVal + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatusBase.