Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/coldfront_plugin_cloud/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class CloudAllocationAttribute:
RESOURCE_API_URL = "OpenShift API Endpoint URL"
RESOURCE_IDENTITY_NAME = "OpenShift Identity Provider Name"
RESOURCE_ROLE = "Role for User in Project"
RESOURCE_IBM_AVAILABLE = "IBM Spectrum Scale Storage Available"

RESOURCE_FEDERATION_PROTOCOL = "OpenStack Federation Protocol"
RESOURCE_IDP = "OpenStack Identity Provider"
Expand All @@ -42,6 +43,7 @@ class CloudAllocationAttribute:
CloudResourceAttribute(name=RESOURCE_IDP),
CloudResourceAttribute(name=RESOURCE_PROJECT_DOMAIN),
CloudResourceAttribute(name=RESOURCE_ROLE),
CloudResourceAttribute(name=RESOURCE_IBM_AVAILABLE),
CloudResourceAttribute(name=RESOURCE_USER_DOMAIN),
CloudResourceAttribute(name=RESOURCE_EULA_URL),
CloudResourceAttribute(name=RESOURCE_DEFAULT_PUBLIC_NETWORK),
Expand Down Expand Up @@ -86,7 +88,10 @@ class CloudAllocationAttribute:
QUOTA_LIMITS_CPU = "OpenShift Limit on CPU Quota"
QUOTA_LIMITS_MEMORY = "OpenShift Limit on RAM Quota (MiB)"
QUOTA_LIMITS_EPHEMERAL_STORAGE_GB = "OpenShift Limit on Ephemeral Storage Quota (GiB)"
QUOTA_REQUESTS_STORAGE = "OpenShift Request on Storage Quota (GiB)"
QUOTA_REQUESTS_NESE_STORAGE = "OpenShift Request on NESE Storage Quota (GiB)"
QUOTA_REQUESTS_IBM_STORAGE = (
"OpenShift Request on IBM Spectrum Scale Storage Quota (GiB)"
)
QUOTA_REQUESTS_GPU = "OpenShift Request on GPU Quota"
QUOTA_REQUESTS_VM_GPU_A100_SXM4 = "OpenShift Request on GPU A100 SXM4"
QUOTA_REQUESTS_VM_GPU_V100 = "OpenShift Request on GPU V100"
Expand All @@ -107,7 +112,8 @@ class CloudAllocationAttribute:
CloudAllocationAttribute(name=QUOTA_LIMITS_CPU),
CloudAllocationAttribute(name=QUOTA_LIMITS_MEMORY),
CloudAllocationAttribute(name=QUOTA_LIMITS_EPHEMERAL_STORAGE_GB),
CloudAllocationAttribute(name=QUOTA_REQUESTS_STORAGE),
CloudAllocationAttribute(name=QUOTA_REQUESTS_NESE_STORAGE),
CloudAllocationAttribute(name=QUOTA_REQUESTS_IBM_STORAGE),
CloudAllocationAttribute(name=QUOTA_REQUESTS_GPU),
CloudAllocationAttribute(name=QUOTA_REQUESTS_VM_GPU_A100_SXM4),
CloudAllocationAttribute(name=QUOTA_REQUESTS_VM_GPU_V100),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ def add_arguments(self, parser):
action="store_true",
help="Indicates this is an OpenShift Virtualization resource (default: False)",
)
parser.add_argument(
"--ibm-storage-available",
action="store_true",
help="Indicates that Ibm Scale storage is available in this resource (default: False)",
)

def handle(self, *args, **options):
self.validate_role(options["role"])
Expand Down Expand Up @@ -86,3 +91,11 @@ def handle(self, *args, **options):
resource=openshift,
value=options["role"],
)

ResourceAttribute.objects.get_or_create(
resource_attribute_type=ResourceAttributeType.objects.get(
name=attributes.RESOURCE_IBM_AVAILABLE
),
resource=openshift,
value="true" if options["ibm_storage_available"] else "false",
)
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,9 @@ def process_invoice_row(allocation, attrs, su_name, rate):
allocation,
[
attributes.QUOTA_LIMITS_EPHEMERAL_STORAGE_GB,
attributes.QUOTA_REQUESTS_STORAGE,
attributes.QUOTA_REQUESTS_NESE_STORAGE,
],
"OpenShift Storage",
"OpenShift NESE Storage",
openshift_storage_rate,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
("OpenStack Project ID", {"name": "Allocated Project ID"}),
("OpenStack Project Name", {"name": "Allocated Project Name"}),
("OpenShift Limit on RAM Quota", {"name": "OpenShift Limit on RAM Quota (MB)"}),
(
"OpenShift Request on Storage Quota (GiB)",
{"name": "OpenShift Request on NESE Storage Quota (GiB)"},
),
("OpenStack Volume Quota", {"name": "OpenStack Number of Volumes Quota"}),
("OpenStack Compute RAM Quota", {"name": "OpenStack Compute RAM Quota (MiB)"}),
("OpenStack Volume GB Quota", {"name": "OpenStack Volume Quota (GiB)"}),
Expand Down
7 changes: 6 additions & 1 deletion src/coldfront_plugin_cloud/openshift.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ class OpenShiftResourceAllocator(base.ResourceAllocator):
attributes.QUOTA_LIMITS_EPHEMERAL_STORAGE_GB: lambda x: {
"limits.ephemeral-storage": f"{x}Gi"
},
attributes.QUOTA_REQUESTS_STORAGE: lambda x: {"requests.storage": f"{x}Gi"},
attributes.QUOTA_REQUESTS_NESE_STORAGE: lambda x: {
"ocs-external-storagecluster-ceph-rbd.storageclass.storage.k8s.io/requests.storage": f"{x}Gi"
},
attributes.QUOTA_REQUESTS_IBM_STORAGE: lambda x: {
"ibm-spectrum-scale-fileset.storageclass.storage.k8s.io/requests.storage": f"{x}Gi"
},
attributes.QUOTA_REQUESTS_GPU: lambda x: {"requests.nvidia.com/gpu": f"{x}"},
attributes.QUOTA_PVC: lambda x: {"persistentvolumeclaims": f"{x}"},
}
Expand Down
7 changes: 6 additions & 1 deletion src/coldfront_plugin_cloud/openshift_vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ class OpenShiftVMResourceAllocator(openshift.OpenShiftResourceAllocator):
attributes.QUOTA_LIMITS_EPHEMERAL_STORAGE_GB: lambda x: {
"limits.ephemeral-storage": f"{x}Gi"
},
attributes.QUOTA_REQUESTS_STORAGE: lambda x: {"requests.storage": f"{x}Gi"},
attributes.QUOTA_REQUESTS_NESE_STORAGE: lambda x: {
"ocs-external-storagecluster-ceph-rbd.storageclass.storage.k8s.io/requests.storage": f"{x}Gi"
},
attributes.QUOTA_REQUESTS_IBM_STORAGE: lambda x: {
"ibm-spectrum-scale-fileset.storageclass.storage.k8s.io/requests.storage": f"{x}Gi"
},
attributes.QUOTA_REQUESTS_VM_GPU_A100_SXM4: lambda x: {
"requests.nvidia.com/A100_SXM4_40GB": f"{x}"
},
Expand Down
22 changes: 19 additions & 3 deletions src/coldfront_plugin_cloud/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,17 @@
attributes.QUOTA_LIMITS_CPU: 1,
attributes.QUOTA_LIMITS_MEMORY: 4096,
attributes.QUOTA_LIMITS_EPHEMERAL_STORAGE_GB: 5,
attributes.QUOTA_REQUESTS_STORAGE: 20,
attributes.QUOTA_REQUESTS_NESE_STORAGE: 20,
attributes.QUOTA_REQUESTS_IBM_STORAGE: 0,
attributes.QUOTA_REQUESTS_GPU: 0,
attributes.QUOTA_PVC: 2,
},
"openshift_vm": {
attributes.QUOTA_LIMITS_CPU: 1,
attributes.QUOTA_LIMITS_MEMORY: 4096,
attributes.QUOTA_LIMITS_EPHEMERAL_STORAGE_GB: 5,
attributes.QUOTA_REQUESTS_STORAGE: 20,
attributes.QUOTA_REQUESTS_NESE_STORAGE: 20,
attributes.QUOTA_REQUESTS_IBM_STORAGE: 0,
attributes.QUOTA_REQUESTS_VM_GPU_A100_SXM4: 0,
attributes.QUOTA_REQUESTS_VM_GPU_V100: 0,
attributes.QUOTA_REQUESTS_VM_GPU_H100: 0,
Expand Down Expand Up @@ -74,7 +76,21 @@
def get_expected_attributes(allocator: base.ResourceAllocator):
"""Based on the allocator's resource type, return the expected quotas attributes the allocation should have"""
resource_name = allocator.resource_type
return list(UNIT_QUOTA_MULTIPLIERS[resource_name].keys())
resource_expected_quotas = UNIT_QUOTA_MULTIPLIERS[resource_name].copy()

# If the resource attribute is not set (i.e for OpenStack resources), get_attribute returns None
is_ibm_storage_available = allocator.resource.get_attribute(
attributes.RESOURCE_IBM_AVAILABLE
)
is_ibm_storage_available = (
is_ibm_storage_available and is_ibm_storage_available.lower() == "true"
)
if "openshift" in resource_name and not is_ibm_storage_available:
resource_expected_quotas.pop(
attributes.QUOTA_REQUESTS_IBM_STORAGE, None
) # The resource may or may not already have this attribute

return list(resource_expected_quotas.keys())


def find_allocator(allocation) -> base.ResourceAllocator:
Expand Down
7 changes: 6 additions & 1 deletion src/coldfront_plugin_cloud/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ def new_openstack_resource(name=None, auth_url=None) -> Resource:

@staticmethod
def new_openshift_resource(
name=None, api_url=None, idp=None, for_virtualization=False
name=None,
api_url=None,
idp=None,
for_virtualization=False,
ibm_storage_available=False,
) -> Resource:
resource_name = name or uuid.uuid4().hex

Expand All @@ -90,6 +94,7 @@ def new_openshift_resource(
api_url=api_url or "https://onboarding-onboarding.cluster.local:6443",
idp=idp or "developer",
for_virtualization=for_virtualization,
ibm_storage_available=ibm_storage_available,
)
return Resource.objects.get(name=resource_name)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def setUp(self) -> None:
self.resource = self.new_openshift_resource(
name="Microshift",
api_url=os.getenv("OS_API_URL"),
ibm_storage_available=True,
)

def test_new_allocation(self):
Expand Down Expand Up @@ -135,7 +136,7 @@ def test_new_allocation_quota(self):
2 * 5,
)
self.assertEqual(
allocation.get_attribute(attributes.QUOTA_REQUESTS_STORAGE), 2 * 20
allocation.get_attribute(attributes.QUOTA_REQUESTS_NESE_STORAGE), 2 * 20
)
self.assertEqual(allocation.get_attribute(attributes.QUOTA_REQUESTS_GPU), 2 * 0)
self.assertEqual(allocation.get_attribute(attributes.QUOTA_PVC), 2 * 2)
Expand All @@ -149,7 +150,8 @@ def test_new_allocation_quota(self):
"limits.cpu": "2",
"limits.memory": "8Gi",
"limits.ephemeral-storage": "10Gi",
"requests.storage": "40Gi",
"ocs-external-storagecluster-ceph-rbd.storageclass.storage.k8s.io/requests.storage": "40Gi",
"ibm-spectrum-scale-fileset.storageclass.storage.k8s.io/requests.storage": "0",
"requests.nvidia.com/gpu": "0",
"persistentvolumeclaims": "4",
},
Expand All @@ -164,7 +166,7 @@ def test_new_allocation_quota(self):
allocation, attributes.QUOTA_LIMITS_EPHEMERAL_STORAGE_GB, 50
)
utils.set_attribute_on_allocation(
allocation, attributes.QUOTA_REQUESTS_STORAGE, 100
allocation, attributes.QUOTA_REQUESTS_NESE_STORAGE, 100
)
utils.set_attribute_on_allocation(allocation, attributes.QUOTA_REQUESTS_GPU, 1)
utils.set_attribute_on_allocation(allocation, attributes.QUOTA_PVC, 10)
Expand All @@ -175,7 +177,7 @@ def test_new_allocation_quota(self):
allocation.get_attribute(attributes.QUOTA_LIMITS_EPHEMERAL_STORAGE_GB), 50
)
self.assertEqual(
allocation.get_attribute(attributes.QUOTA_REQUESTS_STORAGE), 100
allocation.get_attribute(attributes.QUOTA_REQUESTS_NESE_STORAGE), 100
)
self.assertEqual(allocation.get_attribute(attributes.QUOTA_REQUESTS_GPU), 1)
self.assertEqual(allocation.get_attribute(attributes.QUOTA_PVC), 10)
Expand All @@ -192,7 +194,8 @@ def test_new_allocation_quota(self):
"limits.cpu": "6",
"limits.memory": "8Gi",
"limits.ephemeral-storage": "50Gi",
"requests.storage": "100Gi",
"ocs-external-storagecluster-ceph-rbd.storageclass.storage.k8s.io/requests.storage": "100Gi",
"ibm-spectrum-scale-fileset.storageclass.storage.k8s.io/requests.storage": "0",
"requests.nvidia.com/gpu": "1",
"persistentvolumeclaims": "10",
},
Expand Down Expand Up @@ -221,7 +224,8 @@ def test_reactivate_allocation(self):
"limits.cpu": "2",
"limits.memory": "8Gi",
"limits.ephemeral-storage": "10Gi",
"requests.storage": "40Gi",
"ocs-external-storagecluster-ceph-rbd.storageclass.storage.k8s.io/requests.storage": "40Gi",
"ibm-spectrum-scale-fileset.storageclass.storage.k8s.io/requests.storage": "0",
"requests.nvidia.com/gpu": "0",
"persistentvolumeclaims": "4",
},
Expand All @@ -242,7 +246,8 @@ def test_reactivate_allocation(self):
"limits.cpu": "3",
"limits.memory": "8Gi",
"limits.ephemeral-storage": "10Gi",
"requests.storage": "40Gi",
"ocs-external-storagecluster-ceph-rbd.storageclass.storage.k8s.io/requests.storage": "40Gi",
"ibm-spectrum-scale-fileset.storageclass.storage.k8s.io/requests.storage": "0",
"requests.nvidia.com/gpu": "0",
"persistentvolumeclaims": "4",
},
Expand Down Expand Up @@ -375,3 +380,106 @@ def test_allocation_new_attribute(self):
"limits.memory": "8Gi",
},
)

def test_migrate_quota_field_names(self):
"""When a quota key in QUOTA_KEY_MAPPING changes to a new value, validate_allocations should update the quota."""
user = self.new_user()
project = self.new_project(pi=user)
allocation = self.new_allocation(project, self.resource, 1)
allocator = openshift.OpenShiftResourceAllocator(self.resource, allocation)

tasks.activate_allocation(allocation.pk)
allocation.refresh_from_db()

project_id = allocation.get_attribute(attributes.ALLOCATION_PROJECT_ID)

quota = allocator.get_quota(project_id)
self.assertEqual(
quota,
{
"limits.cpu": "1",
"limits.memory": "4Gi",
"limits.ephemeral-storage": "5Gi",
"ocs-external-storagecluster-ceph-rbd.storageclass.storage.k8s.io/requests.storage": "20Gi",
"ibm-spectrum-scale-fileset.storageclass.storage.k8s.io/requests.storage": "0",
"requests.nvidia.com/gpu": "0",
"persistentvolumeclaims": "2",
},
)

# Now migrate NESE Storage quota field (ocs-external...) to fake storage quota
with unittest.mock.patch.dict(
openshift.OpenShiftResourceAllocator.QUOTA_KEY_MAPPING,
{
attributes.QUOTA_REQUESTS_NESE_STORAGE: lambda x: {
"fake-storage.storageclass.storage.k8s.io/requests.storage": f"{x}Gi"
}
},
):
call_command("validate_allocations", apply=True)

# Check the quota after migration
quota = allocator.get_quota(project_id)
self.assertEqual(
quota,
{
"limits.cpu": "1",
"limits.memory": "4Gi",
"limits.ephemeral-storage": "5Gi",
"fake-storage.storageclass.storage.k8s.io/requests.storage": "20Gi", # Migrated key
"ibm-spectrum-scale-fileset.storageclass.storage.k8s.io/requests.storage": "0",
"requests.nvidia.com/gpu": "0",
"persistentvolumeclaims": "2",
},
)

def test_ibm_storage_not_available(self):
"""If IBM Scale storage is not available, the corresponding quotas should not be set."""
user = self.new_user()
project = self.new_project(pi=user)

# Set ibm storage as not available
self.resource.resourceattribute_set.filter(
resource_attribute_type__name=attributes.RESOURCE_IBM_AVAILABLE
).update(value="false")
allocation = self.new_allocation(project, self.resource, 1)
allocator = openshift.OpenShiftResourceAllocator(self.resource, allocation)

tasks.activate_allocation(allocation.pk)
allocation.refresh_from_db()

project_id = allocation.get_attribute(attributes.ALLOCATION_PROJECT_ID)

quota = allocator.get_quota(project_id)
self.assertEqual(
quota,
{
"limits.cpu": "1",
"limits.memory": "4Gi",
"limits.ephemeral-storage": "5Gi",
"ocs-external-storagecluster-ceph-rbd.storageclass.storage.k8s.io/requests.storage": "20Gi",
"requests.nvidia.com/gpu": "0",
"persistentvolumeclaims": "2",
},
)

# Now set IBM Scale storage as available
self.resource.resourceattribute_set.filter(
resource_attribute_type__name=attributes.RESOURCE_IBM_AVAILABLE
).update(value="true")

call_command("validate_allocations", apply=True)

quota = allocator.get_quota(project_id)
self.assertEqual(
quota,
{
"limits.cpu": "1",
"limits.memory": "4Gi",
"limits.ephemeral-storage": "5Gi",
"ocs-external-storagecluster-ceph-rbd.storageclass.storage.k8s.io/requests.storage": "20Gi",
"ibm-spectrum-scale-fileset.storageclass.storage.k8s.io/requests.storage": "0", # Newly added IBM key
"requests.nvidia.com/gpu": "0",
"persistentvolumeclaims": "2",
},
)
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def test_new_allocation(self):
2 * 5,
)
self.assertEqual(
allocation.get_attribute(attributes.QUOTA_REQUESTS_STORAGE), 2 * 20
allocation.get_attribute(attributes.QUOTA_REQUESTS_NESE_STORAGE), 2 * 20
)
self.assertEqual(
allocation.get_attribute(attributes.QUOTA_REQUESTS_VM_GPU_A100_SXM4), 2 * 0
Expand All @@ -58,7 +58,7 @@ def test_new_allocation(self):
"limits.cpu": "2",
"limits.memory": "8Gi",
"limits.ephemeral-storage": "10Gi",
"requests.storage": "40Gi",
"ocs-external-storagecluster-ceph-rbd.storageclass.storage.k8s.io/requests.storage": "40Gi",
"requests.nvidia.com/A100_SXM4_40GB": "0",
"requests.nvidia.com/GV100GL_Tesla_V100": "0",
"requests.nvidia.com/H100_SXM5_80GB": "0",
Expand Down