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

Implement GET /api/v1/node/{name}/configuration #46

Merged
merged 6 commits into from
Dec 22, 2024

Conversation

jschlyter
Copy link
Member

@jschlyter jschlyter commented Dec 19, 2024

Closes #45

Summary by CodeRabbit

  • New Features
    • Introduced a new endpoint to retrieve node configuration details.
    • Updated enrollment process to return a new response model, NodeEnrollmentResult.
    • Added a method to create node configurations.
    • Added a new configuration option for configuration_ttl in settings.
  • Bug Fixes
    • Enhanced error handling for public key retrieval and node renewal processes.
  • Tests
    • Added a new test case for the node configuration retrieval functionality.

@jschlyter jschlyter requested a review from eest December 19, 2024 21:04
@jschlyter jschlyter requested a review from a team as a code owner December 19, 2024 21:04
Copy link
Contributor

coderabbitai bot commented Dec 19, 2024

Caution

Review failed

The pull request is closed.

Walkthrough

The pull request introduces several changes to enhance the node management API. Key modifications include updating the NodeConfiguration class to inherit from BaseModel, the addition of a new NodeEnrollmentResult class that combines features of NodeConfiguration and NodeCertificate, and the implementation of a new endpoint, get_node_configuration, to retrieve node configuration details. These changes facilitate the retrieval of current enrollment data without necessitating a new enrollment process.

Changes

File Changes
nodeman/models.py - NodeConfiguration now inherits from BaseModel instead of NodeCertificate
- Added new NodeEnrollmentResult class inheriting from NodeConfiguration and NodeCertificate
nodeman/nodes.py - Updated enroll_node endpoint to return NodeEnrollmentResult
- Added new get_node_configuration endpoint to retrieve node configuration
- Introduced create_node_configuration function
tests/test_api.py - Added test case for retrieving node configuration endpoint
nodeman/settings.py - Added new field configuration_ttl to NodesSettings class with a default value of 300

Assessment against linked issues

Objective Addressed Explanation
Add API endpoint for looking up current enrollment config [#45]

Possibly related PRs

  • Send enrollment key as JWK #34: The changes in nodeman/models.py regarding the NodeEnrollmentResult class and its relationship with NodeConfiguration and NodeCertificate are directly related to the modifications in the nodeman/nodes.py file where NodeEnrollmentResult is integrated into the enroll_node endpoint, indicating a strong connection in how enrollment data is structured and processed.
  • Generic enrollment keys #36: The introduction of the TapirNodeEnrollment class in nodeman/db_models.py and its usage in nodeman/nodes.py for managing enrollment keys aligns with the changes in the main PR, particularly the modifications to how enrollment information is handled and stored, reflecting a cohesive update across the models and endpoints.

Poem

🐰 A rabbit's tale of node config delight,
Where endpoints shine with data so bright!
No more re-enrolling, just one simple call,
Retrieving configurations, standing tall.
Configuration magic, with just a few lines,
Our node management system now brilliantly shines! 🌟


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01016ae and bac0983.

📒 Files selected for processing (1)
  • nodeman/settings.py (2 hunks)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
nodeman/nodes.py (2)

Line range hint 273-281: Refactor duplicate configuration object creation

The configuration object creation logic is duplicated between enroll_node and get_node_configuration. Consider extracting this into a helper method.

+def _create_node_configuration(name: str, request: Request) -> NodeConfiguration:
+    return NodeConfiguration(
+        name=name,
+        mqtt_broker=request.app.settings.nodes.mqtt_broker,
+        mqtt_topics=request.app.settings.nodes.mqtt_topics,
+        trusted_jwks=request.app.trusted_jwks,
+    )

 async def enroll_node(
     name: str,
     request: Request,
 ) -> NodeEnrollmentResult:
     # ... existing code ...
     return NodeEnrollmentResult(
-        name=name,
-        mqtt_broker=request.app.settings.nodes.mqtt_broker,
-        mqtt_topics=request.app.settings.nodes.mqtt_topics,
-        trusted_jwks=request.app.trusted_jwks,
+        **_create_node_configuration(name, request).model_dump(),
         x509_certificate=node_certificate.x509_certificate,
         x509_ca_certificate=node_certificate.x509_ca_certificate,
         x509_certificate_serial_number=node_certificate.x509_certificate_serial_number,
         x509_certificate_not_valid_after=node_certificate.x509_certificate_not_valid_after,
     )

 async def get_node_configuration(
     name: str,
     request: Request,
 ) -> NodeConfiguration:
     # ... existing code ...
-    return NodeConfiguration(
-        name=name,
-        mqtt_broker=request.app.settings.nodes.mqtt_broker,
-        mqtt_topics=request.app.settings.nodes.mqtt_topics,
-        trusted_jwks=request.app.trusted_jwks,
-    )
+    return _create_node_configuration(name, request)

Also applies to: 359-364


351-351: Fix incorrect docstring

The docstring "Enroll new node" is incorrect for the get_node_configuration endpoint.

-    """Enroll new node"""
+    """Get node configuration"""
tests/test_api.py (1)

151-155: Enhance configuration endpoint test coverage

The test could be more comprehensive by:

  1. Verifying all configuration fields (mqtt_broker, mqtt_topics, trusted_jwks)
  2. Adding test cases for error scenarios (e.g., non-activated node)
     response = client.get(f"{node_url}/configuration")
     assert response.status_code == status.HTTP_200_OK
     node_information = response.json()
     print(json.dumps(node_information, indent=4))
     assert node_information["name"] == name
+    assert "mqtt_broker" in node_information
+    assert "mqtt_topics" in node_information
+    assert "trusted_jwks" in node_information
+
+    # Test non-activated node
+    non_activated_name = ".".join(["non-activated", settings.nodes.domain])
+    response = admin_client.post(
+        urljoin(server, "/api/v1/node"),
+        params={"name": non_activated_name}
+    )
+    assert response.status_code == status.HTTP_201_CREATED
+    
+    response = client.get(f"/api/v1/node/{non_activated_name}/configuration")
+    assert response.status_code == status.HTTP_400_BAD_REQUEST
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e8b85d and 834ce90.

📒 Files selected for processing (3)
  • nodeman/models.py (3 hunks)
  • nodeman/nodes.py (5 hunks)
  • tests/test_api.py (2 hunks)
🔇 Additional comments (2)
nodeman/models.py (2)

Line range hint 90-99: LGTM: Good separation of concerns

The change to make NodeConfiguration inherit from BaseModel instead of NodeCertificate improves the design by properly separating configuration data from certificate data.


101-102: LGTM: Well-structured composite model

The NodeEnrollmentResult class effectively combines configuration and certificate data through multiple inheritance, providing a clean way to return both aspects in enrollment responses.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
nodeman/nodes.py (2)

52-58: Add documentation and type hints to the helper function.

The function encapsulates configuration creation logic well, but could benefit from:

  • A docstring explaining its purpose and parameters
  • Type hint for the request parameter
-def create_node_configuration(name: str, request: Request) -> NodeConfiguration:
+def create_node_configuration(name: str, request: Request) -> NodeConfiguration:
+    """Create a NodeConfiguration instance for a given node.
+
+    Args:
+        name: The name of the node
+        request: The FastAPI request object containing application settings
+
+    Returns:
+        NodeConfiguration: The node configuration instance
+    """
     return NodeConfiguration(
         name=name,
         mqtt_broker=request.app.settings.nodes.mqtt_broker,
         mqtt_topics=request.app.settings.nodes.mqtt_topics,
         trusted_jwks=request.app.trusted_jwks,
     )

221-221: Update the docstring to accurately reflect the endpoint's behavior.

The current docstring "Enroll new node" is misleading as this endpoint handles both new node enrollment and returns enrollment results for existing nodes.

-    """Enroll new node"""
+    """Enroll a node and return enrollment results.
+
+    This endpoint handles the node enrollment process and returns both
+    configuration and certificate information in the enrollment result.
+    """

Also applies to: 282-287

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 834ce90 and d34c0f8.

📒 Files selected for processing (1)
  • nodeman/nodes.py (6 hunks)
🔇 Additional comments (1)
nodeman/nodes.py (1)

344-365: Implementation successfully meets PR objectives.

The new endpoint is well-integrated with the existing codebase and follows established patterns. It correctly handles error cases and reuses the configuration logic. With the suggested improvements to observability and documentation, this will be a solid addition to the API.

nodeman/nodes.py Outdated Show resolved Hide resolved
nodeman/nodes.py Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
nodeman/nodes.py (2)

55-61: Add docstring to helper function.

The helper function is well-structured but would benefit from documentation explaining its purpose and parameters.

 def create_node_configuration(name: str, request: Request) -> NodeConfiguration:
+    """Create a NodeConfiguration instance for the given node.
+
+    Args:
+        name: The name of the node
+        request: FastAPI request object containing application settings
+
+    Returns:
+        NodeConfiguration: Configuration including MQTT broker details and trusted JWKS
+    """
     return NodeConfiguration(
         name=name,
         mqtt_broker=request.app.settings.nodes.mqtt_broker,
         mqtt_topics=request.app.settings.nodes.mqtt_topics,
         trusted_jwks=request.app.trusted_jwks,
     )

347-372: Consider adding response caching.

The configuration endpoint could benefit from response caching since the configuration data is likely to change infrequently. This would improve performance for frequently accessed nodes.

 @router.get(
     "/api/v1/node/{name}/configuration",
     responses={
         status.HTTP_200_OK: {"model": NodeConfiguration},
         status.HTTP_404_NOT_FOUND: {},
     },
     tags=["client"],
     response_model_exclude_none=True,
+    response_class=CachedResponse,
 )
 async def get_node_configuration(
     name: str,
     request: Request,
+    response: Response,
 ) -> NodeConfiguration:
     """Get node configuration"""
 
     node = find_node(name)
 
     if not node.activated:
         logging.debug("Node %s not activated", name, extra={"nodename": name})
         raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Node not activated")
 
     res = create_node_configuration(name=name, request=request)
 
     node_configurations_requested.add(1)
 
+    # Cache response for 5 minutes
+    response.headers["Cache-Control"] = "public, max-age=300"
+
     return res
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d34c0f8 and 2c431df.

📒 Files selected for processing (1)
  • nodeman/nodes.py (7 hunks)
🔇 Additional comments (3)
nodeman/nodes.py (3)

22-22: LGTM: Import and metrics changes are well-structured.

The new NodeEnrollmentResult import and node_configurations_requested metric counter follow the established patterns in the codebase.

Also applies to: 40-42


215-215: LGTM: Enrollment response model changes are correct.

The update to use NodeEnrollmentResult and the helper function improves code organization.

Also applies to: 224-224, 285-291


22-22: Verify NodeEnrollmentResult usage across codebase.

Let's ensure the new NodeEnrollmentResult model is properly handled by API clients.

Also applies to: 215-215, 224-224

✅ Verification successful

NodeEnrollmentResult implementation and usage appears correct

The verification shows:

  • NodeEnrollmentResult properly inherits from NodeConfiguration and NodeCertificate models
  • The model is correctly used in the enroll_node endpoint with proper error handling
  • Test coverage exists for both successful and error cases of node enrollment
  • The implementation follows a consistent pattern across the codebase
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for NodeEnrollmentResult usage in tests and client code
rg "NodeEnrollmentResult" --type python

# Search for enroll_node function calls to verify proper response handling
ast-grep --pattern 'enroll_node($$$)'

Length of output: 110


Script:

#!/bin/bash
# Search for NodeEnrollmentResult usage without file type restriction
rg "NodeEnrollmentResult"

# Search for enroll_node function calls
rg "enroll_node" -A 2

Length of output: 455


Script:

#!/bin/bash
# Get more context around NodeEnrollmentResult class definition
rg -B 2 -A 5 "class NodeEnrollmentResult"

# Get more context around enroll_node function to see response handling
rg -B 2 -A 10 "async def enroll_node"

Length of output: 826


Script:

#!/bin/bash
# Check NodeConfiguration and NodeCertificate parent classes
rg -B 2 -A 5 "class Node(Configuration|Certificate)"

# Look for any imports of NodeEnrollmentResult
rg "from.*NodeEnrollmentResult|import.*NodeEnrollmentResult"

# Check for any tests related to node enrollment
rg -l "test.*enroll.*node|test.*node.*enroll"

Length of output: 1085


Script:

#!/bin/bash
# Check the enrollment test implementation
rg -B 5 -A 10 "def test.*enroll.*node" tests/test_api.py

# Look for any error handling patterns in tests
rg "HTTP_400_BAD_REQUEST.*enroll" tests/test_api.py

Length of output: 1028

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
nodeman/settings.py (1)

48-48: Add validation constraints for configuration_ttl.

While the default value of 300 seconds is reasonable, consider adding validation constraints to ensure the TTL remains within acceptable bounds.

-    configuration_ttl: int = Field(default=300)
+    configuration_ttl: int = Field(default=300, gt=0, le=86400, description="Configuration cache TTL in seconds")
nodeman/nodes.py (1)

373-375: Consider adding ETag for caching optimization.

While the Cache-Control header is properly implemented using the configuration_ttl setting, consider adding an ETag header for more efficient caching. This would allow clients to use If-None-Match for conditional requests.

     # Cache response for 5 minutes
     max_age = request.app.settings.nodes.configuration_ttl
     response.headers["Cache-Control"] = f"public, max-age={max_age}"
+    # Add ETag for conditional requests
+    config_hash = hashlib.sha256(
+        f"{res.model_dump_json()}".encode()
+    ).hexdigest()
+    response.headers["ETag"] = f'W/"{config_hash}"'
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c431df and 01016ae.

📒 Files selected for processing (3)
  • nodeman/nodes.py (7 hunks)
  • nodeman/settings.py (1 hunks)
  • tests/test_api.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_api.py
🔇 Additional comments (5)
nodeman/nodes.py (5)

22-22: LGTM!

The new import and metric counter are properly integrated, following the established patterns in the codebase.

Also applies to: 40-42


55-62: LGTM!

Good refactoring to extract common configuration creation logic into a reusable function.


215-215: LGTM!

The endpoint now returns a combined NodeEnrollmentResult, properly constructed using the helper function.

Also applies to: 224-224, 285-290


361-361: Fix incorrect docstring.

The docstring needs to be updated to accurately describe the endpoint's purpose.


356-377: Add observability instrumentation.

For consistency with other endpoints, add OpenTelemetry tracing and logging.

@jschlyter jschlyter merged commit 6d51236 into main Dec 22, 2024
2 of 3 checks passed
@jschlyter jschlyter deleted the get_node_configuration branch December 22, 2024 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add API endpoint for looking up current enrollment config
1 participant