Skip to content

Commit e7dcbb5

Browse files
author
Julia Okuniewska
authored
feat: filter over lifecyclephase (#22)
added field to supported filters added tests for query over conditions bumped version to 2.0.6-dev Signed-off-by: Eoghan Lawless <[email protected]> Co-authored-by: Julia Okuniewska <[email protected]>
1 parent 0171af7 commit e7dcbb5

File tree

7 files changed

+156
-25
lines changed

7 files changed

+156
-25
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ CONTROLLER_TOOLS_VERSION ?= v0.17.0
366366
ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}')
367367
#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)
368368
ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}')
369-
GOLANGCI_LINT_VERSION ?= v1.62.2
369+
GOLANGCI_LINT_VERSION ?= v1.64.7
370370

371371
.PHONY: kustomize
372372
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.0.5
1+
2.0.6-dev

deployment/charts/cluster-manager/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ type: application
1616
# This is the chart version. This version number should be incremented each time you make changes
1717
# to the chart and its templates, including the app version.
1818
# Versions are expected to follow Semantic Versioning (https://semver.org/)
19-
version: 2.0.5
20-
appVersion: 2.0.5
19+
version: 2.0.6-dev
20+
appVersion: 2.0.6-dev
2121
annotations: {}

deployment/charts/cluster-template-crd/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ apiVersion: v2
66
name: cluster-template-crd
77
description: A Helm chart for the ClusterTemplate CRD
88
type: application
9-
version: 2.0.5
10-
appVersion: 2.0.5
9+
version: 2.0.6-dev
10+
appVersion: 2.0.6-dev
1111
annotations: {}

internal/pagination/pagination.go

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ type OrderBy struct {
2525
IsDesc bool
2626
}
2727

28-
type filterFunc[T any] func(item T, filter *Filter) bool
29-
3028
type orderFunc[T any] func(item1, item2 T, orderBy *OrderBy) bool
3129

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

173171
var filteredItems []T
174-
for _, item := range items {
175-
if len(applyFilters([]T{item}, filters, useAnd, filterFunc)) > 0 {
176-
filteredItems = append(filteredItems, item)
177-
}
178-
}
179-
180-
return filteredItems, nil
181-
}
182-
183-
func applyFilters[T any](items []T, filters []*Filter, useAnd bool, filterFunc filterFunc[T]) []T {
184-
filteredItems := make([]T, 0, len(items))
185172
for _, item := range items {
186173
if useAnd {
187174
// all required filters should match
@@ -197,19 +184,16 @@ func applyFilters[T any](items []T, filters []*Filter, useAnd bool, filterFunc f
197184
}
198185
} else {
199186
// at least one filter match
200-
matchesAny := false
201187
for _, filter := range filters {
202188
if filterFunc(item, filter) {
203-
matchesAny = true
189+
filteredItems = append(filteredItems, item)
204190
break
205191
}
206192
}
207-
if matchesAny {
208-
filteredItems = append(filteredItems, item)
209-
}
210193
}
211194
}
212-
return filteredItems
195+
196+
return filteredItems, nil
213197
}
214198

215199
func OrderItems[T any](items []T, orderBy string, orderFunc func(T, T, *OrderBy) bool) ([]T, error) {
@@ -266,6 +250,7 @@ func ValidateParams(params any) (pageSize, offset *int, orderBy, filter *string,
266250
"name": true,
267251
"kubernetesVersion": true,
268252
"providerStatus": true,
253+
"lifecyclePhase": true,
269254
}
270255
orderByParts := strings.Split(*orderBy, " ")
271256
if len(orderByParts) == 1 {
@@ -287,6 +272,7 @@ func ValidateParams(params any) (pageSize, offset *int, orderBy, filter *string,
287272
"name": true,
288273
"kubernetesVersion": true,
289274
"providerStatus": true,
275+
"lifecyclePhase": true,
290276
"version": true,
291277
}
292278
filterParts := strings.FieldsFunc(*filter, func(r rune) bool {

internal/rest/getv2clusters.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ func filterClusters(cluster api.ClusterInfo, filter *Filter) bool {
164164
if cluster.ProviderStatus != nil {
165165
return MatchWildcard(cluster.ProviderStatus.Message, filter.Value)
166166
}
167+
case "lifecyclePhase":
168+
if cluster.LifecyclePhase != nil {
169+
return MatchWildcard(cluster.LifecyclePhase.Message, filter.Value)
170+
}
167171
default:
168172
return false
169173
}
@@ -187,6 +191,11 @@ func orderClustersBy(cluster1, cluster2 api.ClusterInfo, orderBy *OrderBy) bool
187191
return *cluster1.ProviderStatus.Message > *cluster2.ProviderStatus.Message
188192
}
189193
return *cluster1.ProviderStatus.Message < *cluster2.ProviderStatus.Message
194+
case "lifecyclePhase":
195+
if orderBy.IsDesc {
196+
return *cluster1.LifecyclePhase.Message > *cluster2.LifecyclePhase.Message
197+
}
198+
return *cluster1.LifecyclePhase.Message < *cluster2.LifecyclePhase.Message
190199
default:
191200
return false
192201
}

internal/rest/getv2clusters_test.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import (
1111
"testing"
1212

1313
openapi_types "github.com/oapi-codegen/runtime/types"
14+
"github.com/stretchr/testify/assert"
1415
"github.com/stretchr/testify/mock"
1516
"github.com/stretchr/testify/require"
17+
corev1 "k8s.io/api/core/v1"
1618
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1719
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1820
capi "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -23,6 +25,42 @@ import (
2325
"github.com/open-edge-platform/cluster-manager/v2/pkg/api"
2426
)
2527

28+
var clusterStatusReady = capi.ClusterStatus{
29+
Phase: string(capi.ClusterPhaseProvisioned),
30+
Conditions: []capi.Condition{
31+
{
32+
Type: capi.ReadyCondition,
33+
Status: corev1.ConditionTrue,
34+
},
35+
{
36+
Type: capi.ControlPlaneReadyCondition,
37+
Status: corev1.ConditionTrue,
38+
},
39+
{
40+
Type: capi.InfrastructureReadyCondition,
41+
Status: corev1.ConditionTrue,
42+
},
43+
},
44+
}
45+
46+
var clusterStatusInProgressControlPlane = capi.ClusterStatus{
47+
Phase: string(capi.ClusterPhaseProvisioning),
48+
Conditions: []capi.Condition{
49+
{
50+
Type: capi.ReadyCondition,
51+
Status: corev1.ConditionFalse,
52+
},
53+
{
54+
Type: capi.ControlPlaneReadyCondition,
55+
Status: corev1.ConditionFalse,
56+
},
57+
{
58+
Type: capi.InfrastructureReadyCondition,
59+
Status: corev1.ConditionTrue,
60+
},
61+
},
62+
}
63+
2664
func createMockServer(t *testing.T, clusters []capi.Cluster, projectID string, options ...bool) *Server {
2765
unstructuredClusters := make([]unstructured.Unstructured, len(clusters))
2866
for i, cluster := range clusters {
@@ -77,6 +115,22 @@ func generateCluster(name *string, version *string) capi.Cluster {
77115
}
78116
}
79117

118+
func generateClusterWithStatus(name, version *string, status capi.ClusterStatus) capi.Cluster {
119+
clusterName := ""
120+
if name != nil {
121+
clusterName = *name
122+
}
123+
clusterVersion := ""
124+
if version != nil {
125+
clusterVersion = *version
126+
}
127+
return capi.Cluster{
128+
ObjectMeta: metav1.ObjectMeta{Name: clusterName},
129+
Spec: capi.ClusterSpec{Topology: &capi.Topology{Version: clusterVersion}},
130+
Status: status,
131+
}
132+
}
133+
80134
func generateClusterInfo(name, version string, lifecycleIndicator api.StatusIndicator, lifecycleMessage string) api.ClusterInfo {
81135
return api.ClusterInfo{
82136
Name: ptr(name),
@@ -450,6 +504,88 @@ func TestGetV2Clusters200(t *testing.T) {
450504
}
451505
require.Equal(t, expectedResponse, actualResponse, "GetV2Clusters() response = %v, want %v", actualResponse, expectedResponse)
452506
})
507+
508+
t.Run("filtered clusters by conditions", func(t *testing.T) {
509+
tests := []struct {
510+
name string
511+
clusters []capi.Cluster
512+
filter string
513+
expectedResult api.GetV2Clusters200JSONResponse
514+
}{
515+
{
516+
name: "filtered clusters by providerStatus",
517+
clusters: []capi.Cluster{
518+
generateClusterWithStatus(ptr("example-cluster-1"), ptr("v1.30.6+rke2r1"), clusterStatusReady),
519+
generateClusterWithStatus(ptr("example-cluster-2"), ptr("v1.20.4+rke2r1"), clusterStatusInProgressControlPlane),
520+
},
521+
filter: "providerStatus=ready",
522+
expectedResult: api.GetV2Clusters200JSONResponse{
523+
Clusters: &[]api.ClusterInfo{
524+
generateClusterInfo("example-cluster-1", "v1.30.6+rke2r1", api.STATUSINDICATIONIDLE, "active"),
525+
},
526+
TotalElements: 1,
527+
},
528+
},
529+
{
530+
name: "filtered clusters by lifecyclePhase",
531+
clusters: []capi.Cluster{
532+
generateClusterWithStatus(ptr("example-cluster-1"), ptr("v1.30.6+rke2r1"), clusterStatusReady),
533+
generateClusterWithStatus(ptr("example-cluster-2"), ptr("v1.20.4+rke2r1"), clusterStatusInProgressControlPlane),
534+
},
535+
filter: "lifecyclePhase=active",
536+
expectedResult: api.GetV2Clusters200JSONResponse{
537+
Clusters: &[]api.ClusterInfo{
538+
generateClusterInfo("example-cluster-1", "v1.30.6+rke2r1", api.STATUSINDICATIONIDLE, "active"),
539+
},
540+
TotalElements: 1,
541+
},
542+
},
543+
}
544+
545+
for _, tt := range tests {
546+
t.Run(tt.name, func(t *testing.T) {
547+
server := createMockServer(t, tt.clusters, expectedActiveProjectID)
548+
require.NotNil(t, server, "NewServer() returned nil, want not nil")
549+
550+
// Create a new request & response recorder
551+
req := httptest.NewRequest("GET", "/v2/clusters?filter="+tt.filter, nil)
552+
req.Header.Set("Activeprojectid", expectedActiveProjectID)
553+
rr := httptest.NewRecorder()
554+
555+
// create a handler with middleware to serve the request
556+
handler, err := server.ConfigureHandler()
557+
require.Nil(t, err)
558+
handler.ServeHTTP(rr, req)
559+
560+
// Parse the response body
561+
var actualResponse api.GetV2Clusters200JSONResponse
562+
err = json.Unmarshal(rr.Body.Bytes(), &actualResponse)
563+
assert.NoError(t, err, "Failed to unmarshal response body")
564+
565+
// Check the response status
566+
assert.Equal(t, http.StatusOK, rr.Code, "ServeHTTP() status = %v, want %v", rr.Code, 200)
567+
568+
for _, cluster := range *actualResponse.Clusters {
569+
cluster.ControlPlaneReady.Message = ptr("condition not found")
570+
cluster.ControlPlaneReady.Timestamp = ptr(uint64(0))
571+
572+
cluster.InfrastructureReady.Message = ptr("condition not found")
573+
cluster.InfrastructureReady.Timestamp = ptr(uint64(0))
574+
575+
cluster.ProviderStatus.Indicator = statusIndicatorPtr(api.STATUSINDICATIONUNSPECIFIED)
576+
cluster.ProviderStatus.Message = ptr("condition not found")
577+
cluster.ProviderStatus.Timestamp = ptr(uint64(0))
578+
579+
cluster.NodeHealth.Timestamp = ptr(uint64(0))
580+
cluster.LifecyclePhase.Timestamp = ptr(uint64(0))
581+
}
582+
583+
// Check the response content
584+
assert.Equal(t, tt.expectedResult, actualResponse, "GetV2Clusters() response = %v, want %v", actualResponse, tt.expectedResult)
585+
})
586+
}
587+
})
588+
453589
t.Run("no clusters after filter criteria", func(t *testing.T) {
454590
clusters := []capi.Cluster{
455591
generateCluster(ptr("example-cluster-1"), ptr("v1.30.6+rke2r1")),

0 commit comments

Comments
 (0)