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

bug: schema fails to load when a schemanode defines a single uniqueness constraint on a relationship, without an HFID #4483

Open
wvandeun opened this issue Sep 27, 2024 · 0 comments
Labels
group/backend Issue related to the backend (API Server, Git Agent) type/bug Something isn't working as expected

Comments

@wvandeun
Copy link
Contributor

Component

API Server / GraphQL

Infrahub version

0.16.1

Current Behavior

When you load the following schema:

---
version: "1.0"
nodes:
  - name: One
    namespace: Node
    attributes:
      - name: name
        kind: Text
        optional: false
  - name: Two
    namespace: Node
    uniqueness_constraints:
      - ["one"]
    relationships:
      - name: one
        peer: NodeOne
        kind: Attribute
        optional: false
        cardinality: one

You get the following error message:

HTTP communication failure: Server error '500 Internal Server Error' for url 'http://localhost:8000/api/schema/load?branch=main'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500 on POST to http://localhost:8000/api/schema/load?branch=main

The following exception can be found in the logs of the API server

{"event": "Exception in ASGI application
", "timestamp": "2024-09-27T22:21:10.715780Z", "logger": "uvicorn.error", "level": "error", "exception": "  + Exception Group Traceback (most recent call last):
  |   File \"/usr/local/lib/python3.12/site-packages/starlette/_utils.py\", line 77, in collapse_excgroups
  |     yield
  |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 186, in __call__
  |     async with anyio.create_task_group() as task_group:
  |                ^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File \"/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py\", line 680, in __aexit__
  |     raise BaseExceptionGroup(
  | ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
  +-+---------------- 1 ----------------
    | Traceback (most recent call last):
    |   File \"/usr/local/lib/python3.12/site-packages/uvicorn/protocols/http/httptools_impl.py\", line 401, in run_asgi
    |     result = await app(  # type: ignore[func-returns-value]
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File \"/usr/local/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py\", line 70, in __call__
    |     return await self.app(scope, receive, send)
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File \"/usr/local/lib/python3.12/site-packages/fastapi/applications.py\", line 1054, in __call__
    |     await super().__call__(scope, receive, send)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/applications.py\", line 113, in __call__
    |     await self.middleware_stack(scope, receive, send)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/errors.py\", line 187, in __call__
    |     raise exc
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/errors.py\", line 165, in __call__
    |     await self.app(scope, receive, _send)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/gzip.py\", line 20, in __call__
    |     await responder(scope, receive, send)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/gzip.py\", line 39, in __call__
    |     await self.app(scope, receive, self.send_with_gzip)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/cors.py\", line 85, in __call__
    |     await self.app(scope, receive, send)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette_exporter/middleware.py\", line 499, in __call__
    |     raise exception
    |   File \"/usr/local/lib/python3.12/site-packages/starlette_exporter/middleware.py\", line 405, in __call__
    |     await self.app(scope, receive, wrapped_send)
    |   File \"/usr/local/lib/python3.12/site-packages/asgi_correlation_id/middleware.py\", line 90, in __call__
    |     await self.app(scope, receive, handle_outgoing_request)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 185, in __call__
    |     with collapse_excgroups():
    |          ^^^^^^^^^^^^^^^^^^^^
    |   File \"/usr/local/lib/python3.12/contextlib.py\", line 158, in __exit__
    |     self.gen.throw(value)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/_utils.py\", line 83, in collapse_excgroups
    |     raise exc
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 187, in __call__
    |     response = await self.dispatch_func(request, call_next)
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File \"/source/backend/infrahub/server.py\", line 160, in add_telemetry_span_exception
    |     return await call_next(request)
    |            ^^^^^^^^^^^^^^^^^^^^^^^^
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 163, in call_next
    |     raise app_exc
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 149, in coro
    |     await self.app(scope, receive_or_disconnect, send_no_error)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 185, in __call__
    |     with collapse_excgroups():
    |          ^^^^^^^^^^^^^^^^^^^^
    |   File \"/usr/local/lib/python3.12/contextlib.py\", line 158, in __exit__
    |     self.gen.throw(value)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/_utils.py\", line 83, in collapse_excgroups
    |     raise exc
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 187, in __call__
    |     response = await self.dispatch_func(request, call_next)
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File \"/source/backend/infrahub/server.py\", line 149, in add_process_time_header
    |     response = await call_next(request)
    |                ^^^^^^^^^^^^^^^^^^^^^^^^
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 163, in call_next
    |     raise app_exc
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 149, in coro
    |     await self.app(scope, receive_or_disconnect, send_no_error)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 185, in __call__
    |     with collapse_excgroups():
    |          ^^^^^^^^^^^^^^^^^^^^
    |   File \"/usr/local/lib/python3.12/contextlib.py\", line 158, in __exit__
    |     self.gen.throw(value)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/_utils.py\", line 83, in collapse_excgroups
    |     raise exc
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 187, in __call__
    |     response = await self.dispatch_func(request, call_next)
    |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File \"/source/backend/infrahub/server.py\", line 142, in logging_middleware
    |     response = await call_next(request)
    |                ^^^^^^^^^^^^^^^^^^^^^^^^
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 163, in call_next
    |     raise app_exc
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 149, in coro
    |     await self.app(scope, receive_or_disconnect, send_no_error)
    |   File \"/usr/local/lib/python3.12/site-packages/opentelemetry/instrumentation/asgi/__init__.py\", line 631, in __call__
    |     await self.app(scope, otel_receive, otel_send)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/exceptions.py\", line 62, in __call__
    |     await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py\", line 62, in wrapped_app
    |     raise exc
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py\", line 51, in wrapped_app
    |     await app(scope, receive, sender)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/routing.py\", line 715, in __call__
    |     await self.middleware_stack(scope, receive, send)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/routing.py\", line 735, in app
    |     await route.handle(scope, receive, send)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/routing.py\", line 288, in handle
    |     await self.app(scope, receive, send)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/routing.py\", line 76, in app
    |     await wrap_app_handling_exceptions(app, request)(scope, receive, send)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py\", line 62, in wrapped_app
    |     raise exc
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py\", line 51, in wrapped_app
    |     await app(scope, receive, sender)
    |   File \"/usr/local/lib/python3.12/site-packages/starlette/routing.py\", line 73, in app
    |     response = await f(request)
    |                ^^^^^^^^^^^^^^^^
    |   File \"/usr/local/lib/python3.12/site-packages/fastapi/routing.py\", line 301, in app
    |     raw_response = await run_endpoint_function(
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File \"/usr/local/lib/python3.12/site-packages/fastapi/routing.py\", line 212, in run_endpoint_function
    |     return await dependant.call(**values)
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File \"/source/backend/infrahub/api/schema.py\", line 253, in load_schema
    |     candidate_schema, result = evaluate_candidate_schemas(branch_schema=branch_schema, schemas_to_evaluate=schemas)
    |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File \"/source/backend/infrahub/api/schema.py\", line 125, in evaluate_candidate_schemas
    |     candidate_schema.process()
    |   File \"/source/backend/infrahub/core/schema_manager.py\", line 473, in process
    |     self.process_post_validation()
    |   File \"/source/backend/infrahub/core/schema_manager.py\", line 509, in process_post_validation
    |     self.process_human_friendly_id()
    |   File \"/source/backend/infrahub/core/schema_manager.py\", line 1071, in process_human_friendly_id
    |     schema_path = node.parse_schema_path(path=constraint_path, schema=node)
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File \"/source/backend/infrahub/core/schema/basenode_schema.py\", line 427, in parse_schema_path
    |     schema_path.related_schema = schema.get(name=relationship_schema.peer, duplicate=True)
    |                                  ^^^^^^^^^^
    |   File \"/usr/local/lib/python3.12/site-packages/pydantic/main.py\", line 811, in __getattr__
    |     raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')
    | AttributeError: 'NodeSchema' object has no attribute 'get'
    +------------------------------------

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File \"/usr/local/lib/python3.12/site-packages/uvicorn/protocols/http/httptools_impl.py\", line 401, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File \"/usr/local/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py\", line 70, in __call__
    return await self.app(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File \"/usr/local/lib/python3.12/site-packages/fastapi/applications.py\", line 1054, in __call__
    await super().__call__(scope, receive, send)
  File \"/usr/local/lib/python3.12/site-packages/starlette/applications.py\", line 113, in __call__
    await self.middleware_stack(scope, receive, send)
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/errors.py\", line 187, in __call__
    raise exc
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/errors.py\", line 165, in __call__
    await self.app(scope, receive, _send)
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/gzip.py\", line 20, in __call__
    await responder(scope, receive, send)
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/gzip.py\", line 39, in __call__
    await self.app(scope, receive, self.send_with_gzip)
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/cors.py\", line 85, in __call__
    await self.app(scope, receive, send)
  File \"/usr/local/lib/python3.12/site-packages/starlette_exporter/middleware.py\", line 499, in __call__
    raise exception
  File \"/usr/local/lib/python3.12/site-packages/starlette_exporter/middleware.py\", line 405, in __call__
    await self.app(scope, receive, wrapped_send)
  File \"/usr/local/lib/python3.12/site-packages/asgi_correlation_id/middleware.py\", line 90, in __call__
    await self.app(scope, receive, handle_outgoing_request)
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 185, in __call__
    with collapse_excgroups():
         ^^^^^^^^^^^^^^^^^^^^
  File \"/usr/local/lib/python3.12/contextlib.py\", line 158, in __exit__
    self.gen.throw(value)
  File \"/usr/local/lib/python3.12/site-packages/starlette/_utils.py\", line 83, in collapse_excgroups
    raise exc
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 187, in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File \"/source/backend/infrahub/server.py\", line 160, in add_telemetry_span_exception
    return await call_next(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 163, in call_next
    raise app_exc
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 149, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 185, in __call__
    with collapse_excgroups():
         ^^^^^^^^^^^^^^^^^^^^
  File \"/usr/local/lib/python3.12/contextlib.py\", line 158, in __exit__
    self.gen.throw(value)
  File \"/usr/local/lib/python3.12/site-packages/starlette/_utils.py\", line 83, in collapse_excgroups
    raise exc
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 187, in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File \"/source/backend/infrahub/server.py\", line 149, in add_process_time_header
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 163, in call_next
    raise app_exc
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 149, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 185, in __call__
    with collapse_excgroups():
         ^^^^^^^^^^^^^^^^^^^^
  File \"/usr/local/lib/python3.12/contextlib.py\", line 158, in __exit__
    self.gen.throw(value)
  File \"/usr/local/lib/python3.12/site-packages/starlette/_utils.py\", line 83, in collapse_excgroups
    raise exc
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 187, in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File \"/source/backend/infrahub/server.py\", line 142, in logging_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 163, in call_next
    raise app_exc
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/base.py\", line 149, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File \"/usr/local/lib/python3.12/site-packages/opentelemetry/instrumentation/asgi/__init__.py\", line 631, in __call__
    await self.app(scope, otel_receive, otel_send)
  File \"/usr/local/lib/python3.12/site-packages/starlette/middleware/exceptions.py\", line 62, in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
  File \"/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py\", line 62, in wrapped_app
    raise exc
  File \"/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py\", line 51, in wrapped_app
    await app(scope, receive, sender)
  File \"/usr/local/lib/python3.12/site-packages/starlette/routing.py\", line 715, in __call__
    await self.middleware_stack(scope, receive, send)
  File \"/usr/local/lib/python3.12/site-packages/starlette/routing.py\", line 735, in app
    await route.handle(scope, receive, send)
  File \"/usr/local/lib/python3.12/site-packages/starlette/routing.py\", line 288, in handle
    await self.app(scope, receive, send)
  File \"/usr/local/lib/python3.12/site-packages/starlette/routing.py\", line 76, in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
  File \"/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py\", line 62, in wrapped_app
    raise exc
  File \"/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py\", line 51, in wrapped_app
    await app(scope, receive, sender)
  File \"/usr/local/lib/python3.12/site-packages/starlette/routing.py\", line 73, in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
  File \"/usr/local/lib/python3.12/site-packages/fastapi/routing.py\", line 301, in app
    raw_response = await run_endpoint_function(
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File \"/usr/local/lib/python3.12/site-packages/fastapi/routing.py\", line 212, in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File \"/source/backend/infrahub/api/schema.py\", line 253, in load_schema
    candidate_schema, result = evaluate_candidate_schemas(branch_schema=branch_schema, schemas_to_evaluate=schemas)
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File \"/source/backend/infrahub/api/schema.py\", line 125, in evaluate_candidate_schemas
    candidate_schema.process()
  File \"/source/backend/infrahub/core/schema_manager.py\", line 473, in process
    self.process_post_validation()
  File \"/source/backend/infrahub/core/schema_manager.py\", line 509, in process_post_validation
    self.process_human_friendly_id()
  File \"/source/backend/infrahub/core/schema_manager.py\", line 1071, in process_human_friendly_id
    schema_path = node.parse_schema_path(path=constraint_path, schema=node)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File \"/source/backend/infrahub/core/schema/basenode_schema.py\", line 427, in parse_schema_path
    schema_path.related_schema = schema.get(name=relationship_schema.peer, duplicate=True)
                                 ^^^^^^^^^^
  File \"/usr/local/lib/python3.12/site-packages/pydantic/main.py\", line 811, in __getattr__
    raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')
AttributeError: 'NodeSchema' object has no attribute 'get'"}

If you modify the schema (add HFID) then it load fine

---
version: "1.0"
nodes:
  - name: One
    namespace: Node
    attributes:
      - name: name
        kind: Text
        optional: false
  - name: Two
    namespace: Node
    uniqueness_constraints:
      - ["one"]
    human_friendly_id:
      - one__name__value
    relationships:
      - name: one
        peer: NodeOne
        kind: Attribute
        optional: false
        cardinality: one

Expected Behavior

If this is considered to be an invalid schema, then we should return a proper error message to the user informing them of the exact issue and how it can be corrected. If this is considered a valid schema then we should load it without any error message.

Steps to Reproduce

  • start an Infrahub instance
  • try loading the following schema
```yaml
---
version: "1.0"
nodes:
  - name: One
    namespace: Node
    attributes:
      - name: name
        kind: Text
        optional: false
  - name: Two
    namespace: Node
    uniqueness_constraints:
      - ["one"]
    relationships:
      - name: one
        peer: NodeOne
        kind: Attribute
        optional: false
        cardinality: one

Additional Information

No response

@wvandeun wvandeun added type/bug Something isn't working as expected group/backend Issue related to the backend (API Server, Git Agent) labels Sep 27, 2024
@exalate-issue-sync exalate-issue-sync bot added this to the Infrahub - 0.16.2 milestone Sep 27, 2024
@exalate-issue-sync exalate-issue-sync bot removed the group/backend Issue related to the backend (API Server, Git Agent) label Sep 27, 2024
@exalate-issue-sync exalate-issue-sync bot removed this from the Infrahub - 0.16.2 milestone Sep 27, 2024
@exalate-issue-sync exalate-issue-sync bot added the group/backend Issue related to the backend (API Server, Git Agent) label Sep 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
group/backend Issue related to the backend (API Server, Git Agent) type/bug Something isn't working as expected
Projects
None yet
Development

No branches or pull requests

1 participant