Refactor to allow resource-agnostic validation#248
Refactor to allow resource-agnostic validation#248QuanMPhm wants to merge 1 commit intonerc-project:mainfrom
Conversation
knikolla
left a comment
There was a problem hiding this comment.
This is a good start, but not going as far as I would like in terms of making it "resource-agnostic", as you still have a lot of if resource_name in OPENSHIFT|OPENSTACK.
Try thinking about a way to push those resource-specific functions inside the respective allocators and creating a new function in the base Allocator that serves as the abstraction.
See my comment about validate project exist.
src/coldfront_plugin_cloud/management/commands/validate_allocations.py
Outdated
Show resolved
Hide resolved
|
Note-to-self: The branch name for the original solution (before time of this comment) is |
9153d7b to
a86da68
Compare
40883e2 to
129234b
Compare
|
@knikolla I've cleaned house on |
|
@QuanMPhm can you resolve conflicts (and update to new quota system if necessary). I would like to have this be part of next month's update. |
129234b to
a0ba80d
Compare
|
@knikolla I have resolved all conflicts. This is ready to review |
| current_value = quota.get(quota_key, None) | ||
| current_value = parse_quota_value(current_value, attr) | ||
|
|
||
| self.check_and_apply_quota_attr( |
There was a problem hiding this comment.
Is this going to trigger a delete and set quota for each quota attribute that is out of date?
There was a problem hiding this comment.
Yes. The check_and_apply_quota_attr() (defined in base.py) will have the same quota validation logic as the current validate_allocation command. Out-of-date quotas will trigger a call to set_quota, which deletes and sets the quotas
There was a problem hiding this comment.
Yes, but in this implementation the call to set quota is made for each allocation attribute, rather than setting a flag for failed_validation which calls set_quota once at the end and setting all the quotas and avoiding unnecessary deletions and recreations.
There was a problem hiding this comment.
Ah yes. I noticed that inefficiency too with the validation code. I'll refactor that
There was a problem hiding this comment.
@knikolla the quota validation function has been refactored
|
I did a quick first pass, I like this approach much more. Will do a second pass early tomorrow morning. |
Quota validation will now behave the same for both resource types. OpenStack's particular use of default quotas is reflected in a new test in `openstack/test_allocation.py` OpenStack integration code is slightly changed to better handle missing object storage quotas Much of the validation logic has been pushed into `base.py`, `openshift.py`, and `openstack.py`
a0ba80d to
a22bf80
Compare
There was a problem hiding this comment.
Pull request overview
Refactors validate_allocations to be more resource-agnostic by delegating validation/sync behavior to each resource allocator (OpenStack/OpenShift/ESI), aligning behavior across resource types as requested in #239 (built on #245).
Changes:
- Move common validation/sync logic into
ResourceAllocator(user sync + quota comparison/apply helper) and implement per-resourceset_project_configuration()flows. - Update OpenStack/OpenShift allocators to own quota validation/application (and OpenShift labels/limitrange validation).
- Update functional/unit tests to use the new public
get_project()API and to cover “new quota attribute added after allocations exist”.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/coldfront_plugin_cloud/tests/unit/openshift/test_project.py | Switch to get_project() public API. |
| src/coldfront_plugin_cloud/tests/functional/openstack/test_allocation.py | Update logging assertion target; add functional test for new quota attribute behavior. |
| src/coldfront_plugin_cloud/tests/functional/openshift/test_allocation.py | Switch to get_project() public API in functional checks. |
| src/coldfront_plugin_cloud/openstack.py | Implement set_project_configuration() + quota validation/apply; adjust Swift quota handling. |
| src/coldfront_plugin_cloud/openshift.py | Implement set_project_configuration() + limitrange/label/quota validation/apply; expose get_project(). |
| src/coldfront_plugin_cloud/management/commands/validate_allocations.py | Simplify command to iterate resource types and call allocator-provided configuration sync. |
| src/coldfront_plugin_cloud/base.py | Add shared user sync + quota comparison/apply helpers and allocation_str. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try: | ||
| allocator.set_quota( | ||
| allocation.get_attribute(attributes.ALLOCATION_PROJECT_ID) | ||
| ) | ||
| except Exception as e: | ||
| allocator.get_project(project_id) | ||
| except http.NotFound: |
There was a problem hiding this comment.
get_project() for OpenShift raises kubernetes.dynamic.exceptions.NotFoundError (not keystoneauth1.exceptions.http.NotFound). Catching only http.NotFound here means missing OpenShift projects will bubble up and abort the command. Consider normalizing allocator get_project() to raise a common exception type, or catch both NotFound exception types here.
|
|
||
| expected_value = self.allocation.get_attribute(attr) | ||
| current_value = quota.get(key, None) | ||
| if key == OPENSTACK_OBJ_KEY and expected_value <= 0: |
There was a problem hiding this comment.
expected_value can be None for pre-existing allocations when a quota attribute is newly added to the resource. The expected_value <= 0 check will raise TypeError in that case (this matches the PR description about missing object storage quota). Guard against None before the comparison (e.g., handle the expected_value is None case first).
| if key == OPENSTACK_OBJ_KEY and expected_value <= 0: | |
| if ( | |
| key == OPENSTACK_OBJ_KEY | |
| and expected_value is not None | |
| and expected_value <= 0 | |
| ): |
| try: | ||
| swift = self.object(project_id).head_account() | ||
| except ksa_exceptions.catalog.EndpointNotFound: | ||
| logger.debug("No swift available, skipping its quota.") |
There was a problem hiding this comment.
In the Swift quota lookup, EndpointNotFound is logged but execution continues into the second head_account() call, which is not wrapped in an EndpointNotFound handler. This will re-raise and fail get_quota() for deployments without an object-store endpoint. After catching EndpointNotFound, return/skip the object quota block (or include EndpointNotFound in the second try/except).
| def check_and_apply_quota_attr( | ||
| self, | ||
| attr: str, | ||
| expected_quota: int, | ||
| current_quota: int, | ||
| apply: bool, | ||
| ) -> bool: |
There was a problem hiding this comment.
Type hints here say expected_quota/current_quota are int, but the method explicitly handles None values. Updating the annotations to Optional[int] (and the return type/docstring if any) would better reflect actual usage and avoid misleading type checking.
| self.assertEqual(actual_nova_quota.cores, 2) | ||
| self.assertTrue(default_ram_quota > 0) | ||
|
|
||
| # Add a new attribute for Openshift |
There was a problem hiding this comment.
Comment says "Add a new attribute for Openshift" but this test is exercising OpenStack quota defaults. Renaming the comment to OpenStack avoids confusion when debugging failures.
| # Add a new attribute for Openshift | |
| # Add a new attribute for OpenStack |
| 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, tasks, utils | ||
| from coldfront_plugin_cloud.models.quota_models import QuotaSpecs |
There was a problem hiding this comment.
base.py now imports tasks at module import time, but tasks.py also imports base (tasks -> base -> tasks). While this may work depending on import order, it introduces a circular dependency that is easy to break later. Consider importing tasks lazily inside set_users() (or moving user-sync orchestration out of base).
There was a problem hiding this comment.
@QuanMPhm base.py should not import tasks.py. Please call the methods within the allocator.
| current_value = int( | ||
| self.object(project_id).head_account().get(OPENSTACK_OBJ_KEY) | ||
| ) |
There was a problem hiding this comment.
head_account().get(OPENSTACK_OBJ_KEY) can return None when the quota header is missing; int(None) will raise TypeError. If the goal is to treat a missing object quota as unset, handle the None case explicitly (e.g., default to 0/None or trigger initialization) before converting to int.
| current_value = int( | |
| self.object(project_id).head_account().get(OPENSTACK_OBJ_KEY) | |
| ) | |
| quota_header_value = self.object(project_id).head_account().get( | |
| OPENSTACK_OBJ_KEY | |
| ) | |
| current_value = ( | |
| int(quota_header_value) if quota_header_value is not None else None | |
| ) |
| f"{coldfront_user.user.username} is not part of {project_id}" | ||
| ) | ||
| if apply: | ||
| tasks.add_user_to_allocation(coldfront_user.pk) |
There was a problem hiding this comment.
Call the allocator's method rather than tasks.py
|
|
||
| return failed_validation | ||
|
|
||
| # if apply: |
There was a problem hiding this comment.
Should this be deleted?
Closes #239. This PR consists of the last commit. Built on top of #245. More details in the commit message.
@knikolla @jtriley I have a small bug in how error handling is done when the object storage quota is missing. Just want to confirm that my change is desirable.