Skip to content

fix(E3671): Set gp3 Iops limits per resource type - #4866

Merged
kddejong merged 2 commits into
aws-cloudformation:mainfrom
Thanatos9404:fix/e3671-gp3-iops-80000
Oct 26, 2026
Merged

kddejong merged 2 commits into
aws-cloudformation:mainfrom
Thanatos9404:fix/e3671-gp3-iops-80000

Conversation

@Thanatos9404

@Thanatos9404 Thanatos9404 commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Issue #, if available:

Fixes #4836

Description of changes:

E3671 capped gp3 Iops at 16000, which was the EBS limit until September 2025. EC2 now allows 80,000, so the template in the issue gets a false positive at 20000. After review, the limits depend on where the mapping sits:

  • AWS::EC2::Instance: E3671, gp3 maximum 80000.
  • AWS::AutoScaling::LaunchConfiguration: E3671 with its own schema, gp3 maximum 16000, since Auto Scaling still rejects anything higher.
  • AWS::EC2::LaunchTemplate and AWS::EC2::SpotFleet: a new warning, W3672, with the 80000 limits, because CloudFormation does not validate them at create time. Existing E3671 ignores on those resources will not cover W3672.
  • AWS::OpsWorks::Instance is dropped from the keyword list.

The 500 IOPS per GiB ratio is still not checked. The release schema export takes one schema per rule, so it still shows 80000 for launch configurations. Tests cover both E3671 schemas and the new warning; the full suite shows no new failures, and pre-commit passes on the changed files.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@kddejong

Copy link
Copy Markdown
Contributor

Thanks for this — the 80000 figure is right, and I confirmed it by deployment rather than from the docs: an AWS::EC2::Volume at Iops: 80001 fails with EC2's own Volume iops of 80001 is too high; maximum is 80000, and 16001 deploys cleanly. So #4836 is a real false positive.

The problem is that blockdevicemappings.json is shared by five keyworded paths, and the three AWS APIs behind them enforce different limits. Measured by deploying each shape, us-east-1, 2026-10-24:

Path gp3 ceiling ratio 500:1 Iops below 3000 validated at stack-create?
AWS::EC2::Volume 80000 enforced clamped up to 3000 yes
AWS::EC2::Instance 80000 enforced — yes
AWS::AutoScaling::LaunchConfiguration 16000 enforced rejected yes
AWS::EC2::LaunchTemplate none none none no
AWS::EC2::SpotFleet none none none no

So raising the shared maximum to 80000 fixes EC2::Instance and introduces a false negative on AutoScaling::LaunchConfiguration, which rejects anything above 16000:

GP3 volumes must have between 3000 and 16000 IOPS. (Service: AutoScaling, Status Code: 400)

That includes this PR's own new test case. Iops: 20000 in a LaunchConfiguration block device mapping is a CREATE_FAILED with the message above, so the assertion is correct for EC2::Instance and wrong for one of the other four paths the rule covers.

AWS::EC2::SpotFleet is the other end: it accepted Iops: 999999 and stored it verbatim in the fleet config. AWS::EC2::LaunchTemplate accepted 80001 and 2999. Neither validates anything at create, so an error-level finding there is asserting what some future RunInstances will do rather than what CloudFormation will reject.

What I think this needs, which is more than a one-number change:

  • maximum: 80000, E — EC2::Instance (and EC2::Volume, which has no Iops bound at all today)
  • maximum: 16000, E — AutoScaling::LaunchConfiguration
  • W — EC2::LaunchTemplate, EC2::SpotFleet
  • drop AWS::OpsWorks::Instance from the keyword list; the type is absent from the current schema set, so E3006 fires before E3671 is reached

Two schema files rather than one, because the numbers differ, with the keyword list split across them.

One more thing worth knowing before this lands: the current maximum: 16000 was incidentally catching ratio violations, since a breach of Iops <= 500 x VolumeSize on a small volume usually also breached 16000. At 80000 that accidental coverage goes away — a gp3 volume at Size: 1, Iops: 16000 is reported today only because of the ceiling, is silent with this change, and does not deploy. The ratio needs its own rule, which I have written up separately.

Happy to take the split myself if you would rather not expand the scope here. The core finding is yours and correct.

@Thanatos9404 Thanatos9404 changed the title fix(E3671): Allow gp3 Iops up to 80000 fix(E3671): Set gp3 Iops limits per resource type Oct 24, 2026
@Thanatos9404

Copy link
Copy Markdown
Contributor Author

Thanks for testing this against real deployments, that table made the split straightforward. Done in 98ab126: E3671 now covers AWS::EC2::Instance (gp3 up to 80000) and AWS::AutoScaling::LaunchConfiguration (its own schema, capped at 16000), LaunchTemplate and SpotFleet moved to a new warning, W3672, with the 80000 limits, and OpsWorks::Instance is gone from the keyword list. I left AWS::EC2::Volume out because its Iops sits at the top level and values below 3000 are clamped rather than rejected, so it needs its own schema; happy to add it here or in a follow-up, and to renumber W3672 if you'd prefer a different id.

@codecov

codecov Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.69%. Comparing base (f2e3c87) to head (861347b).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4866   +/-   ##
=======================================
  Coverage   94.68%   94.69%           
=======================================
  Files         439      440    +1     
  Lines       16016    16043   +27     
  Branches     3111     3113    +2     
=======================================
+ Hits        15165    15192   +27     
  Misses        468      468           
  Partials      383      383           
Flag Coverage Δ
unittests 94.68% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

EBS raised the gp3 IOPS maximum from 16,000 to 80,000 in September
2025, but the gp3 branch of the block device mapping extension schema
still capped Iops at 16000, so E3671 rejected valid values such as
20000. Raise the maximum to 80000. The io1 and io2 limits and the gp3
minimum are unchanged.
The block device mapping schema was shared by every resource type,
but the APIs behind them enforce different limits. Auto Scaling
launch configurations still reject gp3 Iops above 16000, so they now
use their own schema with that maximum, while EC2 instances keep the
80000 maximum. Launch templates and Spot Fleet launch specifications
are not validated when the stack is created, so their checks move to
a new warning, W3672. AWS::OpsWorks::Instance is dropped from the
keyword list because the type is no longer in the schema set.
@kddejong
kddejong force-pushed the fix/e3671-gp3-iops-80000 branch from 98ab126 to 861347b Compare October 26, 2026 16:21
@kddejong
kddejong merged commit 658d6f1 into aws-cloudformation:main Oct 26, 2026
22 of 23 checks passed
kddejong added a commit that referenced this pull request Oct 26, 2026
* fix(E3671): allow gp3 Iops from 100 on AWS::EC2::Instance

The gp3 branch of the EC2 instance block device mapping extension
applied a minimum of 3000 to Iops, but EC2 accepts any value from 100
upward on this path and clamps it up to 3000. Every value from 100 to
2999 was reported as an error on templates that deploy successfully.

Verified by deployment in us-east-1 on 2026-10-26, same template shape
with only Iops changed:

  Iops: 2999  CREATE_COMPLETE; DescribeVolumes reports the volume
              provisioned at 3000 IOPS, so EC2 clamped the value up.
  Iops: 50    ROLLBACK with "Volume iops of 50 is too low; minimum is
              100. (Service: Ec2, Status Code: 400)".

So the hard floor on this path is 100, matching the io1 and io2 branches
in the same file, which already use 100 and agree with the minimum the
service itself reports.

AWS::AutoScaling::LaunchConfiguration is left at 3000. That API
genuinely rejects lower values with "GP3 volumes must have between 3000
and 16000 IOPS.", so its own schema keeps the higher floor. The per-path
split that makes this possible landed in #4866.

Fixes #4929

* feat(W3673): warn when a gp3 Iops below 3000 is raised

Dropping the gp3 Iops minimum to 100 removed a false positive but left
the user with no signal at all. EC2 does not reject a gp3 Iops between
100 and 2999, it provisions the volume at the gp3 baseline of 3000, so
the stack deploys and the value in the template is not the value in
effect. Measured on live CloudFormation in us-east-1 on 2026-10-26: an
AWS::EC2::Instance with a gp3 Iops of 2999 reached CREATE_COMPLETE and
DescribeVolumes reported the volume at 3000 IOPS.

W3673 covers AWS::EC2::Instance only. AWS::AutoScaling::LaunchConfiguration
is deliberately excluded because it rejects the value outright with
"GP3 volumes must have between 3000 and 16000 IOPS.", which E3671 already
reports, and a warning beside that error would describe behaviour that
does not happen. AWS::OpsWorks::Instance is excluded because the type is
absent from the schemas, so E3006 fires first.

The warning stops at 100. Below that the volume is genuinely rejected
with "Volume iops of 50 is too low; minimum is 100." and E3671 reports
it, so one value never draws both a warning and an error.

A separate id rather than an extension of W3671: W3671 states that the
value is ignored, which is a different fact from the value being
adjusted, and its keyword list spans paths where no adjustment happens.

The docs/rules.md diff also picks up a stale E3688 description that
predates this change, because the file is generated.

* refactor(W3673): express the gp3 Iops warning as a schema extension

W3673 was imperative Python where a schema extension does the same job,
against the preference for the simplest rule type and out of step with
E3671 and W3672, which are both schema driven on the same path. The
condition moves to blockdevicemappings_gp3_iops.json as an if/then with
a negated range, and the rule keeps only the message.

This also fixes a fault in the imperative version. It matched
'VolumeType' case insensitively, so a value of 'GP3' drew the warning
on top of the E3030 enum error it already earns, which is the same
"warning beside an error about one value" problem that keeps this rule
off AWS::AutoScaling::LaunchConfiguration. 'VolumeType' is a case
sensitive enum, so the schema uses a plain const and 'GP3' now reports
E3030 alone.

A negated range needs an explicit type guard. Without "type": "number"
inside the "not", the inner schema is vacuously true for anything that
is not a number, the negation then fails, and the warning fires on an
unresolved Ref, GetAtt or Fn::If and on every non scalar. Removing the
guard fails four of the silence tests, which is what pins it.

Behaviour on resolvable values is unchanged: verified by real lint that
gp3 Iops of 100 and 2999 warn, 50 reports E3671 alone, 3000 and 80000
are silent, Auto Scaling still reports E3671 with no warning beside it,
and the good-fixture sweep is byte identical to a run with no rule.
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.

E3671: gp3 Iops above 16000 rejected; EBS gp3 limit is now 80,000

2 participants