apollographql / router

A configurable, high-performance routing runtime for Apollo Federation 🚀
https://www.apollographql.com/docs/router/
Other
810 stars 273 forks source link

Introspection query fails with a managed federated supergraph #637

Closed delkopiso closed 2 years ago

delkopiso commented 2 years ago

Describe the bug Introspection query fails with a managed federated supergraph.

To Reproduce Steps to reproduce the behavior:

  1. Setup a subgraph:
    
    type User {
    id: ID!
    name: String!
    }

input RegisterInput { name: String! email: String! password: String! }

input LoginInput { username: String! password: String! }

type Mutation { login(input: LoginInput!): String! register(input: RegisterInput!): Boolean! }

type Query { me: User! }


2. Setup  supergraph via Apollo Studio and Apollo Router with v2 Federation

3. Make introspection query to supergraph gateway
```shell
curl 'http://localhost/api/gateway/graphql' -iL -X POST -H 'content-type: application/json' -d '{"operation": "IntrospectionQuery", "query":"query IntrospectionQuery { __schema { types { name } } }", "variables": {}}'                                                   
HTTP/1.1 500 Internal Server Error
Content-Length: 201
Date: Tue, 15 Mar 2022 02:19:21 GMT
Content-Type: text/plain; charset=utf-8

{"errors":[{"message":"Value retrieval failed: Empty query plan. This often means an unhandled Introspection query was sent. Please file an issue to apollographql/router.","locations":[],"path":null}]}

Expected behavior The same output as querying the subgraph directly:

curl 'http://localhost/api/accounts/graphql' -iL -X POST -H 'content-type: application/json' -d '{"operation": "IntrospectionQuery", "query":"query IntrospectionQuery { __schema { types { name } } }", "variables": {}}'
HTTP/1.1 200 OK
Content-Length: 273
Content-Type: application/json
Date: Tue, 15 Mar 2022 01:55:41 GMT

{"data":{"__schema":{"types":[{"name":"Boolean"},{"name":"Float"},{"name":"ID"},{"name":"Int"},{"name":"LoginInput"},{"name":"Mutation"},{"name":"Query"},{"name":"RegisterInput"},{"name":"String"},{"name":"User"},{"name":"_Any"},{"name":"_FieldSet"},{"name":"_Service"}]}}}

Output Apollo Router logs:

{"timestamp":"2022-03-15T02:19:21.978929Z","level":"DEBUG","fields":{"message":"finished processing request","latency":"475 ms","status":500},"target":"tower_http::trace::on_response","span":{"method":"POST","uri":"/graphql","version":"HTTP/1.1","name":"request"},"spans":[{"method":"POST","uri":"/graphql","version":"HTTP/1.1","name":"request"}]}
{"timestamp":"2022-03-15T02:19:21.979482Z","level":"ERROR","fields":{"message":"response failed","classification":"Status code: 500 Internal Server Error","latency":"475 ms"},"target":"tower_http::trace::on_failure","span":{"method":"POST","uri":"/graphql","version":"HTTP/1.1","name":"request"},"spans":[{"method":"POST","uri":"/graphql","version":"HTTP/1.1","name":"request"}]}

Desktop (please complete the following information): Docker for Mac running on MacOS Monterrey 12.2.1 Apollo Router v0.1.0-alpha.8

abernix commented 2 years ago

@delkopiso currently, we safe list particular well-known introspection queries, like the one provided by graphql-js and those sent by tooling and editors.

We're absolutely happy to add this query to the list, but just out of curiosity, is that an introspection query that you just often run yourself? Or is it from a particular tool? A little more information would just be education in how we think about this. Thanks!

delkopiso commented 2 years ago

@abernix the safe listing makes sense. And yes, that's just an introspection I run for sanity checks when things go wrong.

The issue I began with was that @graphql-codegen/cli was failing to generate types when pointed to the managed supergraph. I was getting the following error: Value retrieval failed: Empty query plan. This often means an unhandled Introspection query was sent. Please file an issue to apollographql/router.

So your response makes that error message make more sense.

According to @graphql-codegen/cli's docs they are using the introspection query provided by graphql-js. And switching from using rotuer to gateway fixes my underlying problem, so I still think there's an existing issue with instrospection resolution in router. Let me see if I can produce a small reproduction repo.

abernix commented 2 years ago

@delkopiso There are slight variations in some versions, including variations in spacing. We currently do exact matching with no normalization (#43, maybe). That's to say, if you can capture the exact query that @graphql-codegen/cli is sending, we'll happily add that!

delkopiso commented 2 years ago

@abernix I created a repo demonstrating the difference in expected behavior: https://github.com/delkopiso/apollo-router-introspection-test.

I went through the logs to grab the introspection queries both servers received:

Gateway Log ```javascript { body: { query: 'query IntrospectionQuery {\n' + ' __schema {\n' + ' queryType {\n' + ' name\n' + ' }\n' + ' mutationType {\n' + ' name\n' + ' }\n' + ' subscriptionType {\n' + ' name\n' + ' }\n' + ' types {\n' + ' ...FullType\n' + ' }\n' + ' directives {\n' + ' name\n' + ' description\n' + ' locations\n' + ' args {\n' + ' ...InputValue\n' + ' }\n' + ' }\n' + ' }\n' + '}\n' + '\n' + 'fragment FullType on __Type {\n' + ' kind\n' + ' name\n' + ' description\n' + ' fields(includeDeprecated: true) {\n' + ' name\n' + ' description\n' + ' args {\n' + ' ...InputValue\n' + ' }\n' + ' type {\n' + ' ...TypeRef\n' + ' }\n' + ' isDeprecated\n' + ' deprecationReason\n' + ' }\n' + ' inputFields {\n' + ' ...InputValue\n' + ' }\n' + ' interfaces {\n' + ' ...TypeRef\n' + ' }\n' + ' enumValues(includeDeprecated: true) {\n' + ' name\n' + ' description\n' + ' isDeprecated\n' + ' deprecationReason\n' + ' }\n' + ' possibleTypes {\n' + ' ...TypeRef\n' + ' }\n' + '}\n' + '\n' + 'fragment InputValue on __InputValue {\n' + ' name\n' + ' description\n' + ' type {\n' + ' ...TypeRef\n' + ' }\n' + ' defaultValue\n' + '}\n' + '\n' + 'fragment TypeRef on __Type {\n' + ' kind\n' + ' name\n' + ' ofType {\n' + ' kind\n' + ' name\n' + ' ofType {\n' + ' kind\n' + ' name\n' + ' ofType {\n' + ' kind\n' + ' name\n' + ' ofType {\n' + ' kind\n' + ' name\n' + ' ofType {\n' + ' kind\n' + ' name\n' + ' ofType {\n' + ' kind\n' + ' name\n' + ' ofType {\n' + ' kind\n' + ' name\n' + ' }\n' + ' }\n' + ' }\n' + ' }\n' + ' }\n' + ' }\n' + ' }\n' + '}' } } ```
Router Log ```jsonl {"timestamp":"2022-03-17T05:37:31.597637Z","level":"DEBUG","fields":{"message":"incoming body is content-length (1414 bytes)"},"target":"hyper::proto::h1::conn"} {"timestamp":"2022-03-17T05:37:31.601865Z","level":"DEBUG","fields":{"message":"started processing request"},"target":"tower_http::trace::on_request","span":{"method":"POST","uri":"/graphql","version":"HTTP/1.1","name":"request"},"spans":[{"method":"POST","uri":"/graphql","version":"HTTP/1.1","name":"request"}]} {"timestamp":"2022-03-17T05:37:31.603791Z","level":"DEBUG","fields":{"message":"incoming body completed"},"target":"hyper::proto::h1::conn"} {"timestamp":"2022-03-17T05:37:31.607769Z","level":"DEBUG","fields":{"service.ready":true,"message":"processing request"},"target":"tower::buffer::worker","span":{"operation_name":"","query":"query IntrospectionQuery {\n __schema {\n queryType {\n name\n }\n mutationType {\n name\n }\n subscriptionType {\n name\n }\n types {\n ...FullType\n }\n directives {\n name\n description\n locations\n args {\n ...InputValue\n }\n }\n }\n}\n\nfragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n description\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n}\n\nfragment InputValue on __InputValue {\n name\n description\n type {\n ...TypeRef\n }\n defaultValue\n}\n\nfragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n}","name":"graphql_request"},"spans":[{"method":"POST","uri":"/graphql","version":"HTTP/1.1","name":"request"},{"operation_name":"","query":"query IntrospectionQuery {\n __schema {\n queryType {\n name\n }\n mutationType {\n name\n }\n subscriptionType {\n name\n }\n types {\n ...FullType\n }\n directives {\n name\n description\n locations\n args {\n ...InputValue\n }\n }\n }\n}\n\nfragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n description\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n}\n\nfragment InputValue on __InputValue {\n name\n description\n type {\n ...TypeRef\n }\n defaultValue\n}\n\nfragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n}","name":"graphql_request"}]} {"timestamp":"2022-03-17T05:37:31.609600Z","level":"DEBUG","fields":{"service.ready":true,"message":"processing request"},"target":"tower::buffer::worker","span":{"operation_name":"","query":"query IntrospectionQuery {\n __schema {\n queryType {\n name\n }\n mutationType {\n name\n }\n subscriptionType {\n name\n }\n types {\n ...FullType\n }\n directives {\n name\n description\n locations\n args {\n ...InputValue\n }\n }\n }\n}\n\nfragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n description\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n}\n\nfragment InputValue on __InputValue {\n name\n description\n type {\n ...TypeRef\n }\n defaultValue\n}\n\nfragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n}","name":"graphql_request"},"spans":[{"method":"POST","uri":"/graphql","version":"HTTP/1.1","name":"request"},{"operation_name":"","query":"query IntrospectionQuery {\n __schema {\n queryType {\n name\n }\n mutationType {\n name\n }\n subscriptionType {\n name\n }\n types {\n ...FullType\n }\n directives {\n name\n description\n locations\n args {\n ...InputValue\n }\n }\n }\n}\n\nfragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n description\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n}\n\nfragment InputValue on __InputValue {\n name\n description\n type {\n ...TypeRef\n }\n defaultValue\n}\n\nfragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n}","name":"graphql_request"}]} {"timestamp":"2022-03-17T05:37:31.613664Z","level":"DEBUG","fields":{"service.ready":true,"message":"processing request"},"target":"tower::buffer::worker","span":{"operation_name":"","query":"query IntrospectionQuery {\n __schema {\n queryType {\n name\n }\n mutationType {\n name\n }\n subscriptionType {\n name\n }\n types {\n ...FullType\n }\n directives {\n name\n description\n locations\n args {\n ...InputValue\n }\n }\n }\n}\n\nfragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n description\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n}\n\nfragment InputValue on __InputValue {\n name\n description\n type {\n ...TypeRef\n }\n defaultValue\n}\n\nfragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n}","name":"graphql_request"},"spans":[{"method":"POST","uri":"/graphql","version":"HTTP/1.1","name":"request"},{"operation_name":"","query":"query IntrospectionQuery {\n __schema {\n queryType {\n name\n }\n mutationType {\n name\n }\n subscriptionType {\n name\n }\n types {\n ...FullType\n }\n directives {\n name\n description\n locations\n args {\n ...InputValue\n }\n }\n }\n}\n\nfragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n description\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n}\n\nfragment InputValue on __InputValue {\n name\n description\n type {\n ...TypeRef\n }\n defaultValue\n}\n\nfragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n}","name":"graphql_request"}]} {"timestamp":"2022-03-17T05:37:31.616015Z","level":"DEBUG","fields":{"message":"finished processing request","latency":"17 ms","status":500},"target":"tower_http::trace::on_response","span":{"method":"POST","uri":"/graphql","version":"HTTP/1.1","name":"request"},"spans":[{"method":"POST","uri":"/graphql","version":"HTTP/1.1","name":"request"}]} {"timestamp":"2022-03-17T05:37:31.616266Z","level":"ERROR","fields":{"message":"response failed","classification":"Status code: 500 Internal Server Error","latency":"17 ms"},"target":"tower_http::trace::on_failure","span":{"method":"POST","uri":"/graphql","version":"HTTP/1.1","name":"request"},"spans":[{"method":"POST","uri":"/graphql","version":"HTTP/1.1","name":"request"}]} {"timestamp":"2022-03-17T05:37:31.616863Z","level":"DEBUG","fields":{"message":"flushed 329 bytes"},"target":"hyper::proto::h1::io"} ```

Hopefully this helps

abernix commented 2 years ago

Thank you so much for the reproduction! I see two potential solutions the Router team may take here: