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

feat: rate limiter + unit tests + readme #115

Merged
merged 11 commits into from
Oct 3, 2024
Merged

feat: rate limiter + unit tests + readme #115

merged 11 commits into from
Oct 3, 2024

Conversation

fforbeck
Copy link
Member

@fforbeck fforbeck commented Sep 26, 2024

This PR add tests for the Rate Limiter middleware, the main changes include:

  • Updated README with steps and configs to run it locally
    • Project Setup
    • Running Tests
  • Added Mocha, Chai and Sinon packages to run Unit Tests
  • Rate Limiter
    • Feature Flag (env variable)
    • Fixes
      • Rate Limiter middleware needs to be placed after the IPFS Parsed URL middleware, so we can access the CID
      • Refactored the `import {version} from './package.json' to be set via task and imported as ENV variable, so we can easily tweak that in the tests and don't run into import issues
    • Unit Tests
        1. Calls next handler if no auth token is provided and rate limit is not exceeded
        1. Throws an error if no auth token is provided and rate limit is exceeded
        1. Calls next handler if auth token is present but no token metadata is found and rate limit is not exceeded
        1. Throws an error if auth token is present but no token metadata is found and rate limit is exceeded
        1. Calls next handler if auth token is present and token metadata is invalid but rate limit is not exceeded
        1. Throws an error if auth token is present and token metadata is invalid and rate limit is exceeded
        1. Calls next handler if auth token is present and token metadata is valid
    • Update Github Action to execute Unit Tests

Add a new middleware that checks a rate limiting service and returns a 429 if the CID is over a rate limit.

This sketches out an API for the rate limiting and accounting services suggested in storacha/RFC#28

This is not ready to merge, but should probably be the starting point for this work once we all agree that this is the right shape.
introduce a KV namespace for caching token metadata - for now the "invalid" boolean is the most important thing, as that's how we decide whether a token is valid.

I thought we'd be able to serve tokened requests when in doubt, but I'm a bit worried about people generating random tokens if we go that route - sending a different random token with each request would theoretically allow them to make infinite unbilled requests.

instead I went with a pattern that will likely require us to warm the cache before a token-based request achieves hot-storage level performance - we cache auth token metadata in KV and wait for the accounting service to return it if we don't find it in the cache. Using a "Stale While Revalidate" pattern to update the cache will allow us to maintain performance and the ability to disable tokens.
@travis
Copy link
Member

travis commented Sep 27, 2024

Could you say more about why Jest is useful here? We've typically used node:test for this project, and Mocha (https://mochajs.org/) or AVA (https://github.com/avajs/ava) on other projects - I'm not entirely sure why we've shied away from Jest, but I am a bit hesitant to add yet another testing framework to the mix for our team. Could definitely be convinced - I've used Jest in the past and it's generally pretty good - but need a bit more convincing ;-p

@fforbeck
Copy link
Member Author

Could you say more about why Jest is useful here? We've typically used node:test for this project, and Mocha (https://mochajs.org/) or AVA (https://github.com/avajs/ava) on other projects - I'm not entirely sure why we've shied away from Jest, but I am a bit hesitant to add yet another testing framework to the mix for our team. Could definitely be convinced - I've used Jest in the past and it's generally pretty good - but need a bit more convincing ;-p

sure thing @travis

I don’t have super strong opinions on this either, but Jest does give you some nice built-in features like mocking, spies, and coverage reports right out of the box. With Mocha (or node:test), you’d need to add additional libraries like Sinon for mocks and nyc for coverage, which Jest handles natively.

That said, if we can use Mocha alongside node:test, it should work fine, even though Mocha isn't as feature-rich in those areas. Totally open to whichever approach the team prefers! 😊

@travis
Copy link
Member

travis commented Sep 27, 2024

ok cool - I lean toward Mocha, but tbh I'd like @alanshaw to weigh in since he's written a lot more of this code (like, infinitely more, as this is really my first freeway contribution as well!) - Alan any preferences/thoughts on testing frameworks here?

@fforbeck fforbeck force-pushed the feat/rate-limits-2 branch 4 times, most recently from b55ce9a to 1a31dee Compare September 30, 2024 16:38
@fforbeck fforbeck marked this pull request as ready for review September 30, 2024 16:41
@fforbeck fforbeck force-pushed the feat/rate-limits-2 branch 4 times, most recently from 35e6793 to 6ca4fee Compare September 30, 2024 17:12
@fforbeck fforbeck changed the title feat: rate limiter feat: rate limiter + unit tests + repo readme Sep 30, 2024
@fforbeck fforbeck changed the title feat: rate limiter + unit tests + repo readme feat: rate limiter unit tests + repo readme Sep 30, 2024
Copy link
Member

@travis travis 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 added a few comments, some of them blocking - I took https://conventionalcomments.org/ for a spin on this review, curious if you like it!

I probably won't Approve this PR in general since I've never contributed to Freeway myself - @alanshaw could you take a look at this when you get a chance?

package.json Outdated Show resolved Hide resolved
src/middleware.js Outdated Show resolved Hide resolved
src/bindings.d.ts Outdated Show resolved Hide resolved
src/bindings.d.ts Outdated Show resolved Hide resolved
src/bindings.d.ts Outdated Show resolved Hide resolved
src/bindings.d.ts Outdated Show resolved Hide resolved
wrangler.toml Show resolved Hide resolved
Copy link
Member

@alanshaw alanshaw left a comment

Choose a reason for hiding this comment

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

Love the docs updates 🙏 thank you.

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Show resolved Hide resolved
set-version-env.js Outdated Show resolved Hide resolved
src/middleware.js Outdated Show resolved Hide resolved
src/bindings.d.ts Outdated Show resolved Hide resolved
src/middleware.js Outdated Show resolved Hide resolved
src/middleware.js Outdated Show resolved Hide resolved
src/middleware.js Outdated Show resolved Hide resolved
src/middleware.js Outdated Show resolved Hide resolved
src/middleware.js Outdated Show resolved Hide resolved
@alanshaw
Copy link
Member

alanshaw commented Oct 1, 2024

Could you say more about why Jest is useful here? We've typically used node:test for this project, and Mocha (https://mochajs.org/) or AVA (https://github.com/avajs/ava) on other projects - I'm not entirely sure why we've shied away from Jest, but I am a bit hesitant to add yet another testing framework to the mix for our team. Could definitely be convinced - I've used Jest in the past and it's generally pretty good - but need a bit more convincing ;-p

sure thing @travis

I don’t have super strong opinions on this either, but Jest does give you some nice built-in features like mocking, spies, and coverage reports right out of the box. With Mocha (or node:test), you’d need to add additional libraries like Sinon for mocks and nyc for coverage, which Jest handles natively.

That said, if we can use Mocha alongside node:test, it should work fine, even though Mocha isn't as feature-rich in those areas. Totally open to whichever approach the team prefers! 😊

Slightly my bad here, I tried node --test because it was new, but turns out it isn't great so I'd welcome a switch to something else. We also don't use it in any other repo.

I'm mildly against having 2 testing frameworks in the same repo, but at the same time I don't want to waste time here - there's a bunch of tests written now using it so...meh. The point is more that testing exists at all - I don't mind which framework we use. People like consistency though (thinking about the team mostly here as I have a high tolerance for chaos), and using the same framework(s) everywhere allows us to work more quickly and without context switches. We should establish a single/set of frameworks we're comfortable using as a team. I realise that may have changed now. @travis this is something needed to be decided for team norms.

Just a couple of notes: 1) I though jest was legacy/unmaintained? 2) We don't use jest anywhere else.

README.md Outdated Show resolved Hide resolved
wrangler.toml Show resolved Hide resolved
README.md Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
.github/actions/test/action.yml Outdated Show resolved Hide resolved
src/middleware.js Outdated Show resolved Hide resolved
src/middleware.js Outdated Show resolved Hide resolved
src/bindings.d.ts Outdated Show resolved Hide resolved
src/middleware.js Outdated Show resolved Hide resolved
src/middleware.js Outdated Show resolved Hide resolved
@Peeja
Copy link
Member

Peeja commented Oct 1, 2024

FWIW, I'm highly in favor of Jest. It's not legacy; quite the opposite, AFAIK it's where the community primarily is right now. Granted I'm biased by being used to Jest, but I'd love to decide to (in the long run) move everything to Jest.

@fforbeck fforbeck requested review from travis, Peeja and alanshaw and removed request for travis October 1, 2024 19:42
src/handlers/rate-limiter.js Show resolved Hide resolved
if (cachedValue) {
return deserializeTokenMetadata(cachedValue)
} else {
const accounting = Accounting.create({ serviceURL: env.ACCOUNTING_SERVICE_URL })
Copy link
Member

Choose a reason for hiding this comment

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

Yes, understood but I'm not convinced it'll be a performance problem.

Related, I'd probably implement the cache as a transparent wrapper around the accounting service and pass the accounting service as a dependency to the rate limiting service.

i.e. something like:

const accounting = Accounting.create({ serviceURL: env.ACCOUNTING_SERVICE_URL })
const cachedAccounting = Accounting.withCache(accounting, env.AUTH_TOKEN_METADATA)
const rateLimiter = RateLimiter.create(cachedAccounting)

const status = await rateLimiter.check(cid, request)
if (status  === RATE_LIMIT_EXCEEDED.YES) {
  // ...

So then it should be easier to test the rate limiter, since the rate limiter is not importing a function that requires a KV and an accounting service. Also easier to test the caching logic in isolation.

...anyways...just a suggestion - non blocking.

@fforbeck
Copy link
Member Author

fforbeck commented Oct 2, 2024

@alanshaw I've applied pretty much all the suggestions, however, I left some of them to be done in different PRs to minimize the number of changes here.
TODO

@fforbeck fforbeck requested a review from alanshaw October 2, 2024 13:24
src/bindings.d.ts Show resolved Hide resolved
src/bindings.d.ts Show resolved Hide resolved
@fforbeck fforbeck changed the title feat: rate limiter unit tests + repo readme feat: rate limiter + unit tests + readme Oct 2, 2024
@fforbeck fforbeck merged commit 7bc4c6d into main Oct 3, 2024
1 check passed
@fforbeck fforbeck deleted the feat/rate-limits-2 branch October 3, 2024 10:43
hannahhoward pushed a commit that referenced this pull request Nov 9, 2024
🤖 I have created a release *beep* *boop*
---


##
[2.21.0](v2.20.2...v2.21.0)
(2024-11-06)


### Features

* **blob-fetcher:** use updated blob fetcher
([#124](#124))
([90bb605](90bb605))
* egress tracker middleware
([#120](#120))
([847829b](847829b))
* rate limiter + unit tests + readme
([#115](#115))
([7bc4c6d](7bc4c6d))


### Bug Fixes

* **test:** enable nodejs compat for miniflare
([#127](#127))
([0165521](0165521))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

4 participants