Skip to content

Commit

Permalink
Merge pull request #16 from gianlucam76/profile-result
Browse files Browse the repository at this point in the history
getClusterStatus
  • Loading branch information
gianlucam76 authored May 24, 2024
2 parents b491bf2 + 247f463 commit 0f6e7a3
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 70 deletions.
78 changes: 49 additions & 29 deletions internal/server/clustersummary_status.go
Original file line number Diff line number Diff line change
@@ -1,44 +1,64 @@
package server

import (
"errors"
"fmt"
"sort"
configv1alpha1 "github.com/projectsveltos/addon-controller/api/v1alpha1"
)

// getMapInRange extracts a subset of key-value pairs from the given map, skipping the first 'skip' pairs and then taking up to 'limit' pairs.
func getMapInRange[K comparable, V any](m map[K]V, limit, skip int) (map[K]V, error) {
if skip < 0 {
return nil, errors.New("skip cannot be negative")
}
if limit < 0 {
return nil, errors.New("limit cannot be negative")
}
if skip >= len(m) {
return nil, nil
type ProfileStatusResult struct {
ProfileName string `json:"profileName"`
ProfileType string `json:"profileType"`
ClusterFeatureSummary
}

func getFlattenedProfileStatusesInRange(flattenedProfileStatuses []ProfileStatusResult, limit, skip int) ([]ProfileStatusResult, error) {
return getSliceInRange(flattenedProfileStatuses, limit, skip)
}

func flattenProfileStatuses(profileStatuses []ClusterProfileStatus, failedOnly bool) []ProfileStatusResult {
result := make([]ProfileStatusResult, 0)

for i := range profileStatuses {
result = append(result, flattenProfileStatus(&profileStatuses[i], failedOnly)...)
}

// Extract keys and sort them
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
return result
}

func flattenProfileStatus(profileStatus *ClusterProfileStatus, failedOnly bool) []ProfileStatusResult {
result := make([]ProfileStatusResult, 0)
for i := range profileStatus.Summary {
if !failedOnly || !isCompleted(profileStatus.Summary[i]) {
result = append(result,
ProfileStatusResult{
ProfileName: profileStatus.ProfileName,
ProfileType: profileStatus.ProfileType,
ClusterFeatureSummary: profileStatus.Summary[i],
})
}
}
sort.Slice(keys, func(i, j int) bool {
return fmt.Sprintf("%v", keys[i]) < fmt.Sprintf("%v", keys[j])
})

// Create a new map for the result
result := make(map[K]V)
return result
}

func isCompleted(cfs ClusterFeatureSummary) bool {
if cfs.Status != configv1alpha1.FeatureStatusProvisioned &&
cfs.Status != configv1alpha1.FeatureStatusRemoved {

// Iterate over the sorted keys and collect the desired key-value pairs
for i := skip; i < skip+limit && i < len(keys); i++ {
k := keys[i]
result[k] = m[k]
return false
}

return result, nil
return true
}

func getProfileStatusesInRange(profileStatuses map[string][]ClusterFeatureSummary, limit, skip int) (map[string][]ClusterFeatureSummary, error) {
return getMapInRange(profileStatuses, limit, skip)
// sortClusterProfileStatus sort by ProfileType first, ProfileName later and finally by FeatureID
func sortClusterProfileStatus(flattenedProfileStatuses []ProfileStatusResult, i, j int) bool {
if flattenedProfileStatuses[i].ProfileType == flattenedProfileStatuses[j].ProfileType {
if flattenedProfileStatuses[i].ProfileName == flattenedProfileStatuses[j].ProfileName {
return flattenedProfileStatuses[i].FeatureID < flattenedProfileStatuses[j].FeatureID
}

return flattenedProfileStatuses[i].ProfileName < flattenedProfileStatuses[j].ProfileName
}

return flattenedProfileStatuses[i].ProfileType < flattenedProfileStatuses[j].ProfileType
}
50 changes: 30 additions & 20 deletions internal/server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,42 +175,32 @@ var (
}

getClusterStatus = func(c *gin.Context) {
ginLogger.V(logs.LogDebug).Info("get deployed Kubernetes resources")
ginLogger.V(logs.LogDebug).Info("get list of profiles (and their status) matching a cluster")

failedOnly := getFailedOnlyFromQuery(c)
limit, skip := getLimitAndSkipFromQuery(c)
namespace, name, clusterType := getClusterFromQuery(c)
ginLogger.V(logs.LogDebug).Info(fmt.Sprintf("cluster %s:%s/%s", clusterType, namespace, name))
ginLogger.V(logs.LogDebug).Info(fmt.Sprintf("limit %d skip %d", limit, skip))
ginLogger.V(logs.LogDebug).Info(fmt.Sprintf("failed %t", failedOnly))

manager := GetManagerInstance()
clusterProfileStatuses := manager.GetClusterProfileStatusesByCluster(&namespace, &name, clusterType)
profiles := make(map[string][]ClusterFeatureSummary, len(clusterProfileStatuses))

for _, clusterProfileStatus := range clusterProfileStatuses {
if _, ok := profiles[*clusterProfileStatus.Name]; !ok {
profiles[*clusterProfileStatus.Name] = make([]ClusterFeatureSummary, 0)
}

for _, summary := range clusterProfileStatus.Summary {
// Add it only if the status is not Provisioned (includes removed as well)
for _, failingClusterSummaryType := range failingClusterSummaryTypes {
if summary.Status == failingClusterSummaryType {
profiles[*clusterProfileStatus.Name] = append(profiles[*clusterProfileStatus.Name], summary)
break
}
}
}
}
flattenedProfileStatuses := flattenProfileStatuses(clusterProfileStatuses, failedOnly)
sort.Slice(flattenedProfileStatuses, func(i, j int) bool {
return sortClusterProfileStatus(flattenedProfileStatuses, i, j)
})

result, err := getProfileStatusesInRange(profiles, limit, skip)
result, err := getFlattenedProfileStatusesInRange(flattenedProfileStatuses, limit, skip)
if err != nil {
ginLogger.V(logs.LogInfo).Info(fmt.Sprintf("bad request %s: %v", c.Request.URL, err))
_ = c.AbortWithError(http.StatusBadRequest, err)
}

c.JSON(http.StatusOK, gin.H{
"clusterName": name,
"profiles": result,
"totalResources": len(flattenedProfileStatuses),
"profiles": result,
})
}
)
Expand Down Expand Up @@ -316,6 +306,26 @@ func getLimitAndSkipFromQuery(c *gin.Context) (limit, skip int) {
return
}

func getFailedOnlyFromQuery(c *gin.Context) bool {
// Define default values for limit and skip
failedOnly := false

// Get the values from query parameters
queryFailed := c.Query("failed")

// Parse the query parameters to int (handle errors)
var err error
if queryFailed != "" {
failedOnly, err = strconv.ParseBool(queryFailed)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid failed parameter"})
return failedOnly
}
}

return failedOnly
}

func getClusterFromQuery(c *gin.Context) (namespace, name string, clusterType libsveltosv1alpha1.ClusterType) {
// Get the values from query parameters
queryNamespace := c.Query("namespace")
Expand Down
31 changes: 14 additions & 17 deletions internal/server/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ type ClusterInfo struct {
}

type ClusterProfileStatus struct {
Name *string `json:"name"`
Namespace *string `json:"namespace"`
ProfileName string `json:"profileName"`
ProfileType string `json:"profileType"`
Namespace string `json:"namespace"`
ClusterType libsveltosv1alpha1.ClusterType `json:"clusterType"`
ClusterName *string `json:"clusterName"`
ClusterName string `json:"clusterName"`
Summary []ClusterFeatureSummary `json:"summary"`
}

Expand All @@ -64,13 +65,8 @@ type instance struct {
}

var (
managerInstance *instance
lock = &sync.RWMutex{}
failingClusterSummaryTypes = []configv1alpha1.FeatureStatus{
configv1alpha1.FeatureStatusFailed,
configv1alpha1.FeatureStatusFailedNonRetriable,
configv1alpha1.FeatureStatusProvisioning,
}
managerInstance *instance
lock = &sync.RWMutex{}
)

// InitializeManagerInstance initializes manager instance
Expand Down Expand Up @@ -131,10 +127,10 @@ func (m *instance) GetClusterProfileStatusesByCluster(
clusterProfileStatuses := make([]ClusterProfileStatus, 0)
for _, clusterProfileStatus := range m.clusterSummaryReport {
// since we're sure it is a proper cluster summary => we're sure it has this label
if *clusterProfileStatus.Namespace == *clusterNamespace && *clusterProfileStatus.ClusterName == *clusterName {
if clusterProfileStatus.ClusterType == clusterType {
clusterProfileStatuses = append(clusterProfileStatuses, clusterProfileStatus)
}
if clusterProfileStatus.Namespace == *clusterNamespace && clusterProfileStatus.ClusterName == *clusterName &&
clusterProfileStatus.ClusterType == clusterType {

clusterProfileStatuses = append(clusterProfileStatuses, clusterProfileStatus)
}
}

Expand Down Expand Up @@ -219,10 +215,11 @@ func (m *instance) AddClusterProfileStatus(summary *configv1alpha1.ClusterSummar
clusterFeatureSummaries := MapToClusterFeatureSummaries(&summary.Status.FeatureSummaries)

clusterProfileStatus := ClusterProfileStatus{
Name: &profileOwnerRef.Name,
Namespace: &summary.Namespace,
ProfileName: profileOwnerRef.Name,
ProfileType: profileOwnerRef.Kind,
Namespace: summary.Namespace,
ClusterType: summary.Spec.ClusterType,
ClusterName: &summary.Spec.ClusterName,
ClusterName: summary.Spec.ClusterName,
Summary: clusterFeatureSummaries,
}

Expand Down
9 changes: 5 additions & 4 deletions internal/server/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,10 +305,11 @@ var _ = Describe("Manager", func() {
}

properClusterProfileStatus := server.ClusterProfileStatus{
Name: &properClusterSummary.Name,
Namespace: &properClusterSummary.Namespace,
ProfileName: properClusterSummary.Name,
ProfileType: configv1alpha1.ClusterProfileKind,
Namespace: properClusterSummary.Namespace,
ClusterType: libsveltosv1alpha1.ClusterTypeCapi,
ClusterName: &properClusterSummary.Spec.ClusterName,
ClusterName: properClusterSummary.Spec.ClusterName,
Summary: server.MapToClusterFeatureSummaries(&properClusterSummary.Status.FeatureSummaries),
}

Expand Down Expand Up @@ -392,6 +393,6 @@ var _ = Describe("Manager", func() {
// the remaining cluster profile must be the one specified by the proper cluster summary
// as it is the only one that belongs to the cluster with Namespace cluster.Namespace and
// Name cluster.Name
Expect(*clusterProfileStatuses[0].Name == properClusterSummary.Name).To(BeTrue())
Expect(clusterProfileStatuses[0].ProfileName == properClusterSummary.Name).To(BeTrue())
})
})

0 comments on commit 0f6e7a3

Please sign in to comment.