oncall-engine/engine/apps/grafana_plugin/tests/test_gcom_api_client.py
Joey Orlando 2582a1b1dc
Refactor how RBAC enabled/disabled status is determined for Grafana Cloud stacks (#4279)
# What this PR does

In cloud we are currently (somewhat) improperly determining whether or
not a Grafana stack had the `accessControlOnCall` feature flag enabled.
At first things worked fine. We would enable this feature toggle via the
Grafana Admin UI, and then the OnCall backend would read this value from
GCOM's `GET /instance/<stack_id>` endpoint (via
`config.feature_toggles`), and everything worked as expected.

There was a recent change made in `grafana/deployment_tools` to set this
feature flag to True for all stacks. However, for some reason, the GCOM
endpoint above doesn't return the `accessControlOnCall` feature toggle
value in `config.feature_toggles` if it is set in this manner (it only
returns the value if it is set via the Grafana Admin UI).

So what we should instead be doing is such instead of asking GCOM for
this feature toggle, infer whether RBAC is enabled on the stack by doing
a `HEAD /api/access-control/users/permissions/search` (this endpoint _is
only_ available on a Grafana stack if `accessControlOnCall` is enabled).

**Few caveats to this ☝️**
1. we first have to make sure that the cloud stack is in an `active`
state (ie. not paused). This is because, no matter if the
`accessControlOnCall` is enabled or not, if the stack is in a `paused`
state it will ALWAYS return `HTTP 200` which can be misleading and lead
to bugs (this feels like a bug on the Grafana API, will follow up with
core grafana team)
2. Once we roll out this change we will effectively **actually** be
enabling RBAC for OnCall for all orgs. The Identity Access team would
prefer a progressive rollout, which is why I decided to introduce the
concept of
[`settings.CLOUD_RBAC_ROLLOUT_PERCENTAGE`](https://github.com/grafana/oncall/pull/4279/files#diff-3383aef931e41e44d95829ad971641eeb98fe001be2f5da92217446d300ea1b3R918)
(see also [`Organization.
should_be_considered_for_rbac_permissioning`](https://github.com/grafana/oncall/pull/4279/files#diff-2ca9917f4f56349be39545ee8abd459be5076295d02ca3a7ec545152fcddccdfR348-R362))

## Which issue(s) this PR closes

Related to https://github.com/grafana/identity-access-team/issues/667

## 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] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.
2024-05-14 16:30:16 +00:00

106 lines
3.1 KiB
Python

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
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 _ 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
@pytest.mark.parametrize(
"status, is_deleted",
[
("deleted", True),
("active", False),
("deleting", False),
("paused", False),
("archived", False),
("archiving", False),
("restoring", False),
("migrating", False),
("migrated", False),
("suspending", False),
("suspended", False),
("pending", False),
("starting", False),
("unknown", False),
],
)
def test_cleanup_organization_deleted(status, is_deleted):
client = GcomAPIClient("someToken")
with patch.object(GcomAPIClient, "api_get", return_value=({"items": [{"status": status}]}, None)):
assert client.is_stack_deleted("someStack") == is_deleted