# 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.
106 lines
3.1 KiB
Python
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
|