Skip to content

Refactor to allow resource-agnostic validation#248

Open
QuanMPhm wants to merge 1 commit intonerc-project:mainfrom
QuanMPhm:239/agnostic_validate
Open

Refactor to allow resource-agnostic validation#248
QuanMPhm wants to merge 1 commit intonerc-project:mainfrom
QuanMPhm:239/agnostic_validate

Conversation

@QuanMPhm
Copy link
Copy Markdown
Contributor

@QuanMPhm QuanMPhm commented Sep 15, 2025

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.

@QuanMPhm QuanMPhm changed the title 239/agnostic validate Refactor to allow resource-agnostic validation Sep 15, 2025
Copy link
Copy Markdown
Collaborator

@knikolla knikolla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@QuanMPhm
Copy link
Copy Markdown
Contributor Author

Note-to-self: The branch name for the original solution (before time of this comment) is 239/agnostic_validate_first

@QuanMPhm QuanMPhm force-pushed the 239/agnostic_validate branch from 9153d7b to a86da68 Compare November 11, 2025 19:33
@QuanMPhm QuanMPhm requested a review from knikolla November 11, 2025 19:33
@QuanMPhm QuanMPhm force-pushed the 239/agnostic_validate branch 2 times, most recently from 40883e2 to 129234b Compare November 11, 2025 20:08
@QuanMPhm
Copy link
Copy Markdown
Contributor Author

QuanMPhm commented Nov 11, 2025

@knikolla I've cleaned house on validate_allocations.py. Hopefully this is closer to your vision. I haven't included tests for idempotency yet. For the sake of not making this PR too complicated, I believe that should be done in a subsequent PR?

@knikolla
Copy link
Copy Markdown
Collaborator

@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.

@QuanMPhm QuanMPhm force-pushed the 239/agnostic_validate branch from 129234b to a0ba80d Compare March 20, 2026 13:57
@QuanMPhm
Copy link
Copy Markdown
Contributor Author

@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(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to trigger a delete and set quota for each quota attribute that is out of date?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes. I noticed that inefficiency too with the validation code. I'll refactor that

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@knikolla the quota validation function has been refactored

@knikolla
Copy link
Copy Markdown
Collaborator

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`
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-resource set_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.

Comment on lines 73 to +75
try:
allocator.set_quota(
allocation.get_attribute(attributes.ALLOCATION_PROJECT_ID)
)
except Exception as e:
allocator.get_project(project_id)
except http.NotFound:
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.

expected_value = self.allocation.get_attribute(attr)
current_value = quota.get(key, None)
if key == OPENSTACK_OBJ_KEY and expected_value <= 0:
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
if key == OPENSTACK_OBJ_KEY and expected_value <= 0:
if (
key == OPENSTACK_OBJ_KEY
and expected_value is not None
and expected_value <= 0
):

Copilot uses AI. Check for mistakes.
Comment on lines +291 to +294
try:
swift = self.object(project_id).head_account()
except ksa_exceptions.catalog.EndpointNotFound:
logger.debug("No swift available, skipping its quota.")
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +98
def check_and_apply_quota_attr(
self,
attr: str,
expected_quota: int,
current_quota: int,
apply: bool,
) -> bool:
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
self.assertEqual(actual_nova_quota.cores, 2)
self.assertTrue(default_ram_quota > 0)

# Add a new attribute for Openshift
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
# Add a new attribute for Openshift
# Add a new attribute for OpenStack

Copilot uses AI. Check for mistakes.
Comment on lines 7 to 11
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
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@QuanMPhm base.py should not import tasks.py. Please call the methods within the allocator.

Comment on lines +147 to +149
current_value = int(
self.object(project_id).head_account().get(OPENSTACK_OBJ_KEY)
)
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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
)

Copilot uses AI. Check for mistakes.
f"{coldfront_user.user.username} is not part of {project_id}"
)
if apply:
tasks.add_user_to_allocation(coldfront_user.pk)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call the allocator's method rather than tasks.py


return failed_validation

# if apply:
Copy link
Copy Markdown
Collaborator

@knikolla knikolla Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be deleted?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor validate_allocations to be resource-agnostic

3 participants