From cf3203d14ae07e9c71283f204266c3f2f1f17a38 Mon Sep 17 00:00:00 2001 From: Kofo Okesola Date: Fri, 1 Mar 2024 09:56:01 +0100 Subject: [PATCH] TT-10962 (#6072) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [TT-10962](https://tyktech.atlassian.net/browse/TT-10962) - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Refactoring or add test (improvements in base code or adds test coverage to functionality) - [ ] I ensured that the documentation is up to date - [ ] I explained why this PR updates go.mod in detail with reasoning why it's required - [ ] I would like a code coverage CI quality gate exception and have explained why [TT-10962]: https://tyktech.atlassian.net/browse/TT-10962?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ ___ bug_fix ___ - Disabled the `EnableSingleFlight` feature in both `ProxyOnly` and `UniversalDataGraph` adapters to address existing issues. ___
Relevant files
Bug fix
adapter_proxy_only.go
Disable SingleFlight in ProxyOnly Adapter                               

apidef/adapter/gqlengineadapter/adapter_proxy_only.go
  • Disabled EnableSingleFlight feature by setting its value to false.
  • +1/-1     
    adapter_udg.go
    Disable SingleFlight in UniversalDataGraph Adapter             

    apidef/adapter/gqlengineadapter/adapter_udg.go
  • Disabled EnableSingleFlight feature by setting its value to false.
  • +1/-1     
    ___ > ✨ **PR-Agent usage**: >Comment `/help` on the PR to get a list of all available PR-Agent tools and their descriptions Co-authored-by: Shakira Salazar <69164527+rhianeKobar@users.noreply.github.com> (cherry picked from commit 9ed401d9a82f3df1f86da94d9b642f542bc10402) --- .../gqlengineadapter/adapter_proxy_only.go | 59 +++++ .../adapter/gqlengineadapter/adapter_udg.go | 224 ++++++++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 apidef/adapter/gqlengineadapter/adapter_proxy_only.go create mode 100644 apidef/adapter/gqlengineadapter/adapter_udg.go diff --git a/apidef/adapter/gqlengineadapter/adapter_proxy_only.go b/apidef/adapter/gqlengineadapter/adapter_proxy_only.go new file mode 100644 index 00000000000..f925633d00e --- /dev/null +++ b/apidef/adapter/gqlengineadapter/adapter_proxy_only.go @@ -0,0 +1,59 @@ +package gqlengineadapter + +import ( + "net/http" + "strings" + + graphqlDataSource "github.com/TykTechnologies/graphql-go-tools/pkg/engine/datasource/graphql_datasource" + "github.com/TykTechnologies/graphql-go-tools/pkg/graphql" + + "github.com/TykTechnologies/tyk/apidef" +) + +type ProxyOnly struct { + ApiDefinition *apidef.APIDefinition + HttpClient *http.Client + StreamingClient *http.Client + Schema *graphql.Schema + + subscriptionClientFactory graphqlDataSource.GraphQLSubscriptionClientFactory +} + +func (p *ProxyOnly) EngineConfig() (*graphql.EngineV2Configuration, error) { + var err error + if p.Schema == nil { + p.Schema, err = parseSchema(p.ApiDefinition.GraphQL.Schema) + if err != nil { + return nil, err + } + } + + staticHeaders := make(http.Header) + for key, value := range p.ApiDefinition.GraphQL.Proxy.RequestHeaders { + staticHeaders.Set(key, value) + } + + url := p.ApiDefinition.Proxy.TargetURL + if strings.HasPrefix(url, "tyk://") { + url = strings.ReplaceAll(url, "tyk://", "http://") + staticHeaders.Set(apidef.TykInternalApiHeader, "true") + } + + upstreamConfig := graphql.ProxyUpstreamConfig{ + URL: url, + StaticHeaders: staticHeaders, + SubscriptionType: graphqlSubscriptionType(p.ApiDefinition.GraphQL.Proxy.SubscriptionType), + } + + v2Config, err := graphql.NewProxyEngineConfigFactory( + p.Schema, + upstreamConfig, + graphqlDataSource.NewBatchFactory(), + graphql.WithProxyHttpClient(p.HttpClient), + graphql.WithProxyStreamingClient(p.StreamingClient), + graphql.WithProxySubscriptionClientFactory(subscriptionClientFactoryOrDefault(p.subscriptionClientFactory)), + ).EngineV2Configuration() + + v2Config.EnableSingleFlight(false) + return &v2Config, err +} diff --git a/apidef/adapter/gqlengineadapter/adapter_udg.go b/apidef/adapter/gqlengineadapter/adapter_udg.go new file mode 100644 index 00000000000..f0e313cfe47 --- /dev/null +++ b/apidef/adapter/gqlengineadapter/adapter_udg.go @@ -0,0 +1,224 @@ +package gqlengineadapter + +import ( + "encoding/json" + "net/http" + + graphqlDataSource "github.com/TykTechnologies/graphql-go-tools/pkg/engine/datasource/graphql_datasource" + kafkaDataSource "github.com/TykTechnologies/graphql-go-tools/pkg/engine/datasource/kafka_datasource" + restDataSource "github.com/TykTechnologies/graphql-go-tools/pkg/engine/datasource/rest_datasource" + "github.com/TykTechnologies/graphql-go-tools/pkg/engine/plan" + "github.com/TykTechnologies/graphql-go-tools/pkg/graphql" + + "github.com/TykTechnologies/tyk/apidef" +) + +type UniversalDataGraph struct { + ApiDefinition *apidef.APIDefinition + HttpClient *http.Client + StreamingClient *http.Client + Schema *graphql.Schema + + subscriptionClientFactory graphqlDataSource.GraphQLSubscriptionClientFactory +} + +func (u *UniversalDataGraph) EngineConfig() (*graphql.EngineV2Configuration, error) { + var err error + if u.Schema == nil { + u.Schema, err = parseSchema(u.ApiDefinition.GraphQL.Schema) + if err != nil { + return nil, err + } + } + + conf := graphql.NewEngineV2Configuration(u.Schema) + conf.EnableSingleFlight(false) + + fieldConfigs := u.engineConfigV2FieldConfigs() + datsSources, err := u.engineConfigV2DataSources() + if err != nil { + return nil, err + } + + conf.SetFieldConfigurations(fieldConfigs) + conf.SetDataSources(datsSources) + + return &conf, nil +} + +func (u *UniversalDataGraph) engineConfigV2FieldConfigs() (planFieldConfigs plan.FieldConfigurations) { + for _, fc := range u.ApiDefinition.GraphQL.Engine.FieldConfigs { + planFieldConfig := plan.FieldConfiguration{ + TypeName: fc.TypeName, + FieldName: fc.FieldName, + DisableDefaultMapping: fc.DisableDefaultMapping, + Path: fc.Path, + } + + planFieldConfigs = append(planFieldConfigs, planFieldConfig) + } + + generatedArgs := u.Schema.GetAllFieldArguments(graphql.NewSkipReservedNamesFunc()) + generatedArgsAsLookupMap := graphql.CreateTypeFieldArgumentsLookupMap(generatedArgs) + u.engineConfigV2Arguments(&planFieldConfigs, generatedArgsAsLookupMap) + + return planFieldConfigs +} + +func (u *UniversalDataGraph) engineConfigV2DataSources() (planDataSources []plan.DataSourceConfiguration, err error) { + for _, ds := range u.ApiDefinition.GraphQL.Engine.DataSources { + planDataSource := plan.DataSourceConfiguration{ + RootNodes: []plan.TypeField{}, + } + + for _, typeField := range ds.RootFields { + planTypeField := plan.TypeField{ + TypeName: typeField.Type, + FieldNames: typeField.Fields, + } + + planDataSource.RootNodes = append(planDataSource.RootNodes, planTypeField) + } + + switch ds.Kind { + case apidef.GraphQLEngineDataSourceKindREST: + var restConfig apidef.GraphQLEngineDataSourceConfigREST + err = json.Unmarshal(ds.Config, &restConfig) + if err != nil { + return nil, err + } + + planDataSource.Factory = &restDataSource.Factory{ + Client: u.HttpClient, + } + + urlWithoutQueryParams, queryConfigs, err := extractURLQueryParamsForEngineV2(restConfig.URL, restConfig.Query) + if err != nil { + return nil, err + } + + planDataSource.Custom = restDataSource.ConfigJSON(restDataSource.Configuration{ + Fetch: restDataSource.FetchConfiguration{ + URL: urlWithoutQueryParams, + Method: restConfig.Method, + Body: restConfig.Body, + Query: queryConfigs, + Header: convertApiDefinitionHeadersToHttpHeaders(restConfig.Headers), + }, + }) + + case apidef.GraphQLEngineDataSourceKindGraphQL: + var graphqlConfig apidef.GraphQLEngineDataSourceConfigGraphQL + err = json.Unmarshal(ds.Config, &graphqlConfig) + if err != nil { + return nil, err + } + + if graphqlConfig.HasOperation { + planDataSource.Factory = &restDataSource.Factory{ + Client: u.HttpClient, + } + planDataSource.Custom, err = generateRestDataSourceFromGraphql(graphqlConfig) + if err != nil { + return nil, err + } + break + } + + planDataSource.Factory, err = createGraphQLDataSourceFactory(createGraphQLDataSourceFactoryParams{ + graphqlConfig: graphqlConfig, + subscriptionClientFactory: subscriptionClientFactoryOrDefault(u.subscriptionClientFactory), + httpClient: u.HttpClient, + streamingClient: u.StreamingClient, + }) + if err != nil { + return nil, err + } + + planDataSource.Custom = graphqlDataSource.ConfigJson(graphqlDataSourceConfiguration( + graphqlConfig.URL, + graphqlConfig.Method, + graphqlConfig.Headers, + graphqlConfig.SubscriptionType, + )) + + case apidef.GraphQLEngineDataSourceKindKafka: + var kafkaConfig apidef.GraphQLEngineDataSourceConfigKafka + err = json.Unmarshal(ds.Config, &kafkaConfig) + if err != nil { + return nil, err + } + + planDataSource.Factory = &kafkaDataSource.Factory{} + planDataSource.Custom = kafkaDataSource.ConfigJSON(kafkaDataSource.Configuration{ + Subscription: kafkaDataSource.SubscriptionConfiguration{ + BrokerAddresses: kafkaConfig.BrokerAddresses, + Topics: kafkaConfig.Topics, + GroupID: kafkaConfig.GroupID, + ClientID: kafkaConfig.ClientID, + KafkaVersion: kafkaConfig.KafkaVersion, + StartConsumingLatest: kafkaConfig.StartConsumingLatest, + BalanceStrategy: kafkaConfig.BalanceStrategy, + IsolationLevel: kafkaConfig.IsolationLevel, + SASL: kafkaConfig.SASL, + }, + }) + } + + planDataSources = append(planDataSources, planDataSource) + } + + err = u.determineChildNodes(planDataSources) + return planDataSources, err +} + +func (u *UniversalDataGraph) engineConfigV2Arguments(fieldConfs *plan.FieldConfigurations, generatedArgs map[graphql.TypeFieldLookupKey]graphql.TypeFieldArguments) { + for i := range *fieldConfs { + if len(generatedArgs) == 0 { + return + } + + lookupKey := graphql.CreateTypeFieldLookupKey((*fieldConfs)[i].TypeName, (*fieldConfs)[i].FieldName) + currentArgs, ok := generatedArgs[lookupKey] + if !ok { + continue + } + + (*fieldConfs)[i].Arguments = createArgumentConfigurationsForArgumentNames(currentArgs.ArgumentNames...) + delete(generatedArgs, lookupKey) + } + + for _, genArgs := range generatedArgs { + *fieldConfs = append(*fieldConfs, plan.FieldConfiguration{ + TypeName: genArgs.TypeName, + FieldName: genArgs.FieldName, + Arguments: createArgumentConfigurationsForArgumentNames(genArgs.ArgumentNames...), + }) + } +} + +func (u *UniversalDataGraph) determineChildNodes(planDataSources []plan.DataSourceConfiguration) error { + for i := range planDataSources { + if _, ok := planDataSources[i].Factory.(*restDataSource.Factory); ok { + continue + } + for j := range planDataSources[i].RootNodes { + typeName := planDataSources[i].RootNodes[j].TypeName + for k := range planDataSources[i].RootNodes[j].FieldNames { + fieldName := planDataSources[i].RootNodes[j].FieldNames[k] + typeFields := u.Schema.GetAllNestedFieldChildrenFromTypeField(typeName, fieldName, graphql.NewIsDataSourceConfigV2RootFieldSkipFunc(planDataSources)) + + children := make([]plan.TypeField, 0) + for _, tf := range typeFields { + childNode := plan.TypeField{ + TypeName: tf.TypeName, + FieldNames: tf.FieldNames, + } + children = append(children, childNode) + } + planDataSources[i].ChildNodes = append(planDataSources[i].ChildNodes, children...) + } + } + } + return nil +}