Skip to content

Quota merge corrupts multiple restrictions when they use the same method #592

Description

@cmorasca

APIM-CLI version

1.14.15

API-Management version

7.7.20250530

Bug description

When an API has multiple quota restrictions configured for the same method, especially method: "*", updating one restriction through apim api import can incorrectly modify another restriction.

I reproduced the issue with both APIM CLI 1.14.13 and 1.14.15.

Environment

  • APIM CLI: 1.14.15
  • Java: 17.0.12
  • API Manager: 7.7.20250530
  • Quota: System Default Quota
  • API state: unpublished
  • quotaMode: default behavior

Initial quota

The API initially has two independent System Default Quota restrictions:

systemQuota:
  system: false
  restrictions:
  - method: "*"
    type: "throttle"
    config:
      period: "day"
      per: "1"
      messages: "10000"
  - method: "*"
    type: "throttlemb"
    config:
      period: "minute"
      per: "1"
      mb: "100"

This configuration was exported by APIM CLI from an API where the quotas had initially been configured manually in API Manager.

In API Manager this correctly represents:

10000 messages every 1 day
100 MB every 1 minute

Change

Only the message rate was changed from 10000 to 20000:

systemQuota:
  system: false
  restrictions:
  - method: "*"
    type: "throttle"
    config:
      period: "day"
      per: "1"
      messages: "20000"
  - method: "*"
    type: "throttlemb"
    config:
      period: "minute"
      per: "1"
      mb: "100"

Then the API was imported again:

apim api import -s <stage> -c api-config.yaml

Expected result

The existing restrictions should remain independent:

20000 messages every 1 day
100 MB every 1 minute

Only the messages value of the throttle restriction should be updated.

Actual result

After the import, the System Default Quota contains two Throttle MB restrictions:

100 MB every 1 minute
100 MB every 1 minute

The throttle restriction is lost.

Debug log

The debug log shows that the desired and actual quotas are correctly detected before the merge:

Desired:
[
  QuotaRestriction [method=*, type=throttle,
    config={period=day, per=1, messages=20000}],
  QuotaRestriction [method=*, type=throttlemb,
    config={period=minute, per=1, mb=100}]
]

Actual:
[
  QuotaRestriction [method=*, type=throttle,
    config={period=day, per=1, messages=10000}],
  QuotaRestriction [method=*, type=throttlemb,
    config={period=minute, per=1, mb=100}]
]

However, immediately after mergeRestriction(), APIM CLI produces:

Merged Quota:
[
  QuotaRestriction [method=*, type=throttlemb,
    config={period=minute, per=1, mb=100}],
  QuotaRestriction [method=*, type=throttlemb,
    config={period=minute, per=1, messages=20000, mb=100}]
]

The incorrect merged quota is then sent to API Manager with the quota PUT request.

This indicates that the problem happens inside APIM CLI before the update is sent to API Manager.

Suspected root cause

In APIQuotaManager.mergeRestriction(), the source code contains this comment:

// It's considered as the same restriction when quota type, method, period & per are equal

However, the actual matching condition only compares the method:

if (desiredRestriction.getMethod().equals(existingRestriction.getMethod()))

quotaApiMethodExists() also appears to determine whether a restriction exists using only method.

Therefore, when multiple restrictions have:

method: "*"

they are all considered to be the same restriction, regardless of:

type
period
per

In the example above, while processing the second desired restriction (throttlemb), the CLI matches it against the first existing restriction (throttle) simply because both use method: "*".

It then changes the existing restriction type from throttle to throttlemb and updates its configuration. This also explains why the debug output contains the invalid mixed configuration:

type=throttlemb
config={period=minute, per=1, messages=20000, mb=100}

Suggested behavior

Restrictions should be matched using a key that uniquely identifies the quota window, consistent with the existing source code comment, for example:

type + method + period + per

With that matching logic, these two restrictions would correctly be treated as different entries:

throttle   + * + day    + 1
throttlemb + * + minute + 1

The value being limited, messages or mb, could then be updated without affecting the other restriction.

Reproducibility

The issue has been reproduced with:

APIM CLI 1.14.13
APIM CLI 1.14.15

and is consistently reproducible when an API has multiple quota restrictions using the same method, particularly method: "*".

Steps to reproduce

Written in description.

Relevant log output

2026-09-11 12:54:16,419 [APIChangeState] DEBUG: Changed property: systemQuota [
  Desired: APIQuota [
    id=null,
    type=SYSTEM,
    restrictions=[
      QuotaRestriction [
        api=*,
        method=*,
        type=throttle,
        config={period=day, per=1, messages=20000}
      ],
      QuotaRestriction [
        api=*,
        method=*,
        type=throttlemb,
        config={period=minute, per=1, mb=100}
      ]
    ]
  ]
  vs
  Actual: APIQuota [
    id=null,
    type=SYSTEM,
    restrictions=[
      QuotaRestriction [
        api=<API_ID>,
        method=*,
        type=throttle,
        config={period=day, per=1, messages=10000}
      ],
      QuotaRestriction [
        api=<API_ID>,
        method=*,
        type=throttlemb,
        config={period=minute, per=1, mb=100}
      ]
    ]
  ]
]

2026-09-11 12:54:16,954 [APIQuotaManager] INFO : Updating System default quota for API: <API_NAME>

2026-09-11 12:54:16,954 [APIQuotaManager] DEBUG: System default-Restrictions:
  Desired: [
    QuotaRestriction [
      api=*,
      method=*,
      type=throttle,
      config={period=day, per=1, messages=20000}
    ],
    QuotaRestriction [
      api=*,
      method=*,
      type=throttlemb,
      config={period=minute, per=1, mb=100}
    ]
  ],
  Actual: [
    QuotaRestriction [
      api=<API_ID>,
      method=*,
      type=throttle,
      config={period=day, per=1, messages=10000}
    ],
    QuotaRestriction [
      api=<API_ID>,
      method=*,
      type=throttlemb,
      config={period=minute, per=1, mb=100}
    ]
  ]

2026-09-11 12:54:16,955 [APIQuotaManager] DEBUG: Current Default Quota:
  APIQuota [
    id=<SYSTEM_DEFAULT_QUOTA_ID>,
    type=API,
    restrictions=[
      QuotaRestriction [
        api=<API_ID>,
        method=*,
        type=throttle,
        config={period=day, per=1, messages=10000}
      ],
      QuotaRestriction [
        api=<API_ID>,
        method=*,
        type=throttlemb,
        config={period=minute, per=1, mb=100}
      ]
    ]
  ]

2026-09-11 12:54:16,956 [APIQuotaManager] DEBUG: Merged Quota: [
  QuotaRestriction [
    api=<API_ID>,
    method=*,
    type=throttlemb,
    config={period=minute, per=1, mb=100}
  ],
  QuotaRestriction [
    api=<API_ID>,
    method=*,
    type=throttlemb,
    config={period=minute, per=1, messages=20000, mb=100}
  ]
]

2026-09-11 12:54:16,973 [RestAPICall] DEBUG:
  Http verb: PUT
  URI: https://<API_MANAGER_HOST>/api/portal/v1.4/quotas/<SYSTEM_DEFAULT_QUOTA_ID>

2026-09-11 12:54:17,684 [UpdateExistingAPI] INFO :
  Successfully updated unpublished API: <API_NAME> <API_VERSION>

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions