From 8fc4ea6dd49925149e006ad053e55c2f61aea5c0 Mon Sep 17 00:00:00 2001 From: Quan Pham Date: Fri, 3 Oct 2025 12:11:31 -0400 Subject: [PATCH] Allow pushing user-allocation membership to Keycloak A Keycloak admin client has been added When `activate_allocation` is called, the user is added to a Keycloak group named after the project ID on the remote cluster. If the user does not already exist in Keycloak, the case is ignored for now Authentication to Keycloak is done via client credentials grant When `deactivate_allocation` is called, the user is removed from the Keycloak group Unit tests have been updated to remove dependancy on Keycloak A comment in `validate_allocations` has been updated to reflect the more restrictive validation behavior, where users on cluster projects will be removed if they are not part of the Coldfront allocation (rather than if they are not registered on Coldfront at all). This is relevant for functional tests for this new feature. --- .../workflows/test-functional-microshift.yaml | 4 + .../workflows/test-functional-microstack.yaml | 4 + ci/run_functional_tests_openshift.sh | 5 ++ ci/run_functional_tests_openstack.sh | 5 ++ ci/setup-keycloak.sh | 59 +++++++++++++ requirements.txt | 1 + src/coldfront_plugin_cloud/base.py | 36 ++++++-- src/coldfront_plugin_cloud/kc_client.py | 83 +++++++++++++++++++ .../commands/validate_allocations.py | 4 +- src/coldfront_plugin_cloud/openshift.py | 4 + src/coldfront_plugin_cloud/openstack.py | 2 + src/coldfront_plugin_cloud/tests/base.py | 17 ++-- .../functional/openshift/test_allocation.py | 9 +- .../functional/openstack/test_allocation.py | 15 +++- .../tests/unit/openshift/base.py | 2 + .../unit/test_calculate_quota_unit_hours.py | 22 ++--- .../unit/test_fetch_daily_billable_usage.py | 17 ++-- .../unit/test_migrate_field_of_science.py | 8 +- 18 files changed, 257 insertions(+), 40 deletions(-) create mode 100755 ci/setup-keycloak.sh create mode 100644 src/coldfront_plugin_cloud/kc_client.py diff --git a/.github/workflows/test-functional-microshift.yaml b/.github/workflows/test-functional-microshift.yaml index f3a8e32d..aae89ed2 100644 --- a/.github/workflows/test-functional-microshift.yaml +++ b/.github/workflows/test-functional-microshift.yaml @@ -36,6 +36,10 @@ jobs: run: | bash ./ci/setup-oc-client.sh + - name: Install Keycloak + run: | + bash ./ci/setup-keycloak.sh + - name: Install Microshift run: | ./ci/microshift.sh diff --git a/.github/workflows/test-functional-microstack.yaml b/.github/workflows/test-functional-microstack.yaml index 031b1e1d..1b48b1ee 100644 --- a/.github/workflows/test-functional-microstack.yaml +++ b/.github/workflows/test-functional-microstack.yaml @@ -21,6 +21,10 @@ jobs: with: python-version: 3.12 + - name: Install Keycloak + run: | + bash ./ci/setup-keycloak.sh + - name: Install dependencies, ColdFront and plugin run: | ./ci/setup.sh diff --git a/ci/run_functional_tests_openshift.sh b/ci/run_functional_tests_openshift.sh index 2aa9125d..ce2adfc3 100755 --- a/ci/run_functional_tests_openshift.sh +++ b/ci/run_functional_tests_openshift.sh @@ -5,6 +5,11 @@ # Tests expect the resource to be name Devstack set -xe +export KEYCLOAK_BASE_URL="http://localhost:8080" +export KEYCLOAK_REALM="master" +export KEYCLOAK_CLIENT_ID="coldfront" +export KEYCLOAK_CLIENT_SECRET="nomoresecret" + export OPENSHIFT_MICROSHIFT_TOKEN="$(oc create token -n onboarding onboarding-serviceaccount)" export OPENSHIFT_MICROSHIFT_VERIFY="false" diff --git a/ci/run_functional_tests_openstack.sh b/ci/run_functional_tests_openstack.sh index b6aa1c67..81ab53a9 100755 --- a/ci/run_functional_tests_openstack.sh +++ b/ci/run_functional_tests_openstack.sh @@ -5,6 +5,11 @@ # Tests expect the resource to be name Devstack set -xe +export KEYCLOAK_BASE_URL="http://localhost:8080" +export KEYCLOAK_REALM="master" +export KEYCLOAK_CLIENT_ID="coldfront" +export KEYCLOAK_CLIENT_SECRET="nomoresecret" + export CREDENTIAL_NAME=$(openssl rand -base64 12) export OPENSTACK_DEVSTACK_APPLICATION_CREDENTIAL_SECRET=$( diff --git a/ci/setup-keycloak.sh b/ci/setup-keycloak.sh new file mode 100755 index 00000000..7f8a39b5 --- /dev/null +++ b/ci/setup-keycloak.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +set -xe + +sudo docker run -d --name keycloak \ + -e KEYCLOAK_ADMIN=admin \ + -e KEYCLOAK_ADMIN_PASSWORD=nomoresecret \ + -p 8080:8080 \ + -p 8443:8443 \ + quay.io/keycloak/keycloak:25.0 start-dev + +# wait for keycloak to be ready +until curl -s http://localhost:8080/auth/realms/master; do + echo "Waiting for Keycloak to be ready..." + sleep 5 +done + +# Create client and add admin role to client's service account +ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" \ + -d "username=admin" \ + -d "password=nomoresecret" \ + -d "grant_type=password" \ + -d "client_id=admin-cli" \ + -d "scope=openid" \ +| jq -r '.access_token') + + +curl -X POST "http://localhost:8080/admin/realms/master/clients" \ + -H "Authorization: Bearer $ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "clientId": "coldfront", + "secret": "nomoresecret", + "redirectUris": ["http://localhost:8080/*"], + "serviceAccountsEnabled": true + }' + +COLDFRONT_CLIENT_ID=$(curl -X GET "http://localhost:8080/admin/realms/master/clients?clientId=coldfront" \ + -H "Authorization: Bearer $ACCESS_TOKEN" | jq -r '.[0].id') + + +COLDFRONT_SERVICE_ACCOUNT_ID=$(curl -X GET "http://localhost:8080/admin/realms/master/clients/$COLDFRONT_CLIENT_ID/service-account-user" \ + -H "Authorization: Bearer $ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ +| jq -r '.id') + +ADMIN_ROLE_ID=$(curl -X GET "http://localhost:8080/admin/realms/master/roles/admin" \ + -H "Authorization: Bearer $ACCESS_TOKEN" | jq -r '.id') + +# Add admin role to the service account user +curl -X POST "http://localhost:8080/admin/realms/master/users/$COLDFRONT_SERVICE_ACCOUNT_ID/role-mappings/realm" \ + -H "Authorization: Bearer $ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d '[ + { + "id": "'$ADMIN_ROLE_ID'", + "name": "admin" + } + ]' diff --git a/requirements.txt b/requirements.txt index b1e6c65b..3c9959d2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ python-novaclient python-neutronclient python-swiftclient pytz +requests diff --git a/src/coldfront_plugin_cloud/base.py b/src/coldfront_plugin_cloud/base.py index 2a8806b0..5789a644 100644 --- a/src/coldfront_plugin_cloud/base.py +++ b/src/coldfront_plugin_cloud/base.py @@ -2,13 +2,16 @@ import functools import json from typing import NamedTuple +import logging from coldfront.core.allocation import models as allocation_models from coldfront.core.resource import models as resource_models -from coldfront_plugin_cloud import attributes +from coldfront_plugin_cloud import attributes, kc_client from coldfront_plugin_cloud.models.quota_models import QuotaSpecs +logger = logging.getLogger(__name__) + class ResourceAllocator(abc.ABC): resource_type = "" @@ -45,6 +48,29 @@ def get_or_create_federated_user(self, username): user = self.create_federated_user(username) return user + def assign_role_on_user(self, username, project_id): + self.kc_admin_client.create_group(project_id) + if user_id := self.kc_admin_client.get_user_id(username): + group_id = self.kc_admin_client.get_group_id(project_id) + self.kc_admin_client.add_user_to_group(user_id, group_id) + else: + logger.warning( + f"User {username} not found in Keycloak, cannot add to group." + ) + + def remove_role_from_user(self, username, project_id): + if user_id := self.kc_admin_client.get_user_id(username): + group_id = self.kc_admin_client.get_group_id(project_id) + self.kc_admin_client.remove_user_from_group(user_id, group_id) + else: + logger.warning( + f"User {username} not found in Keycloak, cannot remove from group." + ) + + @functools.cached_property + def kc_admin_client(self): + return kc_client.KeyCloakAPIClient() + @functools.cached_property def auth_url(self): return self.resource.get_attribute(attributes.RESOURCE_AUTH_URL).rstrip("/") @@ -88,11 +114,3 @@ def create_federated_user(self, unique_id): @abc.abstractmethod def get_federated_user(self, unique_id): pass - - @abc.abstractmethod - def assign_role_on_user(self, username, project_id): - pass - - @abc.abstractmethod - def remove_role_from_user(self, username, project_id): - pass diff --git a/src/coldfront_plugin_cloud/kc_client.py b/src/coldfront_plugin_cloud/kc_client.py new file mode 100644 index 00000000..db876a31 --- /dev/null +++ b/src/coldfront_plugin_cloud/kc_client.py @@ -0,0 +1,83 @@ +import os +import functools + +import requests + + +class KeyCloakAPIClient: + def __init__(self): + self.base_url = os.getenv("KEYCLOAK_BASE_URL") + self.realm = os.getenv("KEYCLOAK_REALM") + self.client_id = os.getenv("KEYCLOAK_CLIENT_ID", "coldfront") + self.client_secret = os.getenv("KEYCLOAK_CLIENT_SECRET", "nomoresecret") + + self.token_url = ( + f"{self.base_url}/realms/{self.realm}/protocol/openid-connect/token" + ) + + @functools.cached_property + def api_client(self): + params = { + "grant_type": "client_credentials", + "client_id": self.client_id, + "client_secret": self.client_secret, + } + r = requests.post(self.token_url, data=params).json() + headers = { + "Authorization": ("Bearer %s" % r["access_token"]), + "Content-Type": "application/json", + } + session = requests.session() + session.headers.update(headers) + return session + + def create_group(self, group_name): + url = f"{self.base_url}/admin/realms/{self.realm}/groups" + payload = {"name": group_name} + response = self.api_client.post(url, json=payload) + + # If group already exists, ignore and move on + if response.status_code not in (201, 409): + response.raise_for_status() + + def create_user(self, cf_username): + """Helper function to create user in Keycloak, for testing purposes only""" + url = f"{self.base_url}/admin/realms/{self.realm}/users" + payload = { + "username": cf_username, + "enabled": True, + "email": cf_username, + } + r = self.api_client.post(url, json=payload) + r.raise_for_status() + + def get_group_id(self, group_name) -> str | None: + """Return None if group not found""" + query = f"search={group_name}&exact=true" + url = f"{self.base_url}/admin/realms/{self.realm}/groups?{query}" + r = self.api_client.get(url).json() + return r[0]["id"] if r else None + + def get_user_id(self, cf_username) -> str | None: + """Return None if user not found""" + # TODO (Quan): Confirm that Coldfront usernames map to Keycloak emails, not email, or something else? + query = f"username={cf_username}&exact=true" + url = f"{self.base_url}/admin/realms/{self.realm}/users?{query}" + r = self.api_client.get(url).json() + return r[0]["id"] if r else None + + def add_user_to_group(self, user_id, group_id): + url = f"{self.base_url}/admin/realms/{self.realm}/users/{user_id}/groups/{group_id}" + r = self.api_client.put(url) + r.raise_for_status() + + def remove_user_from_group(self, user_id, group_id): + url = f"{self.base_url}/admin/realms/{self.realm}/users/{user_id}/groups/{group_id}" + r = self.api_client.delete(url) + r.raise_for_status() + + def get_user_groups(self, user_id) -> list[str]: + url = f"{self.base_url}/admin/realms/{self.realm}/users/{user_id}/groups" + r = self.api_client.get(url) + r.raise_for_status() + return [group["name"] for group in r.json()] diff --git a/src/coldfront_plugin_cloud/management/commands/validate_allocations.py b/src/coldfront_plugin_cloud/management/commands/validate_allocations.py index 7515e279..da6c2863 100644 --- a/src/coldfront_plugin_cloud/management/commands/validate_allocations.py +++ b/src/coldfront_plugin_cloud/management/commands/validate_allocations.py @@ -49,7 +49,7 @@ def sync_users(project_id, allocation, allocator, apply): if apply: tasks.add_user_to_allocation(coldfront_user.pk) - # remove users that are in the resource but not in coldfront + # remove users that are in the resource but not in coldfront allocation users = set( [coldfront_user.user.username for coldfront_user in coldfront_users] ) @@ -57,7 +57,7 @@ def sync_users(project_id, allocation, allocator, apply): if allocation_user not in users: failed_validation = True logger.warning( - f"{allocation_user} exists in the resource {project_id} but not in coldfront" + f"{allocation_user} exists in the resource {project_id} but not in coldfront allocation" ) if apply: allocator.remove_role_from_user(allocation_user, project_id) diff --git a/src/coldfront_plugin_cloud/openshift.py b/src/coldfront_plugin_cloud/openshift.py index d6e1f605..754e3934 100644 --- a/src/coldfront_plugin_cloud/openshift.py +++ b/src/coldfront_plugin_cloud/openshift.py @@ -364,6 +364,8 @@ def assign_role_on_user(self, username, project_id): # Role already exists, ignore pass + super().assign_role_on_user(username, project_id) + def remove_role_from_user(self, username, project_id): """Remove a role from a user in a project using direct OpenShift API calls""" try: @@ -386,6 +388,8 @@ def remove_role_from_user(self, username, project_id): # Rolebinding doesn't exist, nothing to remove pass + super().remove_role_from_user(username, project_id) + def _create_project(self, project_name, project_id): pi_username = self.allocation.project.pi.username diff --git a/src/coldfront_plugin_cloud/openstack.py b/src/coldfront_plugin_cloud/openstack.py index 5b3bcfa2..856ee749 100644 --- a/src/coldfront_plugin_cloud/openstack.py +++ b/src/coldfront_plugin_cloud/openstack.py @@ -333,12 +333,14 @@ def assign_role_on_user(self, username, project_id): user = self.get_federated_user(username) self.identity.roles.grant(user=user["id"], project=project_id, role=role) + super().assign_role_on_user(username, project_id) def remove_role_from_user(self, username, project_id): role = self.identity.roles.find(name=self.member_role_name) if user := self.get_federated_user(username): self.identity.roles.revoke(user=user["id"], project=project_id, role=role) + super().remove_role_from_user(username, project_id) def create_default_network(self, project_id): neutron = neutronclient.Client(session=get_session_for_resource(self.resource)) diff --git a/src/coldfront_plugin_cloud/tests/base.py b/src/coldfront_plugin_cloud/tests/base.py index 7e1b7d68..66922c4c 100644 --- a/src/coldfront_plugin_cloud/tests/base.py +++ b/src/coldfront_plugin_cloud/tests/base.py @@ -24,6 +24,8 @@ from coldfront.core.field_of_science.models import FieldOfScience from django.core.management import call_command +from coldfront_plugin_cloud import kc_client + class TestBase(TestCase): def setUp(self) -> None: @@ -37,11 +39,7 @@ def setUp(self) -> None: # For testing we can validate allocations with this status AllocationStatusChoice.objects.get_or_create(name="Active (Needs Renewal)") - @staticmethod - def new_user(username=None) -> User: - username = username or f"{uuid.uuid4().hex}@example.com" - User.objects.create(username=username, email=username) - return User.objects.get(username=username) + self.kc_admin_client = kc_client.KeyCloakAPIClient() @staticmethod def new_esi_resource(name=None, auth_url=None) -> Resource: @@ -104,6 +102,15 @@ def new_openshift_resource( ) return Resource.objects.get(name=resource_name) + def new_user(self, username=None, add_to_keycloak=True) -> User: + username = username or f"{uuid.uuid4().hex}@example.com" + User.objects.create(username=username, email=username) + + if add_to_keycloak: + self.kc_admin_client.create_user(username) + + return User.objects.get(username=username) + def new_project(self, title=None, pi=None) -> Project: title = title or uuid.uuid4().hex pi = pi or self.new_user() diff --git a/src/coldfront_plugin_cloud/tests/functional/openshift/test_allocation.py b/src/coldfront_plugin_cloud/tests/functional/openshift/test_allocation.py index 50aae4ae..cec1dccb 100644 --- a/src/coldfront_plugin_cloud/tests/functional/openshift/test_allocation.py +++ b/src/coldfront_plugin_cloud/tests/functional/openshift/test_allocation.py @@ -1,7 +1,6 @@ import os import time import unittest -import uuid from coldfront_plugin_cloud import attributes, openshift, tasks, utils from coldfront_plugin_cloud.tests import base @@ -51,7 +50,13 @@ def test_new_allocation(self): allocator._get_role(user.username, project_id) + # Check Keycloak group and user membership + self.kc_admin_client.get_group_id(project_id) + user_id = self.kc_admin_client.get_user_id(user.username) + assert project_id in self.kc_admin_client.get_user_groups(user_id) + allocator.remove_role_from_user(user.username, project_id) + assert project_id not in self.kc_admin_client.get_user_groups(user_id) with self.assertRaises(openshift.NotFound): allocator._get_role(user.username, project_id) @@ -108,7 +113,7 @@ def test_add_remove_user(self): # directly add a user to openshift which should then be # deleted when validate_allocations is called - non_coldfront_user = uuid.uuid4().hex + non_coldfront_user = self.new_user(add_to_keycloak=True).username allocator.get_or_create_federated_user(non_coldfront_user) allocator.assign_role_on_user(non_coldfront_user, project_id) assert non_coldfront_user in allocator.get_users(project_id) diff --git a/src/coldfront_plugin_cloud/tests/functional/openstack/test_allocation.py b/src/coldfront_plugin_cloud/tests/functional/openstack/test_allocation.py index 9b6614e0..2b05f648 100644 --- a/src/coldfront_plugin_cloud/tests/functional/openstack/test_allocation.py +++ b/src/coldfront_plugin_cloud/tests/functional/openstack/test_allocation.py @@ -1,6 +1,5 @@ import os import unittest -import uuid import time from coldfront_plugin_cloud import attributes, openstack, tasks, utils @@ -58,6 +57,11 @@ def test_new_allocation(self): self.assertEqual(len(roles), 1) self.assertEqual(roles[0].role["id"], self.role_member.id) + # Check Keycloak group and user membership + self.kc_admin_client.get_group_id(project_id) + user_id = self.kc_admin_client.get_user_id(user.username) + assert project_id in self.kc_admin_client.get_user_groups(user_id) + # Check default network # Port build-up time is not instant time.sleep(5) @@ -309,6 +313,11 @@ def test_add_remove_user(self): tasks.add_user_to_allocation(allocation_user2.pk) + # Check Keycloak group and user membership + self.kc_admin_client.get_group_id(project_id) + user2_id = self.kc_admin_client.get_user_id(user2.username) + assert project_id in self.kc_admin_client.get_user_groups(user2_id) + openstack_user = allocator.get_federated_user(user2.username) openstack_user = self.identity.users.get(openstack_user["id"]) @@ -322,6 +331,8 @@ def test_add_remove_user(self): tasks.remove_user_from_allocation(allocation_user2.pk) + assert project_id not in self.kc_admin_client.get_user_groups(user2_id) + roles = self.identity.role_assignments.list( user=openstack_user.id, project=openstack_project.id ) @@ -338,7 +349,7 @@ def test_add_remove_user(self): # directly add a user to openstack which should then be # deleted when validate_allocations is called - non_coldfront_user = uuid.uuid4().hex + non_coldfront_user = self.new_user(add_to_keycloak=True).username allocator.get_or_create_federated_user(non_coldfront_user) allocator.assign_role_on_user(non_coldfront_user, project_id) assert non_coldfront_user in allocator.get_users(project_id) diff --git a/src/coldfront_plugin_cloud/tests/unit/openshift/base.py b/src/coldfront_plugin_cloud/tests/unit/openshift/base.py index f8ad60a5..1e8e922f 100644 --- a/src/coldfront_plugin_cloud/tests/unit/openshift/base.py +++ b/src/coldfront_plugin_cloud/tests/unit/openshift/base.py @@ -17,6 +17,8 @@ def __init__(self): self.apis = {} self.member_role_name = "admin" + self.kc_admin_client = mock.Mock() + class TestUnitOpenshiftBase(base.TestBase): def setUp(self) -> None: diff --git a/src/coldfront_plugin_cloud/tests/unit/test_calculate_quota_unit_hours.py b/src/coldfront_plugin_cloud/tests/unit/test_calculate_quota_unit_hours.py index dedacac6..88a2bc67 100644 --- a/src/coldfront_plugin_cloud/tests/unit/test_calculate_quota_unit_hours.py +++ b/src/coldfront_plugin_cloud/tests/unit/test_calculate_quota_unit_hours.py @@ -39,7 +39,7 @@ def test_new_allocation_quota(self, mock_load_outages): mock_load_outages.return_value = [] with freezegun.freeze_time("2020-03-15 00:01:00"): - user = self.new_user() + user = self.new_user(add_to_keycloak=False) project = self.new_project(pi=user) allocation = self.new_allocation(project, self.resource, 2) utils.set_attribute_on_allocation( @@ -104,7 +104,7 @@ def test_new_allocation_quota(self, mock_load_outages): def test_new_allocation_quota_expired(self): """Test that expiration doesn't affect invoicing.""" - user = self.new_user() + user = self.new_user(add_to_keycloak=False) project = self.new_project(pi=user) allocation = self.new_allocation(project, self.resource, 2) allocation.status = allocation_models.AllocationStatusChoice.objects.get( @@ -134,7 +134,7 @@ def test_new_allocation_quota_expired(self): def test_new_allocation_quota_denied(self): """Test a simple case of invoicing until a status change.""" - user = self.new_user() + user = self.new_user(add_to_keycloak=False) project = self.new_project(pi=user) allocation = self.new_allocation(project, self.resource, 2) @@ -161,7 +161,7 @@ def test_new_allocation_quota_denied(self): def test_new_allocation_quota_last_revoked(self): """Test that we correctly distinguish the last transition to an unbilled state.""" - user = self.new_user() + user = self.new_user(add_to_keycloak=False) project = self.new_project(pi=user) allocation = self.new_allocation(project, self.resource, 2) @@ -203,7 +203,7 @@ def test_new_allocation_quota_last_revoked(self): self.assertEqual(value, 144) def test_new_allocation_quota_new(self): - user = self.new_user() + user = self.new_user(add_to_keycloak=False) project = self.new_project(pi=user) allocation = self.new_allocation(project, self.resource, 2) @@ -218,7 +218,7 @@ def test_new_allocation_quota_new(self): self.assertEqual(value, 0) def test_new_allocation_quota_never_approved(self): - user = self.new_user() + user = self.new_user(add_to_keycloak=False) project = self.new_project(pi=user) allocation = self.new_allocation(project, self.resource, 2) @@ -237,7 +237,7 @@ def test_new_allocation_quota_never_approved(self): def test_change_request_decrease(self): """Test for when a change request decreases the quota""" - user = self.new_user() + user = self.new_user(add_to_keycloak=False) project = self.new_project(pi=user) allocation = self.new_allocation(project, self.resource, 2) @@ -280,7 +280,7 @@ def test_change_request_decrease(self): def test_change_request_increase(self): """Test for when a change request increases the quota""" - user = self.new_user() + user = self.new_user(add_to_keycloak=False) project = self.new_project(pi=user) allocation = self.new_allocation(project, self.resource, 2) @@ -323,7 +323,7 @@ def test_change_request_increase(self): def test_change_request_decrease_multiple(self): """Test for when multiple different change request decreases the quota""" - user = self.new_user() + user = self.new_user(add_to_keycloak=False) project = self.new_project(pi=user) allocation = self.new_allocation(project, self.resource, 2) @@ -383,7 +383,7 @@ def test_change_request_decrease_multiple(self): self.assertEqual(value, 48) def test_new_allocation_quota_change_request(self): - user = self.new_user() + user = self.new_user(add_to_keycloak=False) project = self.new_project(pi=user) allocation = self.new_allocation(project, self.resource, 2) @@ -606,7 +606,7 @@ def test_nerc_outages_integration(self, mock_rates_loader): with patch.object(utils, "_OUTAGES_DATA", mock_outages_data): with freezegun.freeze_time("2020-03-01"): - user = self.new_user() + user = self.new_user(add_to_keycloak=False) project = self.new_project(pi=user) resource = self.new_openstack_resource( name="TEST-RESOURCE", internal_name="test-service" diff --git a/src/coldfront_plugin_cloud/tests/unit/test_fetch_daily_billable_usage.py b/src/coldfront_plugin_cloud/tests/unit/test_fetch_daily_billable_usage.py index 477801ff..010890f8 100644 --- a/src/coldfront_plugin_cloud/tests/unit/test_fetch_daily_billable_usage.py +++ b/src/coldfront_plugin_cloud/tests/unit/test_fetch_daily_billable_usage.py @@ -93,8 +93,9 @@ def test_get_allocations_for_daily_billing(self): ) fakedev = self.new_openstack_resource(name="FakeDev", internal_name="FakeDev") - prod_project = self.new_project() - dev_project = self.new_project() + user = self.new_user(add_to_keycloak=False) + prod_project = self.new_project(pi=user) + dev_project = self.new_project(pi=user) prod_allocation_1 = self.new_allocation( project=prod_project, resource=fakeprod, quantity=1, status="Active" @@ -138,7 +139,9 @@ def test_call_command(self, mock_get_allocation_usage): fakeprod = self.new_openstack_resource( name="FakeProd", internal_name="FakeProd" ) - prod_project = self.new_project() + + user = self.new_user(add_to_keycloak=False) + prod_project = self.new_project(pi=user) allocation_1 = self.new_allocation( project=prod_project, resource=fakeprod, quantity=1, status="Active" ) @@ -216,15 +219,17 @@ def test_send_alert_email(self): fakeprod = self.new_openstack_resource( name="FakeProd", internal_name="FakeProd" ) - prod_project = self.new_project(title="FakeProject") + + user = self.new_user(add_to_keycloak=False) + prod_project = self.new_project(pi=user, title="FakeProject") allocation_1 = self.new_allocation( project=prod_project, resource=fakeprod, quantity=1, status="Active" ) - manager = self.new_user() + manager = self.new_user(add_to_keycloak=False) self.new_project_user(manager, prod_project, role="Manager") - normal_user = self.new_user() + normal_user = self.new_user(add_to_keycloak=False) self.new_project_user(normal_user, prod_project, role="User") with mock.patch("coldfront.core.utils.mail.send_email") as mock_send_email: diff --git a/src/coldfront_plugin_cloud/tests/unit/test_migrate_field_of_science.py b/src/coldfront_plugin_cloud/tests/unit/test_migrate_field_of_science.py index 7a7baabb..93d035fc 100644 --- a/src/coldfront_plugin_cloud/tests/unit/test_migrate_field_of_science.py +++ b/src/coldfront_plugin_cloud/tests/unit/test_migrate_field_of_science.py @@ -33,9 +33,11 @@ def test_command_output(self): new_fos_1_des = uuid.uuid4().hex # Migrate to new fos new_fos_2_des = old_fos_4.description # Migrate to existing fos - fake_project_1 = self.new_project() - fake_project_2 = self.new_project() - fake_project_3 = self.new_project() + # To avoid Keycloak dependency in unit test + fake_user = self.new_user(add_to_keycloak=False) + fake_project_1 = self.new_project(pi=fake_user) + fake_project_2 = self.new_project(pi=fake_user) + fake_project_3 = self.new_project(pi=fake_user) fake_project_1.field_of_science = old_fos_1 fake_project_2.field_of_science = old_fos_2 fake_project_3.field_of_science = old_fos_3