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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ CONTROLLER_TOOLS_VERSION ?= v0.17.0
ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}')
#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)
ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}')
GOLANGCI_LINT_VERSION ?= v1.62.2
GOLANGCI_LINT_VERSION ?= v1.64.7

.PHONY: kustomize
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.0.5
2.0.6-dev
4 changes: 2 additions & 2 deletions deployment/charts/cluster-manager/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 2.0.5
appVersion: 2.0.5
version: 2.0.6-dev
appVersion: 2.0.6-dev
annotations: {}
4 changes: 2 additions & 2 deletions deployment/charts/cluster-template-crd/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ apiVersion: v2
name: cluster-template-crd
description: A Helm chart for the ClusterTemplate CRD
type: application
version: 2.0.5
appVersion: 2.0.5
version: 2.0.6-dev
appVersion: 2.0.6-dev
annotations: {}
24 changes: 5 additions & 19 deletions internal/pagination/pagination.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ type OrderBy struct {
IsDesc bool
}

type filterFunc[T any] func(item T, filter *Filter) bool

type orderFunc[T any] func(item1, item2 T, orderBy *OrderBy) bool

var normalizeEqualsRe = regexp.MustCompile(`[ \t]*=[ \t]*`)
Expand Down Expand Up @@ -171,17 +169,6 @@ func FilterItems[T any](items []T, filter string, filterFunc func(T, *Filter) bo
}

var filteredItems []T
for _, item := range items {
if len(applyFilters([]T{item}, filters, useAnd, filterFunc)) > 0 {
filteredItems = append(filteredItems, item)
}
}

return filteredItems, nil
}

func applyFilters[T any](items []T, filters []*Filter, useAnd bool, filterFunc filterFunc[T]) []T {
filteredItems := make([]T, 0, len(items))
for _, item := range items {
if useAnd {
// all required filters should match
Expand All @@ -197,19 +184,16 @@ func applyFilters[T any](items []T, filters []*Filter, useAnd bool, filterFunc f
}
} else {
// at least one filter match
matchesAny := false
for _, filter := range filters {
if filterFunc(item, filter) {
matchesAny = true
filteredItems = append(filteredItems, item)
break
}
}
if matchesAny {
filteredItems = append(filteredItems, item)
}
}
}
return filteredItems

return filteredItems, nil
}

func OrderItems[T any](items []T, orderBy string, orderFunc func(T, T, *OrderBy) bool) ([]T, error) {
Expand Down Expand Up @@ -266,6 +250,7 @@ func ValidateParams(params any) (pageSize, offset *int, orderBy, filter *string,
"name": true,
"kubernetesVersion": true,
"providerStatus": true,
"lifecyclePhase": true,
}
orderByParts := strings.Split(*orderBy, " ")
if len(orderByParts) == 1 {
Expand All @@ -287,6 +272,7 @@ func ValidateParams(params any) (pageSize, offset *int, orderBy, filter *string,
"name": true,
"kubernetesVersion": true,
"providerStatus": true,
"lifecyclePhase": true,
"version": true,
}
filterParts := strings.FieldsFunc(*filter, func(r rune) bool {
Expand Down
9 changes: 9 additions & 0 deletions internal/rest/getv2clusters.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ func filterClusters(cluster api.ClusterInfo, filter *Filter) bool {
if cluster.ProviderStatus != nil {
return MatchWildcard(cluster.ProviderStatus.Message, filter.Value)
}
case "lifecyclePhase":
if cluster.LifecyclePhase != nil {
return MatchWildcard(cluster.LifecyclePhase.Message, filter.Value)
}
default:
return false
}
Expand All @@ -187,6 +191,11 @@ func orderClustersBy(cluster1, cluster2 api.ClusterInfo, orderBy *OrderBy) bool
return *cluster1.ProviderStatus.Message > *cluster2.ProviderStatus.Message
}
return *cluster1.ProviderStatus.Message < *cluster2.ProviderStatus.Message
case "lifecyclePhase":
if orderBy.IsDesc {
return *cluster1.LifecyclePhase.Message > *cluster2.LifecyclePhase.Message
}
return *cluster1.LifecyclePhase.Message < *cluster2.LifecyclePhase.Message
default:
return false
}
Expand Down
136 changes: 136 additions & 0 deletions internal/rest/getv2clusters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import (
"testing"

openapi_types "github.com/oapi-codegen/runtime/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
capi "sigs.k8s.io/cluster-api/api/v1beta1"
Expand All @@ -23,6 +25,42 @@ import (
"github.com/open-edge-platform/cluster-manager/v2/pkg/api"
)

var clusterStatusReady = capi.ClusterStatus{
Phase: string(capi.ClusterPhaseProvisioned),
Conditions: []capi.Condition{
{
Type: capi.ReadyCondition,
Status: corev1.ConditionTrue,
},
{
Type: capi.ControlPlaneReadyCondition,
Status: corev1.ConditionTrue,
},
{
Type: capi.InfrastructureReadyCondition,
Status: corev1.ConditionTrue,
},
},
}

var clusterStatusInProgressControlPlane = capi.ClusterStatus{
Phase: string(capi.ClusterPhaseProvisioning),
Conditions: []capi.Condition{
{
Type: capi.ReadyCondition,
Status: corev1.ConditionFalse,
},
{
Type: capi.ControlPlaneReadyCondition,
Status: corev1.ConditionFalse,
},
{
Type: capi.InfrastructureReadyCondition,
Status: corev1.ConditionTrue,
},
},
}

func createMockServer(t *testing.T, clusters []capi.Cluster, projectID string, options ...bool) *Server {
unstructuredClusters := make([]unstructured.Unstructured, len(clusters))
for i, cluster := range clusters {
Expand Down Expand Up @@ -77,6 +115,22 @@ func generateCluster(name *string, version *string) capi.Cluster {
}
}

func generateClusterWithStatus(name, version *string, status capi.ClusterStatus) capi.Cluster {
clusterName := ""
if name != nil {
clusterName = *name
}
clusterVersion := ""
if version != nil {
clusterVersion = *version
}
return capi.Cluster{
ObjectMeta: metav1.ObjectMeta{Name: clusterName},
Spec: capi.ClusterSpec{Topology: &capi.Topology{Version: clusterVersion}},
Status: status,
}
}

func generateClusterInfo(name, version string, lifecycleIndicator api.StatusIndicator, lifecycleMessage string) api.ClusterInfo {
return api.ClusterInfo{
Name: ptr(name),
Expand Down Expand Up @@ -450,6 +504,88 @@ func TestGetV2Clusters200(t *testing.T) {
}
require.Equal(t, expectedResponse, actualResponse, "GetV2Clusters() response = %v, want %v", actualResponse, expectedResponse)
})

t.Run("filtered clusters by conditions", func(t *testing.T) {
tests := []struct {
name string
clusters []capi.Cluster
filter string
expectedResult api.GetV2Clusters200JSONResponse
}{
{
name: "filtered clusters by providerStatus",
clusters: []capi.Cluster{
generateClusterWithStatus(ptr("example-cluster-1"), ptr("v1.30.6+rke2r1"), clusterStatusReady),
generateClusterWithStatus(ptr("example-cluster-2"), ptr("v1.20.4+rke2r1"), clusterStatusInProgressControlPlane),
},
filter: "providerStatus=ready",
expectedResult: api.GetV2Clusters200JSONResponse{
Clusters: &[]api.ClusterInfo{
generateClusterInfo("example-cluster-1", "v1.30.6+rke2r1", api.STATUSINDICATIONIDLE, "active"),
},
TotalElements: 1,
},
},
{
name: "filtered clusters by lifecyclePhase",
clusters: []capi.Cluster{
generateClusterWithStatus(ptr("example-cluster-1"), ptr("v1.30.6+rke2r1"), clusterStatusReady),
generateClusterWithStatus(ptr("example-cluster-2"), ptr("v1.20.4+rke2r1"), clusterStatusInProgressControlPlane),
},
filter: "lifecyclePhase=active",
expectedResult: api.GetV2Clusters200JSONResponse{
Clusters: &[]api.ClusterInfo{
generateClusterInfo("example-cluster-1", "v1.30.6+rke2r1", api.STATUSINDICATIONIDLE, "active"),
},
TotalElements: 1,
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
server := createMockServer(t, tt.clusters, expectedActiveProjectID)
require.NotNil(t, server, "NewServer() returned nil, want not nil")

// Create a new request & response recorder
req := httptest.NewRequest("GET", "/v2/clusters?filter="+tt.filter, nil)
req.Header.Set("Activeprojectid", expectedActiveProjectID)
rr := httptest.NewRecorder()

// create a handler with middleware to serve the request
handler, err := server.ConfigureHandler()
require.Nil(t, err)
handler.ServeHTTP(rr, req)

// Parse the response body
var actualResponse api.GetV2Clusters200JSONResponse
err = json.Unmarshal(rr.Body.Bytes(), &actualResponse)
assert.NoError(t, err, "Failed to unmarshal response body")

// Check the response status
assert.Equal(t, http.StatusOK, rr.Code, "ServeHTTP() status = %v, want %v", rr.Code, 200)

for _, cluster := range *actualResponse.Clusters {
cluster.ControlPlaneReady.Message = ptr("condition not found")
cluster.ControlPlaneReady.Timestamp = ptr(uint64(0))

cluster.InfrastructureReady.Message = ptr("condition not found")
cluster.InfrastructureReady.Timestamp = ptr(uint64(0))

cluster.ProviderStatus.Indicator = statusIndicatorPtr(api.STATUSINDICATIONUNSPECIFIED)
cluster.ProviderStatus.Message = ptr("condition not found")
cluster.ProviderStatus.Timestamp = ptr(uint64(0))

cluster.NodeHealth.Timestamp = ptr(uint64(0))
cluster.LifecyclePhase.Timestamp = ptr(uint64(0))
}

// Check the response content
assert.Equal(t, tt.expectedResult, actualResponse, "GetV2Clusters() response = %v, want %v", actualResponse, tt.expectedResult)
})
}
})

t.Run("no clusters after filter criteria", func(t *testing.T) {
clusters := []capi.Cluster{
generateCluster(ptr("example-cluster-1"), ptr("v1.30.6+rke2r1")),
Expand Down
Loading