Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MGMT-16307: Optimize GraphQL search using filters #41

Merged
merged 1 commit into from
Dec 18, 2023
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 go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/json-iterator/go v1.1.12
github.com/onsi/ginkgo/v2 v2.13.0
github.com/onsi/gomega v1.29.0
github.com/thoas/go-funk v0.9.3
go.uber.org/mock v0.3.0
k8s.io/api v0.28.4
k8s.io/apimachinery v0.28.4
Expand Down Expand Up @@ -39,7 +40,6 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/thoas/go-funk v0.9.3 // indirect
golang.org/x/exp v0.0.0-20230118134722-a68e582fa157 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/term v0.14.0 // indirect
Expand Down
110 changes: 110 additions & 0 deletions internal/graphql/graphql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
Copyright (c) 2023 Red Hat, Inc.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing permissions and limitations under the
License.
*/

package graphql

import (
"fmt"

"github.com/openshift-kni/oran-o2ims/internal/model"
"github.com/openshift-kni/oran-o2ims/internal/search"
)

type FilterOperator search.Operator

// String generates a GraphQL string representation of the operator. It panics if used on an unknown
// operator.
func (o FilterOperator) String() (result string, err error) {
switch search.Operator(o) {
case search.Eq:
result = "="
case search.Neq:
result = "!="
case search.Gt:
result = ">"
case search.Gte:
result = ">="
case search.Lt:
result = "<"
case search.Lte:
result = "<="
default:
err = fmt.Errorf("unknown operator %d", o)
}
return
}

type PropertyCluster string

// MapProperty maps a specified O2 property name to the search API property name
func (p PropertyCluster) MapProperty() string {
switch p {
case "name":
return "name"
case "resourcePoolID":
return "cluster"
default:
// unknown property
return ""
}
}

type PropertyNode string

// MapProperty maps a specified O2 property name to the search API property name
func (p PropertyNode) MapProperty() string {
switch p {
case "description":
return "name"
case "resourcePoolID":
return "cluster"
case "globalAssetID":
return "_uid"
case "resourceID":
return "_systemUUID"
default:
// unknown property
return ""
}
}

type FilterTerm search.Term

// Map a filter term to a GraphQL SearchFilter
func (t FilterTerm) MapFilter(mapPropertyFunc func(string) string) (searchFilter *model.SearchFilter, err error) {
// Get filter operator
var operator string
operator, err = FilterOperator(t.Operator).String()
if err != nil {
return
}

// Generate values
values := []*string{}
for _, v := range t.Values {
value := fmt.Sprintf("%s%s", operator, v.(string))
values = append(values, &value)
}

// Convert to GraphQL property
searchProperty := mapPropertyFunc(t.Path[0])

// Build search filter
searchFilter = &model.SearchFilter{
Property: searchProperty,
Values: values,
}

return
}
73 changes: 73 additions & 0 deletions internal/graphql/graphql_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
Copyright (c) 2023 Red Hat, Inc.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing permissions and limitations under the
License.
*/

package graphql

import (
. "github.com/onsi/ginkgo/v2/dsl/core"
. "github.com/onsi/ginkgo/v2/dsl/table"
. "github.com/onsi/gomega"
"github.com/openshift-kni/oran-o2ims/internal/model"
"github.com/openshift-kni/oran-o2ims/internal/search"
"k8s.io/utils/ptr"
)

var _ = Describe("GraphQL filters", func() {
DescribeTable(
"Map a filter term to a SearchFilter",
func(term search.Term, expected *model.SearchFilter, mapPropertyFunc func(string) string) {
actual, err := FilterTerm(term).MapFilter(mapPropertyFunc)
Expect(err).ToNot(HaveOccurred())
Expect(actual).To(Equal(expected))
},
Entry(
"Filter term for Cluster",
search.Term{
Operator: search.Eq,
Path: []string{
"resourcePoolID",
},
Values: []any{
"spoke0",
},
},
&model.SearchFilter{
Property: "cluster",
Values: []*string{ptr.To("=spoke0")},
},
func(s string) string {
return PropertyCluster(s).MapProperty()
},
),
Entry(
"Filter term for Node",
search.Term{
Operator: search.Eq,
Path: []string{
"resourcePoolID",
},
Values: []any{
"spoke0",
},
},
&model.SearchFilter{
Property: "cluster",
Values: []*string{ptr.To("=spoke0")},
},
func(s string) string {
return PropertyNode(s).MapProperty()
},
),
)
})
40 changes: 40 additions & 0 deletions internal/graphql/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
Copyright (c) 2023 Red Hat, Inc.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing permissions and limitations under the
License.
*/

package graphql

import (
"log/slog"
"testing"

. "github.com/onsi/ginkgo/v2/dsl/core"
. "github.com/onsi/gomega"
)

func TestSearch(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Search")
}

var logger *slog.Logger

var _ = BeforeSuite(func() {
// Create a logger that writes to the Ginkgo writer, so that the log messages will be
// attached to the output of the right test:
options := &slog.HandlerOptions{
Level: slog.LevelDebug,
}
handler := slog.NewJSONHandler(GinkgoWriter, options)
logger = slog.New(handler)
})
1 change: 0 additions & 1 deletion internal/service/resource_fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,6 @@ func (r *ResourceFetcher) getSearchResponse(ctx context.Context) (result io.Read
if err != nil {
return
}
r.logger.Error(fmt.Sprintf("%v", graphqlVars))

// Build GraphQL request body
var requestBody bytes.Buffer
Expand Down
Loading