Paginate calls to get instances from gcom (#2669)

# What this PR does
GCOM now has many more instances returned than in the past. Paginate
these calls instead of getting all at once.

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
This commit is contained in:
Michael Derynck 2023-07-28 15:19:27 -06:00 committed by GitHub
parent ddd98e0c3f
commit f201fd2be2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 96 additions and 5 deletions

View file

@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Update the direct paging feature to page for acknowledged & silenced alert groups,
and show a warning for resolved alert groups by @vadimkerr ([#2639](https://github.com/grafana/oncall/pull/2639))
- Change calls to get instances from GCOM to paginate by @mderynck ([#2669](https://github.com/grafana/oncall/pull/2669))
- Update checking on-call users to use schedule final events ([#2651](https://github.com/grafana/oncall/pull/2651))
### Fixed

View file

@ -253,6 +253,7 @@ class GcomAPIClient(APIClient):
DELETED_INSTANCE_QUERY = "instances?status=deleted&includeDeleted=true"
STACK_STATUS_DELETED = "deleted"
STACK_STATUS_ACTIVE = "active"
PAGE_SIZE = 1000
def __init__(self, api_token: str) -> None:
super().__init__(settings.GRAFANA_COM_API_URL, api_token)
@ -315,8 +316,20 @@ class GcomAPIClient(APIClient):
return False
return self._feature_toggle_is_enabled(instance_info, "accessControlOnCall")
def get_instances(self, query: str):
return self.api_get(query)
def get_instances(self, query: str, page_size=None):
if not page_size:
page, _ = self.api_get(query)
yield page
else:
cursor = 0
while cursor is not None:
if query:
page_query = query + f"&cursor={cursor}&pageSize={page_size}"
else:
page_query = f"?cursor={cursor}&pageSize={page_size}"
page, _ = self.api_get(page_query)
yield page
cursor = page["nextCursor"]
def is_stack_deleted(self, stack_id: str) -> bool:
url = f"instances?includeDeleted=true&id={stack_id}"

View file

@ -101,12 +101,13 @@ def get_instance_ids(query: str) -> Tuple[Optional[set], bool]:
return None, False
client = GcomAPIClient(settings.GRAFANA_COM_API_TOKEN)
instances, status = client.get_instances(query)
instance_pages = client.get_instances(query, GcomAPIClient.PAGE_SIZE)
if not instances:
if not instance_pages:
return None, True
ids = set(i["id"] for i in instances["items"])
ids = set(i["id"] for page in instance_pages for i in page["items"])
return ids, True

View file

@ -1,8 +1,11 @@
import uuid
from unittest.mock import patch
import pytest
from apps.grafana_plugin.helpers.client import GcomAPIClient
from apps.grafana_plugin.helpers.gcom import get_instance_ids
from settings.base import CLOUD_LICENSE_NAME
class TestIsRbacEnabledForStack:
@ -82,3 +85,76 @@ class TestIsRbacEnabledForStack:
GcomAPIClient("someFakeApiToken")._feature_toggle_is_enabled(instance_info, self.TEST_FEATURE_TOGGLE)
== expected
)
def build_paged_responses(page_size, pages, total_items):
response = []
remaining = total_items
for i in range(pages):
if not page_size:
page_item_count = remaining
else:
page_item_count = min(page_size, remaining)
remaining -= page_size
items = []
for j in range(page_item_count):
items.append({"id": str(uuid.uuid4())})
next_cursor = None if i == pages - 1 else i * page_size
response.append(({"items": items, "nextCursor": next_cursor}, {}))
return response
@pytest.mark.parametrize(
"page_size, expected_pages, expected_items",
[
(None, 1, 0),
(None, 1, 5),
(10, 2, 20),
(10, 4, 33),
],
)
def test_get_instances_pagination(page_size, expected_pages, expected_items):
response = build_paged_responses(page_size, expected_pages, expected_items)
client = GcomAPIClient("someToken")
pages = []
items = 0
with patch(
"apps.grafana_plugin.helpers.client.APIClient.api_get",
side_effect=response,
):
instance_pages = client.get_instances("", page_size)
for page in instance_pages:
pages.append(page)
items += len(page.get("items", []))
assert len(pages) == expected_pages
assert items == expected_items
@pytest.mark.parametrize(
"query, expected_pages, expected_items",
[
(GcomAPIClient.ACTIVE_INSTANCE_QUERY, 1, 0),
("", 1, 543),
(GcomAPIClient.DELETED_INSTANCE_QUERY, 2, 2000),
("", 4, 3333),
],
)
def test_get_instance_ids_pagination(settings, query, expected_pages, expected_items):
settings.GRAFANA_COM_API_TOKEN = "someToken"
settings.LICENSE = CLOUD_LICENSE_NAME
response = build_paged_responses(GcomAPIClient.PAGE_SIZE, expected_pages, expected_items)
with patch(
"apps.grafana_plugin.helpers.client.APIClient.api_get",
side_effect=response,
):
instance_ids, status = get_instance_ids(query)
item_count = len(instance_ids)
assert status is True
assert item_count == expected_items
if item_count > 0:
assert type(next(iter(instance_ids))) is str