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

Refactor the plugin API #202

Merged
merged 55 commits into from
Mar 13, 2024
Merged

Refactor the plugin API #202

merged 55 commits into from
Mar 13, 2024

Conversation

sporkmonger
Copy link
Contributor

@sporkmonger sporkmonger commented Mar 7, 2024

This is a major redesign of the plugin API. This aligns Bulwark's plugin API to the WASI 0.2 types.

Redesign goal

Parameterize the handlers so that plugin developers no longer have to make awkward function calls to pull in values that make more sense as parameters. (e.g. get_request()) Similarly, handlers now have return values that make their intended output more explicit. A secondary goal is enabling future performance improvement by allowing WASM instances to be recycled instead of having to create and tear them down on every request.

Major changes in this PR

  • wasmtime updates necessary for WASI 0.2 (supercedes Upgrade to wasmtime 16 #191)
  • updated WIT definitions for the new API design
  • refactored external processor, extracting a new ProcessorContext with a lifetime that lines up with the request/response cycle
  • refactored integration tests to make them less flaky

Caveats

  • This PR does not implement instance recycling even though enabling it is one of the PR's goals. This will happen in a follow-up PR.
  • Even though the wasi:http/outgoing-handler API is wired up and implemented, it doesn't seem to be getting exposed correctly in the compiled .wasm output. I don't know why, but I'm inclined to treat it as a bug to fix after this PR lands rather than as part of it, unless the cause of the bug gets spotted during review. This PR has already been in-progress for way too long.

Summary by CodeRabbit

  • New Features

    • Introduced a new job to ensure ABI files are up-to-date.
    • Added compatibility updates for wasmtime releases.
    • Implemented a build crate for constructing plugins with enhanced error handling.
    • Added methods for creating Decision instances and managing plugin information.
    • Expanded functionality for interacting with Redis including rate limiting and circuit breaking.
    • Enhanced plugin execution with new error handling capabilities and type conversions.
    • Introduced interfaces for configuration settings, I/O event handling, random data generation, network operations, and HTTP request/response handling within plugins.
  • Refactor

    • Updated and renamed various internal components and functions for clarity and improved functionality.
    • Restructured plugin and decision-making implementations.
    • Removed redundant code and streamlined error handling across different modules.
  • Documentation

    • Updated README files with new compatibility and functionality details.
  • Chores

    • Updated .gitignore rules across several directories.
  • Style

    • Improved code organization and module structure for better readability and maintenance.

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.

Review Status

Actionable comments generated: 2

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between f24d780 and 430b9cd.
Files selected for processing (4)
  • crates/wasm-host/src/context.rs (1 hunks)
  • crates/wasm-host/src/from.rs (1 hunks)
  • crates/wasm-sdk/src/errors.rs (3 hunks)
  • wit/redis.wit (1 hunks)
Files skipped from review as they are similar to previous changes (3)
  • crates/wasm-host/src/context.rs
  • crates/wasm-host/src/from.rs
  • wit/redis.wit
Additional comments: 8
crates/wasm-sdk/src/errors.rs (8)
  • 3-3: Renaming Error to error and using ::anyhow::anyhow directly simplifies error creation, but it's crucial to ensure this change is consistently applied across the entire codebase to avoid confusion. The previous discussion indicates that anyhow::Error is still used in some places, which might be intentional for wrapping errors. However, it's important to document this decision clearly to maintain clarity in error handling practices.
  • 16-24: The renaming of ParamError to EnvVarError and the update of its variants make the error names more descriptive and aligned with their specific use cases. This change enhances code readability and makes error handling more intuitive.
  • 63-73: Adding ConfigError with new variants improves the specificity of configuration-related errors. This addition follows the same pattern as EnvVarError, making the error handling more consistent across different aspects of the plugin API.
  • 103-105: The conversion from std::string::FromUtf8Error to ConfigError for handling invalid Unicode errors is a good practice, ensuring that errors from different sources can be uniformly handled within the plugin API.
  • 118-127: The addition of RemoteStateError with new variants, including InvalidInteger, enhances the granularity of error handling for remote state operations. This change is in line with the PR's objective to improve the developer experience by making error handling more explicit and intuitive.
  • 130-145: The conversion from crate::wit::bulwark::plugin::redis::Error to RemoteStateError is well-implemented, covering all relevant cases from the Redis error domain. This ensures that errors from Redis operations are correctly mapped to the plugin API's error handling model.
  • 150-154: The conversion from std::string::FromUtf8Error to RemoteStateError for handling invalid Unicode errors in the context of remote state is consistent with similar conversions for other error types, maintaining uniformity in error handling.
  • 158-161: The conversion from ParseCounterError to RemoteStateError for handling invalid integer errors is a thoughtful addition, ensuring that parsing errors are correctly categorized within the broader error handling model of the plugin API.

crates/wasm-sdk/src/errors.rs Show resolved Hide resolved
crates/wasm-sdk/src/errors.rs Show resolved Hide resolved
Copy link

@pullrequest pullrequest bot left a comment

Choose a reason for hiding this comment

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

Syncing a few comments to some of the ongoing discussion.

Image of Ryan Ryan


Reviewed with ❤️ by PullRequest

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.

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 430b9cd and dc66855.
Files selected for processing (1)
  • crates/wasm-host/src/context.rs (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • crates/wasm-host/src/context.rs

@sporkmonger
Copy link
Contributor Author

sporkmonger commented Mar 12, 2024

I did see a TODO mentioning overflow checks. It would probably good to just validate values to be in the specific expected range of 0..u64::MAX as early on as possible, which I'm guessing would be in the guest environment.

I'm assuming you're referring to this comment:

// TODO: handle overflow errors by expiring everything on overflow and returning nil?

That's related to the possibility that we could increment a counter enough that the counter overflows. However, I just tested it out and Redis already guards against overflows natively. We just have to handle that error correctly in the script:

(error) ERR increment or decrement would overflow

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.

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between dc66855 and dedd665.
Files selected for processing (1)
  • crates/wasm-host/src/context.rs (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • crates/wasm-host/src/context.rs

@sporkmonger
Copy link
Contributor Author

sporkmonger commented Mar 12, 2024

That's related to the possibility that we could increment a counter enough that the counter overflows. However, I just tested it out and Redis already guards against overflows natively. We just have to handle that error correctly in the script:

(error) ERR increment or decrement would overflow

Here's the error given by overflow in script execution:

Error::Other("error accessing remote state: An error was signalled by the server - ResponseError: Error running script (call to f_4a0fa977db07ef5425a3504da4db83571294fe4a): @user_script:16: ERR value is not an integer or out of range")

I might just leave it as-is because the error is returned in a Result the plugin can handle rather than panicking and the default non-error-case behavior is to leave the original TTL in place anyway.

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.

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between dedd665 and 7ef4a43.
Files selected for processing (1)
  • crates/wasm-host/src/context.rs (1 hunks)
Additional comments: 8
crates/wasm-host/src/context.rs (8)
  • 16-38: The PluginContext struct is well-defined, encapsulating all necessary components for plugin execution, including WASI contexts, HTTP contexts, I/O buffers, configurations, permissions, and Redis information. This struct serves as a central point for managing plugin execution environments, ensuring that plugins have access to the resources and configurations they need while adhering to security constraints.
  • 40-46: The RedisInfo struct provides a clear and concise way to manage Redis connections and Lua script registries. This struct is crucial for enabling plugins to interact with Redis, supporting rate limiting, circuit breaking, and other features that require Redis operations.
  • 48-65: The ScriptRegistry struct organizes predefined Lua scripts for Redis operations, such as rate limiting and circuit breaking. This approach centralizes script management, making it easier to maintain and update scripts as needed. It's a good practice to keep these scripts within the codebase for better version control and auditability.
  • 216-231: The implementation of the WasiView trait for PluginContext is straightforward and correctly delegates to the internal wasi_table and wasi_ctx fields. This implementation ensures that the plugin context can be used wherever a WASI view is required, facilitating interactions with WASI syscalls.
  • 234-252: The implementation of the WasiHttpView trait, specifically the send_request method, correctly enforces HTTP domain permissions before dispatching outgoing requests. This is a critical security feature that prevents plugins from making unauthorized HTTP requests. The use of verify_http_domains for permission checks is appropriate and aligns with the security model.
  • 322-1029: The implementations of Redis operations (e.g., get, set, del, incr, incr_by, sadd, smembers, srem, expire, expire_at, incr_rate_limit, check_rate_limit, incr_breaker, check_breaker) are comprehensive and cover a wide range of Redis functionalities required by plugins. Each method correctly enforces remote state key permissions using verify_remote_state_prefixes, ensuring that plugins can only access allowed keys. The use of asynchronous futures and the ? operator for error handling is consistent and idiomatic in Rust async programming. These implementations provide a solid foundation for plugins to interact with Redis in a secure and controlled manner.
  • 1032-1051: The verify_http_domains function correctly parses the authority part of an HTTP request and checks if it's allowed based on the configured permissions. The use of Url::parse for parsing and the domain extraction logic is appropriate. This function plays a crucial role in enforcing HTTP domain permissions, ensuring that plugins can only make requests to allowed domains.
  • 1053-1069: The verify_remote_state_prefixes function ensures that access to any remote state key adheres to the configured permissions. The logic for checking if a key starts with any of the allowed prefixes is correct and straightforward. This function is essential for maintaining the security and integrity of remote state access by plugins.

Copy link

@pullrequest pullrequest bot left a comment

Choose a reason for hiding this comment

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

I've just finished my final pass over this review.

Like I said in an earlier pass, this refactor looks quite solid and has a lot of great improvements. The main points I noted were mostly just related to edge cases with Redis, and those have already been addressed, just to be extra careful.

Otherwise, I just had 2 additional comments in this final pass.


Thanks for all of the hard work you're doing on maintaining this project! Like I expressed when reviewing last May, I'm a big fan of it.

Image of Ryan Ryan


Reviewed with ❤️ by PullRequest

crates/ext-processor/src/service.rs Outdated Show resolved Hide resolved
crates/wasm-host/src/context.rs 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.

Review Status

Actionable comments generated: 3

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 7ef4a43 and e310777.
Files selected for processing (1)
  • crates/wasm-sdk-macros/src/lib.rs (9 hunks)
Check Runs (2)
Clippy completed (6)
Test suite (stable, ubuntu-latest) completed (6)
Additional comments: 8
crates/wasm-sdk-macros/src/lib.rs (8)
  • 6-6: The constant WIT_PATH is defined using concat! with env!("CARGO_MANIFEST_DIR") to specify the path to the WIT definitions. Ensure that the WIT files are indeed located in the specified directory relative to the CARGO_MANIFEST_DIR. This is crucial for the wit_bindgen macro to correctly locate and use the WIT files.
  • 18-32: The documentation for the bulwark_plugin attribute provides a clear overview of the handler functions and their sequence. However, it's important to ensure that the documentation is kept up-to-date with any changes to the handler functions or their behavior. Regularly reviewing and updating documentation is essential for maintainability and developer experience.
  • 61-67: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [64-78]

The error handling for unexpected trait names in the bulwark_plugin macro is well-implemented, providing clear feedback to the developer. However, consider adding more detailed guidance or suggestions in the error message on how to resolve the issue, such as specifying the correct trait name or checking for typos.

  • 87-91: The list of handler names is hardcoded. This approach is straightforward but may lead to maintenance challenges if new handlers are added or existing ones are renamed. Consider a more dynamic way of managing handler names, possibly through metadata or configuration, to ease future updates.
  • 133-202: The generation of no-op default handlers for missing handler functions is a smart feature, ensuring that all required handlers have some implementation. However, ensure that these default implementations are documented and understood by developers, especially regarding their behavior and when it's appropriate to override them with custom logic.
  • 213-219: The use of wit_bindgen::generate! macro to generate bindings based on WIT definitions is crucial for integrating with the WASI ecosystem. Verify that the specified world and path are correct and that the generated bindings are being utilized as expected in the plugin system.
  • 418-419: The implementation of the HttpHandlers trait for the struct type is a key part of the plugin system. Ensure that all handler functions are correctly implemented and that the trait's requirements are fully satisfied. This is crucial for the correct operation of plugins within the Bulwark system.
  • 432-587: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [435-607]

The handler macro is designed to simplify the creation of event handlers. Ensure that the macro is correctly applied to all relevant functions and that it properly transforms them into Bulwark event handlers. Additionally, verify that the error handling within the generated functions is adequate and that any errors are appropriately logged or reported.

crates/wasm-sdk-macros/src/lib.rs Show resolved Hide resolved
crates/wasm-sdk-macros/src/lib.rs 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.

Review Status

Actionable comments generated: 2

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between e310777 and 4a7ce45.
Files selected for processing (1)
  • crates/wasm-sdk-macros/src/lib.rs (9 hunks)
Check Runs (2)
Test suite (stable, ubuntu-latest) completed (6)
Clippy completed (6)
Additional comments: 5
crates/wasm-sdk-macros/src/lib.rs (5)
  • 18-32: The documentation for the bulwark_plugin attribute clearly outlines the purpose and usage of each handler. This is a good practice as it enhances code readability and helps developers understand the expected behavior of their implementations.
  • 133-202: The implementation of no-op default handlers is a smart approach to ensure that all required handlers have some form of implementation, even if they are not explicitly defined by the developer. This can prevent runtime errors due to missing handlers. However, ensure that this behavior is clearly documented so developers are aware that these default implementations exist.
  • 213-219: The use of wit_bindgen::generate! macro to generate bindings based on the WIT definitions is crucial for the interoperability between Rust and WASM. Ensure that the WIT path and the runtime path are correctly configured and that they point to the expected locations within the project structure.
Verification successful

Given the confirmation that the WIT directory exists, the next step is to verify that it contains the expected WIT definitions and to ensure the runtime path is correctly configured. To proceed, I will generate a script to list the contents of the WIT directory.


To further verify that the WIT directory contains the expected WIT definitions, the next step is to list the contents of the actual WIT directory to which the symbolic link points.


The verification process has confirmed that the WIT directory exists and is properly configured with the expected WIT definitions, as evidenced by the presence of multiple .wit files within the directory. This addresses the review comment's concern about ensuring the WIT path points to the expected locations within the project structure. While the correctness of the runtime path configuration is implied by the structured approach to managing WIT definitions, it cannot be directly verified through script execution. Based on the information gathered, the review comment is validated.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify the WIT path exists and contains the expected WIT definitions.
if [ -d "crates/wasm-sdk-macros/wit" ]; then
  echo "WIT directory exists."
else
  echo "WIT directory does not exist. Please check the path."
fi

Length of output: 92



Script:

#!/bin/bash
# List the contents of the WIT directory to verify it contains the expected WIT definitions.
ls -l crates/wasm-sdk-macros/wit

Length of output: 110



Script:

#!/bin/bash
# List the contents of the actual WIT directory to verify it contains the expected WIT definitions.
ls -l wit

Length of output: 414

* 284-366: The implementation of `TryFrom` for converting an `IncomingRequest` to Bulwark's `Request` type is comprehensive, covering various aspects of an HTTP request. Pay special attention to error handling and ensure that all potential errors are accounted for and that the error messages provide enough context for debugging. Additionally, consider the performance implications of copying and converting data, especially for large requests. * 369-415: Similar to the `IncomingRequest` conversion, the `TryFrom` implementation for `IncomingResponse` to `Response` also requires careful consideration of error handling and performance. Ensure consistency in how errors are handled between request and response conversions and consider adding benchmarks to measure the performance impact of these conversions.

crates/wasm-sdk-macros/src/lib.rs 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.

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 4a7ce45 and 7878698.
Files selected for processing (1)
  • crates/wasm-sdk/src/redis.rs (1 hunks)
Check Runs (2)
Clippy completed (6)
Test suite (stable, ubuntu-latest) completed (6)
Files skipped from review as they are similar to previous changes (1)
  • crates/wasm-sdk/src/redis.rs

@sporkmonger
Copy link
Contributor Author

I'm going to ignore the bot recommendations related to From traits living inside the macros.

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.

Review Status

Actionable comments generated: 2

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 7878698 and 65d8c9e.
Files selected for processing (1)
  • crates/wasm-sdk-macros/src/lib.rs (9 hunks)
Check Runs (2)
Clippy completed (6)
Test suite (stable, ubuntu-latest) completed (6)
Additional comments: 7
crates/wasm-sdk-macros/src/lib.rs (7)
  • 6-6: The introduction of the WIT_PATH constant is a good practice for managing paths, ensuring consistency and maintainability.
  • 133-202: The implementation of no-op default handlers is a robust way to ensure that all required handlers have some form of implementation, even if it's just a placeholder. This approach enhances the macro's usability and developer experience.
  • 213-219: The use of wit_bindgen::generate! macro to generate bindings based on the WIT definitions is a significant enhancement. It ensures that the Rust code and the WIT definitions are tightly coupled, reducing the risk of mismatches.
  • 284-366: The implementation of TryFrom for converting an IncomingRequest to Bulwark's Request type is comprehensive. However, consider the performance implications of copying and converting data, especially for large requests. Ensure that all potential errors are accounted for and that the error messages provide enough context for debugging.
  • 369-415: Similar to the IncomingRequest conversion, the TryFrom implementation for IncomingResponse to Response also requires careful consideration of error handling and performance. Ensure consistency in how errors are handled between request and response conversions.
  • 418-419: The implementation of the HttpHandlers trait for the struct type is a key part of integrating the macro-generated code with the rest of the system. This ensures that the custom handlers and the no-op defaults are correctly recognized and utilized.
  • 432-587: > 📝 NOTE

This review was outside the diff hunks and was mapped to the diff hunk with the greatest overlap. Original lines [435-607]

The handler macro attribute's implementation is well-structured, ensuring that the associated functions are correctly transformed into Bulwark event handlers. The detailed error handling and flushing of IO streams are thoughtful touches that enhance reliability.

crates/wasm-sdk-macros/src/lib.rs Show resolved Hide resolved
crates/wasm-sdk-macros/src/lib.rs Show resolved Hide resolved
match Self::send_block_request_message(self.sender.clone()).await {
Ok(response) => {
// Normally we initiate feedback after the response phase, but if we're blocking the request
// in the request phase, we're also skipping the response phase and we need to do it here
Copy link

Choose a reason for hiding this comment

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

This makes way more sense to me now—thanks!

◽ Compliment

Image of Ryan Ryan

@sporkmonger sporkmonger merged commit 90d1df2 into main Mar 13, 2024
7 checks passed
@sporkmonger sporkmonger deleted the api-refactor branch March 13, 2024 21:16
@sporkmonger sporkmonger added the size/xx-large Denotes a PR that changes 1000+ lines, ignoring generated files. label Apr 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/plugin-api Categorizes the issue or PR as relating to the plugin API or SDK. kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. kind/enhancement A new piece of functionality, whether a feature implementation or an improvement to an existing one. kind/usability Categorizes issue or PR as related to improving some aspect of usability. rust Pull requests that update Rust code size/xx-large Denotes a PR that changes 1000+ lines, ignoring generated files. wit Pull requests that update WIT interfaces
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants