From 6a3b05c5b531eecd31a7d1a6ee0a3d33010721ea Mon Sep 17 00:00:00 2001 From: Quan Pham Date: Sun, 10 Mar 2024 16:32:27 -0400 Subject: [PATCH] Intergrated ESI, allowing users to allocate ESI Network quotas A new option `--esi` has been added to the `add_openstack_resource` command, allowing the creation of ESI resources in Coldfront. Allocations of ESI resources will be initialized with only a network quota of 1 floating IP and network by default. The implementation of this feature required subclassing of the `OpenstackResourceAllocator` to create an allocator class for ESI resources, changes to the various Coldfront commands, the addition of one Openstack quota attribute,a small modification to the CI and test files, and the addition of a test file for ESI allocations. --- README.md | 9 +- ci/run_functional_tests_openstack.sh | 4 + src/coldfront_plugin_cloud/attributes.py | 2 + src/coldfront_plugin_cloud/esi.py | 29 ++++ .../commands/add_openstack_resource.py | 41 ++++-- .../commands/register_cloud_attributes.py | 3 + .../commands/validate_allocations.py | 18 +-- src/coldfront_plugin_cloud/openstack.py | 82 +++++------ src/coldfront_plugin_cloud/tasks.py | 10 ++ src/coldfront_plugin_cloud/tests/base.py | 22 ++- .../tests/functional/esi/__init__.py | 0 .../tests/functional/esi/test_allocations.py | 128 ++++++++++++++++++ .../functional/openstack/test_allocation.py | 4 +- .../tests/unit/test_attribute_migration.py | 2 +- 14 files changed, 282 insertions(+), 72 deletions(-) create mode 100644 src/coldfront_plugin_cloud/esi.py create mode 100644 src/coldfront_plugin_cloud/tests/functional/esi/__init__.py create mode 100644 src/coldfront_plugin_cloud/tests/functional/esi/test_allocations.py diff --git a/README.md b/README.md index 162f68d7..a69f5bc9 100644 --- a/README.md +++ b/README.md @@ -78,12 +78,15 @@ dashboard or through the helper command: ```bash $ coldfront add_openstack_resource -usage: coldfront add_openstack_resource [-h] --name NAME --auth-url AUTH_URL [--users-domain USERS_DOMAIN] [--projects-domain PROJECTS_DOMAIN] --idp IDP - [--protocol PROTOCOL] [--role ROLE] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH] [--traceback] - [--no-color] [--force-color] +usage: coldfront add_openstack_resource [-h] --name NAME --auth-url AUTH_URL [--users-domain USERS_DOMAIN] [--projects-domain PROJECTS_DOMAIN] --idp IDP [--protocol PROTOCOL] [--role ROLE] + [--public-network PUBLIC_NETWORK] [--network-cidr NETWORK_CIDR] [--esi] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH] [--traceback] + [--no-color] [--force-color] [--skip-checks] coldfront add_openstack_resource: error: the following arguments are required: --name, --auth-url, --idp ``` +An Openstack resource can be specified as an ESI resource by setting the `--esi` command flag. +ESI resource allocations will only have quotas for network resources by default. + ### Configuring for OpenShift Note: OpenShift support requires deploying the [openshift-acct-mgt][] diff --git a/ci/run_functional_tests_openstack.sh b/ci/run_functional_tests_openstack.sh index 5c6b8a6d..dbfaabd9 100755 --- a/ci/run_functional_tests_openstack.sh +++ b/ci/run_functional_tests_openstack.sh @@ -13,6 +13,9 @@ export OPENSTACK_DEVSTACK_APPLICATION_CREDENTIAL_SECRET=$( export OPENSTACK_DEVSTACK_APPLICATION_CREDENTIAL_ID=$( openstack application credential show "$credential_name" -f value -c id) +export OPENSTACK_ESI_APPLICATION_CREDENTIAL_SECRET=$OPENSTACK_DEVSTACK_APPLICATION_CREDENTIAL_SECRET +export OPENSTACK_ESI_APPLICATION_CREDENTIAL_ID=$OPENSTACK_DEVSTACK_APPLICATION_CREDENTIAL_ID + export OPENSTACK_PUBLIC_NETWORK_ID=$(openstack network show public -f value -c id) if [[ ! "${CI}" == "true" ]]; then @@ -28,6 +31,7 @@ export KEYCLOAK_PASS="nomoresecret" export KEYCLOAK_REALM="master" coverage run --source="." -m django test coldfront_plugin_cloud.tests.functional.openstack +coverage run --source="." -m django test coldfront_plugin_cloud.tests.functional.esi coverage report openstack application credential delete $OPENSTACK_DEVSTACK_APPLICATION_CREDENTIAL_ID diff --git a/src/coldfront_plugin_cloud/attributes.py b/src/coldfront_plugin_cloud/attributes.py index 9f1b8279..d10d2617 100644 --- a/src/coldfront_plugin_cloud/attributes.py +++ b/src/coldfront_plugin_cloud/attributes.py @@ -75,6 +75,7 @@ class CloudAllocationAttribute: QUOTA_VOLUMES_GB = 'OpenStack Volume Quota (GiB)' QUOTA_FLOATING_IPS = 'OpenStack Floating IP Quota' +QUOTA_NETWORKS = 'Openstack Network Quota' QUOTA_OBJECT_GB = 'OpenStack Swift Quota (GiB)' @@ -96,6 +97,7 @@ class CloudAllocationAttribute: CloudAllocationAttribute(name=QUOTA_VCPU), CloudAllocationAttribute(name=QUOTA_VOLUMES), CloudAllocationAttribute(name=QUOTA_VOLUMES_GB), + CloudAllocationAttribute(name=QUOTA_NETWORKS), CloudAllocationAttribute(name=QUOTA_FLOATING_IPS), CloudAllocationAttribute(name=QUOTA_OBJECT_GB), CloudAllocationAttribute(name=QUOTA_GPU), diff --git a/src/coldfront_plugin_cloud/esi.py b/src/coldfront_plugin_cloud/esi.py new file mode 100644 index 00000000..b14fcf80 --- /dev/null +++ b/src/coldfront_plugin_cloud/esi.py @@ -0,0 +1,29 @@ +import logging +import functools +import os + +from keystoneauth1.identity import v3 +from keystoneauth1 import session + +from coldfront_plugin_cloud import attributes, utils +from coldfront_plugin_cloud.openstack import OpenStackResourceAllocator + +class ESIResourceAllocator(OpenStackResourceAllocator): + + QUOTA_KEY_MAPPING = { + 'network': { + 'keys': { + attributes.QUOTA_FLOATING_IPS: 'floatingip', + attributes.QUOTA_NETWORKS: 'network' + } + } + } + + QUOTA_KEY_MAPPING_ALL_KEYS = {quota_key: quota_name for k in QUOTA_KEY_MAPPING.values() for quota_key, quota_name in k['keys'].items()} + + resource_type = 'esi' + + def get_quota(self, project_id): + quotas = dict() + quotas = self._get_network_quota(quotas, project_id) + return quotas diff --git a/src/coldfront_plugin_cloud/management/commands/add_openstack_resource.py b/src/coldfront_plugin_cloud/management/commands/add_openstack_resource.py index 57f2f25e..3b9124a2 100644 --- a/src/coldfront_plugin_cloud/management/commands/add_openstack_resource.py +++ b/src/coldfront_plugin_cloud/management/commands/add_openstack_resource.py @@ -34,13 +34,23 @@ def add_arguments(self, parser): parser.add_argument('--network-cidr', type=str, default='192.168.0.0/24', help='CIDR for default networks. ' 'Ignored if no --public-network.') + parser.add_argument('--esi', action='store_true', + help='Indicates this is an ESI resource (default: False)') def handle(self, *args, **options): + + if options['esi']: + resource_description = 'ESI Bare Metal environment' + resource_type = 'ESI' + else: + resource_description = 'OpenStack cloud environment' + resource_type = 'OpenStack' + openstack, _ = Resource.objects.get_or_create( - resource_type=ResourceType.objects.get(name='OpenStack'), + resource_type=ResourceType.objects.get(name=resource_type), parent_resource=None, name=options['name'], - description='OpenStack cloud environment', + description=resource_description, is_available=True, is_public=True, is_allocatable=True @@ -82,18 +92,21 @@ def handle(self, *args, **options): resource=openstack, value=options['role'] ) - ResourceAttribute.objects.get_or_create( - resource_attribute_type=ResourceAttributeType.objects.get( - name='quantity_label'), - resource=openstack, - value='Units of computing to allocate to the project. 1 Unit = 1 Instance, 2 vCPU, 0 GPU, 4G RAM, 2 Volumes, 100 GB Volume Storage, and 1 GB Object Storage' - ) - ResourceAttribute.objects.get_or_create( - resource_attribute_type=ResourceAttributeType.objects.get( - name='quantity_default_value'), - resource=openstack, - value=1 - ) + + # Quantity values do not make sense for an ESI allocation + if not options['esi']: + ResourceAttribute.objects.get_or_create( + resource_attribute_type=ResourceAttributeType.objects.get( + name='quantity_label'), + resource=openstack, + value='Units of computing to allocate to the project. 1 Unit = 1 Instance, 2 vCPU, 0 GPU, 4G RAM, 2 Volumes, 100 GB Volume Storage, and 1 GB Object Storage' + ) + ResourceAttribute.objects.get_or_create( + resource_attribute_type=ResourceAttributeType.objects.get( + name='quantity_default_value'), + resource=openstack, + value=1 + ) if options['public_network']: ResourceAttribute.objects.get_or_create( diff --git a/src/coldfront_plugin_cloud/management/commands/register_cloud_attributes.py b/src/coldfront_plugin_cloud/management/commands/register_cloud_attributes.py index 1cf52435..1c50c045 100644 --- a/src/coldfront_plugin_cloud/management/commands/register_cloud_attributes.py +++ b/src/coldfront_plugin_cloud/management/commands/register_cloud_attributes.py @@ -126,6 +126,9 @@ def register_resource_type(self): resource_models.ResourceType.objects.get_or_create( name='OpenShift', description='OpenShift Cloud' ) + resource_models.ResourceType.objects.get_or_create( + name='ESI', description='ESI Bare Metal Cloud' + ) def handle(self, *args, **options): self.register_resource_type() diff --git a/src/coldfront_plugin_cloud/management/commands/validate_allocations.py b/src/coldfront_plugin_cloud/management/commands/validate_allocations.py index ff838d01..ca9d5379 100644 --- a/src/coldfront_plugin_cloud/management/commands/validate_allocations.py +++ b/src/coldfront_plugin_cloud/management/commands/validate_allocations.py @@ -4,6 +4,7 @@ from coldfront_plugin_cloud import attributes from coldfront_plugin_cloud import openstack from coldfront_plugin_cloud import openshift +from coldfront_plugin_cloud import esi from coldfront_plugin_cloud import utils from coldfront_plugin_cloud import tasks @@ -66,11 +67,9 @@ def check_institution_specific_code(self, allocation, apply): def handle(self, *args, **options): - # Openstack Resources first + # Deal with Openstack and ESI resources openstack_resources = Resource.objects.filter( - resource_type=ResourceType.objects.get( - name='OpenStack' - ) + resource_type__name__in=['OpenStack', 'ESI'] ) openstack_allocations = Allocation.objects.filter( resources__in=openstack_resources, @@ -84,10 +83,7 @@ def handle(self, *args, **options): failed_validation = False - allocator = openstack.OpenStackResourceAllocator( - allocation.resources.first(), - allocation - ) + allocator = tasks.find_allocator(allocation) project_id = allocation.get_attribute(attributes.ALLOCATION_PROJECT_ID) if not project_id: @@ -105,11 +101,11 @@ def handle(self, *args, **options): failed_validation = Command.sync_users(project_id, allocation, allocator, options["apply"]) - obj_key = openstack.QUOTA_KEY_MAPPING['object']['keys'][attributes.QUOTA_OBJECT_GB] + obj_key = openstack.OpenStackResourceAllocator.QUOTA_KEY_MAPPING['object']['keys'][attributes.QUOTA_OBJECT_GB] for attr in attributes.ALLOCATION_QUOTA_ATTRIBUTES: if 'OpenStack' in attr.name: - key = openstack.QUOTA_KEY_MAPPING_ALL_KEYS.get(attr.name, None) + key = allocator.QUOTA_KEY_MAPPING_ALL_KEYS.get(attr.name, None) if not key: # Note(knikolla): Some attributes are only maintained # for bookkeeping purposes and do not have a @@ -147,7 +143,7 @@ def handle(self, *args, **options): allocation.get_attribute(attributes.ALLOCATION_PROJECT_ID) ) except Exception as e: - logger.error(f'setting openstack quota failed: {e}') + logger.error(f'setting {allocation.resources.first()} quota failed: {e}') continue logger.warning(f'Quota for allocation {allocation_str} was out of date. Reapplied!') diff --git a/src/coldfront_plugin_cloud/openstack.py b/src/coldfront_plugin_cloud/openstack.py index 0211ce40..79ff890d 100644 --- a/src/coldfront_plugin_cloud/openstack.py +++ b/src/coldfront_plugin_cloud/openstack.py @@ -21,40 +21,8 @@ # 1 GB = 1 000 000 000 B = 10^9 B GB_IN_BYTES = 2 ** 30 -# Map the attribute name in ColdFront, to the client of the respective -# service, the version of the API, and the key in the payload. -QUOTA_KEY_MAPPING = { - 'compute': { - 'keys': { - attributes.QUOTA_INSTANCES: 'instances', - attributes.QUOTA_VCPU: 'cores', - attributes.QUOTA_RAM: 'ram', - }, - }, - 'network': { - 'keys': { - attributes.QUOTA_FLOATING_IPS: 'floatingip', - } - }, - 'object': { - 'keys': { - attributes.QUOTA_OBJECT_GB: 'x-account-meta-quota-bytes', - } - }, - 'volume': { - 'keys': { - attributes.QUOTA_VOLUMES: 'volumes', - attributes.QUOTA_VOLUMES_GB: 'gigabytes', - } - }, -} - COLDFRONT_RGW_SWIFT_INIT_USER = 'coldfront-swift-init' -QUOTA_KEY_MAPPING_ALL_KEYS = dict() -for service in QUOTA_KEY_MAPPING.keys(): - QUOTA_KEY_MAPPING_ALL_KEYS.update(QUOTA_KEY_MAPPING[service]['keys']) - def get_session_for_resource_via_password(resource, username, password, project_id): auth_url = resource.get_attribute(attributes.RESOURCE_AUTH_URL) user_domain = resource.get_attribute(attributes.RESOURCE_USER_DOMAIN) @@ -94,6 +62,35 @@ def get_session_for_resource(resource): class OpenStackResourceAllocator(base.ResourceAllocator): + # Map the attribute name in ColdFront, to the client of the respective + # service, the version of the API, and the key in the payload. + QUOTA_KEY_MAPPING = { + 'compute': { + 'keys': { + attributes.QUOTA_INSTANCES: 'instances', + attributes.QUOTA_VCPU: 'cores', + attributes.QUOTA_RAM: 'ram', + }, + }, + 'network': { + 'keys': { + attributes.QUOTA_FLOATING_IPS: 'floatingip', + } + }, + 'object': { + 'keys': { + attributes.QUOTA_OBJECT_GB: 'x-account-meta-quota-bytes', + } + }, + 'volume': { + 'keys': { + attributes.QUOTA_VOLUMES: 'volumes', + attributes.QUOTA_VOLUMES_GB: 'gigabytes', + } + }, + } + + QUOTA_KEY_MAPPING_ALL_KEYS = {quota_key: quota_name for k in QUOTA_KEY_MAPPING.values() for quota_key, quota_name in k['keys'].items()} resource_type = 'openstack' @@ -161,7 +158,7 @@ def set_quota(self, project_id): # If an attribute with the appropriate name is associated with an # allocation, set that as the quota. Otherwise, multiply # the quantity attribute via the mapping table above. - for service_name, service in QUOTA_KEY_MAPPING.items(): + for service_name, service in self.QUOTA_KEY_MAPPING.items(): # No need to do any calculations here, just go through each service # and set the value in the attribute. payload = dict() @@ -188,7 +185,7 @@ def _set_object_quota(self, project_id, payload): # Note(knikolla): For consistency with other OpenStack # quotas we're storing this as GB on the attribute and # converting to bytes for Swift. - obj_q_mapping = QUOTA_KEY_MAPPING['object']['keys'][ + obj_q_mapping = self.QUOTA_KEY_MAPPING['object']['keys'][ attributes.QUOTA_OBJECT_GB ] payload[obj_q_mapping] *= GB_IN_BYTES @@ -235,22 +232,27 @@ def _init_rgw_for_project(self, project_id): logger.debug(f'rgw swift stat for {project_id}:\n{stat}') self.remove_role_from_user(COLDFRONT_RGW_SWIFT_INIT_USER, project_id) + def _get_network_quota(self, quotas, project_id): + network_quota = self.network.show_quota(project_id)['quota'] + for k in self.QUOTA_KEY_MAPPING['network']['keys'].values(): + quotas[k] = network_quota.get(k) + + return quotas + def get_quota(self, project_id): quotas = dict() compute_quota = self.compute.quotas.get(project_id) - for k in QUOTA_KEY_MAPPING['compute']['keys'].values(): + for k in self.QUOTA_KEY_MAPPING['compute']['keys'].values(): quotas[k] = compute_quota.__getattr__(k) volume_quota = self.volume.quotas.get(project_id) - for k in QUOTA_KEY_MAPPING['volume']['keys'].values(): + for k in self.QUOTA_KEY_MAPPING['volume']['keys'].values(): quotas[k] = volume_quota.__getattr__(k) - network_quota = self.network.show_quota(project_id)['quota'] - for k in QUOTA_KEY_MAPPING['network']['keys'].values(): - quotas[k] = network_quota.get(k) + quotas = self._get_network_quota(quotas, project_id) - key = QUOTA_KEY_MAPPING['object']['keys'][attributes.QUOTA_OBJECT_GB] + key = self.QUOTA_KEY_MAPPING['object']['keys'][attributes.QUOTA_OBJECT_GB] try: swift = self.object(project_id).head_account() quotas[key] = int(int(swift.get(key)) / GB_IN_BYTES) diff --git a/src/coldfront_plugin_cloud/tasks.py b/src/coldfront_plugin_cloud/tasks.py index 4a3c0eb8..32c8f7b6 100644 --- a/src/coldfront_plugin_cloud/tasks.py +++ b/src/coldfront_plugin_cloud/tasks.py @@ -10,6 +10,7 @@ base, openstack, openshift, + esi, utils) logger = logging.getLogger(__name__) @@ -35,6 +36,10 @@ attributes.QUOTA_REQUESTS_STORAGE: 20, attributes.QUOTA_REQUESTS_GPU: 0, attributes.QUOTA_PVC: 2 + }, + 'esi': { + attributes.QUOTA_FLOATING_IPS: 0, + attributes.QUOTA_NETWORKS: 0 } } @@ -48,6 +53,10 @@ }, 'openshift': { attributes.QUOTA_REQUESTS_GPU: 0, + }, + 'esi': { + attributes.QUOTA_FLOATING_IPS: 1, + attributes.QUOTA_NETWORKS: 1 } } @@ -56,6 +65,7 @@ def find_allocator(allocation) -> base.ResourceAllocator: allocators = { 'openstack': openstack.OpenStackResourceAllocator, 'openshift': openshift.OpenShiftResourceAllocator, + 'esi': esi.ESIResourceAllocator, } # TODO(knikolla): It doesn't seem to be possible to select multiple resources # when requesting a new allocation, so why is this multivalued? diff --git a/src/coldfront_plugin_cloud/tests/base.py b/src/coldfront_plugin_cloud/tests/base.py index e078a814..f2f2a35b 100644 --- a/src/coldfront_plugin_cloud/tests/base.py +++ b/src/coldfront_plugin_cloud/tests/base.py @@ -41,9 +41,28 @@ 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) + + @staticmethod + def new_esi_resource(name=None, auth_url=None) -> Resource: + resource_name = name or uuid.uuid4().hex + + call_command( + 'add_openstack_resource', + name=resource_name, + auth_url=auth_url or f'https://{resource_name}/identity/v3', + projects_domain='default', + users_domain='default', + idp='sso', + protocol='openid', + role='member', + public_network=os.getenv('OPENSTACK_PUBLIC_NETWORK_ID'), + network_cidr='192.168.0.0/24', + esi=True + ) + return Resource.objects.get(name=resource_name) @staticmethod - def new_resource(name=None, auth_url=None) -> Resource: + def new_openstack_resource(name=None, auth_url=None) -> Resource: resource_name = name or uuid.uuid4().hex call_command( @@ -57,6 +76,7 @@ def new_resource(name=None, auth_url=None) -> Resource: role='member', public_network=os.getenv('OPENSTACK_PUBLIC_NETWORK_ID'), network_cidr='192.168.0.0/24', + esi=False ) return Resource.objects.get(name=resource_name) diff --git a/src/coldfront_plugin_cloud/tests/functional/esi/__init__.py b/src/coldfront_plugin_cloud/tests/functional/esi/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/coldfront_plugin_cloud/tests/functional/esi/test_allocations.py b/src/coldfront_plugin_cloud/tests/functional/esi/test_allocations.py new file mode 100644 index 00000000..73910b41 --- /dev/null +++ b/src/coldfront_plugin_cloud/tests/functional/esi/test_allocations.py @@ -0,0 +1,128 @@ +import os +import unittest +import uuid +import time + +from coldfront_plugin_cloud import attributes, openstack, esi, tasks +from coldfront_plugin_cloud.tests import base + +from django.core.management import call_command +from keystoneclient.v3 import client +from cinderclient import client as cinderclient +from neutronclient.v2_0 import client as neutronclient +from novaclient import client as novaclient + +@unittest.skipUnless(os.getenv('FUNCTIONAL_TESTS'), 'Functional tests not enabled.') +class TestAllocation(base.TestBase): + + def setUp(self) -> None: + super().setUp() + self.resource = self.new_esi_resource(name='ESI', + auth_url=os.getenv('OS_AUTH_URL')) + self.session = openstack.get_session_for_resource(self.resource) + self.identity = client.Client(session=self.session) + self.compute = novaclient.Client(session=self.session, version=2) + self.volume = cinderclient.Client(session=self.session, version=3) + self.networking = neutronclient.Client(session=self.session) + self.role_member = self.identity.roles.find(name='member') + + def test_new_ESI_allocation(self): + user = self.new_user() + project = self.new_project(pi=user) + allocation = self.new_allocation(project, self.resource, 1) + allocator = esi.ESIResourceAllocator(self.resource, + allocation) + + tasks.activate_allocation(allocation.pk) + allocation.refresh_from_db() + + # Check project + project_id = allocation.get_attribute(attributes.ALLOCATION_PROJECT_ID) + self.assertIsNotNone(project_id) + self.assertIsNotNone(allocation.get_attribute(attributes.ALLOCATION_PROJECT_NAME)) + + openstack_project = self.identity.projects.get(project_id) + self.assertTrue(openstack_project.enabled) + + # Check user and roles + openstack_user = allocator.get_federated_user(user.username) + openstack_user = self.identity.users.get(openstack_user['id']) + + roles = self.identity.role_assignments.list(user=openstack_user.id, + project=openstack_project.id) + + self.assertEqual(len(roles), 1) + self.assertEqual(roles[0].role['id'], self.role_member.id) + + # Check default network + time.sleep(5) + network = self.networking.list_networks( + project_id=project_id, name='default_network')['networks'][0] + router = self.networking.list_routers( + project_id=project_id, name='default_router')['routers'][0] + ports = self.networking.list_ports(project_id=project_id, + network_id=network['id'], + device_id=router['id'])['ports'] + self.assertIsNotNone(ports) + self.assertEqual(ports[0]['status'], 'ACTIVE') + + # Validate get_quota + expected_quota = { + 'floatingip': 1, + 'network': 1, + } + resulting_quota = allocator.get_quota(openstack_project.id) + self.assertEqual(expected_quota, resulting_quota) + + def test_add_remove_user(self): + user = self.new_user() + project = self.new_project(pi=user) + project_user = self.new_project_user(user, project) + allocation = self.new_allocation(project, self.resource, 1) + allocation_user = self.new_allocation_user(allocation, user) + allocator = esi.ESIResourceAllocator(self.resource, + allocation) + + user2 = self.new_user() + project_user2 = self.new_project_user(user2, project) + allocation_user2 = self.new_allocation_user(allocation, user2) + + tasks.activate_allocation(allocation.pk) + allocation.refresh_from_db() + + project_id = allocation.get_attribute(attributes.ALLOCATION_PROJECT_ID) + openstack_project = self.identity.projects.get(project_id) + + tasks.add_user_to_allocation(allocation_user2.pk) + + openstack_user = allocator.get_federated_user(user2.username) + openstack_user = self.identity.users.get(openstack_user['id']) + + roles = self.identity.role_assignments.list(user=openstack_user.id, + project=openstack_project.id) + + self.assertEqual(len(roles), 1) + self.assertEqual(roles[0].role['id'], self.role_member.id) + assert set([user.username, user2.username]) == allocator.get_users(project_id) + + tasks.remove_user_from_allocation(allocation_user2.pk) + + roles = self.identity.role_assignments.list(user=openstack_user.id, + project=openstack_project.id) + + self.assertEqual(len(roles), 0) + assert set([user.username]) == allocator.get_users(project_id) + + # use the validate_allocations command to add a new user + user3 = self.new_user() + allocation_user3 = self.new_allocation_user(allocation, user3) + assert user3.username not in allocator.get_users(project_id) + call_command('validate_allocations', apply=True) + assert user3.username in allocator.get_users(project_id) + + non_coldfront_user = uuid.uuid4().hex + 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) + call_command('validate_allocations', apply=True) + assert non_coldfront_user not 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 2c523e76..542c842c 100644 --- a/src/coldfront_plugin_cloud/tests/functional/openstack/test_allocation.py +++ b/src/coldfront_plugin_cloud/tests/functional/openstack/test_allocation.py @@ -18,7 +18,7 @@ class TestAllocation(base.TestBase): def setUp(self) -> None: super().setUp() - self.resource = self.new_resource(name='Devstack', + self.resource = self.new_openstack_resource(name='Devstack', auth_url=os.getenv('OS_AUTH_URL')) self.session = openstack.get_session_for_resource(self.resource) self.identity = client.Client(session=self.session) @@ -207,7 +207,7 @@ def test_new_allocation_with_quantity(self): # Change allocation attributes for object store quota current_quota = allocator.get_quota(openstack_project.id) - obj_key = openstack.QUOTA_KEY_MAPPING['object']['keys'][attributes.QUOTA_OBJECT_GB] + obj_key = openstack.OpenStackResourceAllocator.QUOTA_KEY_MAPPING['object']['keys'][attributes.QUOTA_OBJECT_GB] if obj_key in current_quota.keys(): utils.set_attribute_on_allocation(allocation, attributes.QUOTA_OBJECT_GB, 6) self.assertEqual(allocation.get_attribute(attributes.QUOTA_OBJECT_GB), 6) diff --git a/src/coldfront_plugin_cloud/tests/unit/test_attribute_migration.py b/src/coldfront_plugin_cloud/tests/unit/test_attribute_migration.py index 9e886994..48dae117 100644 --- a/src/coldfront_plugin_cloud/tests/unit/test_attribute_migration.py +++ b/src/coldfront_plugin_cloud/tests/unit/test_attribute_migration.py @@ -154,7 +154,7 @@ def test_rename_identity_url(self): ): call_command('register_cloud_attributes') - resource = self.new_resource('Example', auth_url_val) + resource = self.new_openstack_resource('Example', auth_url_val) self.assertEqual( resource.get_attribute(new_auth_url_name),