Skip to content

Commit

Permalink
feat: add api endpoint for exposing the current state (#347) (#349)
Browse files Browse the repository at this point in the history
  • Loading branch information
stolpeo authored Dec 5, 2024
1 parent 6cea671 commit c943c94
Show file tree
Hide file tree
Showing 9 changed files with 630 additions and 149 deletions.
2 changes: 2 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ django-rest-framework = "*"
django-rest-knox = "*"
pydantic = "*"
django-impersonate = "*"
attrs = "*"
cattrs = "*"

[dev-packages]
Werkzeug = "*"
Expand Down
311 changes: 174 additions & 137 deletions Pipfile.lock

Large diffs are not rendered by default.

74 changes: 72 additions & 2 deletions adminsec/tests/test_views_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ class ApiTestCase(TestCase):
def setUp(self):
super().setUp()
# Create usersec.models records.
self.hpcuser_user = HpcUserFactory()
self.hpcuser_group = HpcGroupFactory()
self.hpcuser_project = HpcProjectFactory()
self.hpcuser_user = HpcUserFactory(primary_group=self.hpcuser_group)
self.hpcuser_project = HpcProjectFactory(group=self.hpcuser_group)
# Create Django users.
self.user_user = self.make_user("user")
self.user_staff = self.make_user("staff")
Expand All @@ -52,6 +52,7 @@ def setUp(self):
)
# Create HpcProjectCreateRequest.
self.hpcprojectcreaterequest = HpcProjectCreateRequestFactory(
group=self.hpcuser_group,
requester=self.user_user,
status=REQUEST_STATUS_ACTIVE,
)
Expand Down Expand Up @@ -625,3 +626,72 @@ def test_delete_fail(self):
self.response_405()
else:
self.response_403()


class TestHpcAccessStatusApiView(ApiTestCase):
"""Tests for the HpcAccessStatusApiView."""

def test_get_succeed(self):
"""Test the GET method (staff users can do)."""

self.maxDiff = None

expected = {
"hpc_users": [
{
"uid": self.hpcuser_user.user.uid,
"email": self.hpcuser_user.user.email,
"full_name": "User Name",
"first_name": self.hpcuser_user.user.first_name,
"last_name": self.hpcuser_user.user.last_name,
"phone_number": None,
"primary_group": self.hpcuser_group.name,
"resources_requested": self.hpcuser_user.resources_requested,
"status": "INITIAL",
"description": self.hpcuser_user.description,
"username": self.hpcuser_user.username,
"expiration": self.hpcuser_user.expiration.strftime("%Y-%m-%dT%H:%M:%SZ"),
"home_directory": self.hpcuser_user.home_directory,
"login_shell": self.hpcuser_user.login_shell,
}
],
"hpc_groups": [
{
"owner": None,
"delegate": None,
"resources_requested": self.hpcuser_group.resources_requested,
"status": "INITIAL",
"description": self.hpcuser_group.description,
"name": self.hpcuser_group.name,
"folders": self.hpcuser_group.folders,
"expiration": self.hpcuser_group.expiration.strftime("%Y-%m-%dT%H:%M:%SZ"),
"gid": self.hpcuser_group.gid,
}
],
"hpc_projects": [
{
"gid": self.hpcuser_project.gid,
"group": self.hpcuser_group.name,
"delegate": None,
"resources_requested": self.hpcuser_project.resources_requested,
"status": "INITIAL",
"description": self.hpcuser_project.description,
"name": self.hpcuser_project.name,
"folders": self.hpcuser_project.folders,
"expiration": self.hpcuser_project.expiration.strftime("%Y-%m-%dT%H:%M:%SZ"),
"members": [],
}
],
}
for user in [self.user_staff, self.user_admin, self.user_hpcadmin]:
with self.login(user):
self.get("adminsec:api-hpcaccess-status")
self.response_200()
self.assertEqual(self.last_response.json(), expected)

def test_get_fail(self):
"""Test the GET method (non-staff cannot do)."""
for user in [self.user_user]:
with self.login(user):
self.get("adminsec:api-hpcaccess-status")
self.response_403()
5 changes: 5 additions & 0 deletions adminsec/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,11 @@
view=views_api.HpcProjectCreateRequestRetrieveUpdateApiView.as_view(),
name="api-hpcprojectcreaterequest-retrieveupdate",
),
path(
"api/hpcaccess-status/",
view=views_api.HpcAccessStatusApiView.as_view(),
name="api-hpcaccess-status",
),
]

urlpatterns = urlpatterns_ui + urlpatterns_api
27 changes: 27 additions & 0 deletions adminsec/views_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import re

import attr
from rest_framework.exceptions import ValidationError
from rest_framework.generics import (
ListAPIView,
RetrieveAPIView,
RetrieveUpdateAPIView,
get_object_or_404,
)
Expand All @@ -24,6 +26,7 @@
HpcUser,
)
from usersec.serializers import (
HpcAccessStatusSerializer,
HpcGroupCreateRequestSerializer,
HpcGroupSerializer,
HpcProjectCreateRequestSerializer,
Expand Down Expand Up @@ -215,3 +218,27 @@ def perform_update(self, serializer):
raise ValidationError(errors)

super().perform_update(serializer)


@attr.s(frozen=True)
class HpcAccessStatus:
"""Class to hold the status of the HPC access system."""

hpc_users: dict = attr.ib()
hpc_groups: dict = attr.ib()
hpc_projects: dict = attr.ib()


class HpcAccessStatusApiView(RetrieveAPIView):
"""API view for listing all users."""

serializer_class = HpcAccessStatusSerializer
permission_classes = [IsAdminUser | IsHpcAdminUser]

def get_object(self):
"""Return the object to be used in the view."""
return HpcAccessStatus(
hpc_users=HpcUser.objects.all(),
hpc_groups=HpcGroup.objects.all(),
hpc_projects=HpcProject.objects.all(),
)
105 changes: 95 additions & 10 deletions usersec/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,23 +97,28 @@ class Meta:
]


class HpcUserLookupSerializer(serializers.ModelSerializer):
"""Serializer for HpcUser model for lookup purposes."""
class HpcUserStatusSerializer(HpcUserAbstractSerializer, serializers.ModelSerializer):
"""Serializer for HpcUser model."""

primary_group = serializers.SlugRelatedField(slug_field="name", read_only=True)
username = serializers.CharField(read_only=True)
full_name = serializers.SerializerMethodField()

def get_full_name(self, obj) -> str:
return obj.user.name

class Meta:
model = HpcUser
fields = [
"id",
"username",
"primary_group",
"uid",
"email",
"full_name",
"first_name",
"last_name",
"phone_number",
"primary_group",
"resources_requested",
"status",
"description",
"username",
"expiration",
"home_directory",
"login_shell",
]


Expand Down Expand Up @@ -184,6 +189,26 @@ class Meta:
]


class HpcGroupStatusSerializer(HpcGroupAbstractSerializer, serializers.ModelSerializer):
"""Serializer for HpcGroup model."""

owner = serializers.SlugRelatedField(slug_field="username", read_only=True)

class Meta:
model = HpcUser
fields = [
"owner",
"delegate",
"resources_requested",
"status",
"description",
"name",
"folders",
"expiration",
"gid",
]


class HpcProjectAbstractSerializer(HpcObjectAbstractSerializer):
"""Common base class for HPC project serializers."""

Expand Down Expand Up @@ -240,6 +265,29 @@ class Meta:
]


class HpcProjectStatusSerializer(HpcProjectAbstractSerializer, serializers.ModelSerializer):
"""Serializer for HpcProject model."""

group = serializers.SlugRelatedField(slug_field="name", read_only=True)
delegate = serializers.SlugRelatedField(slug_field="username", read_only=True)
members = serializers.SlugRelatedField(slug_field="username", many=True, read_only=True)

class Meta:
model = HpcUser
fields = [
"gid",
"group",
"delegate",
"resources_requested",
"status",
"description",
"name",
"folders",
"expiration",
"members",
]


class HpcRequestAbstractSerializer(HpcObjectAbstractSerializer):
"""Common base class for HPC request serializers."""

Expand Down Expand Up @@ -377,3 +425,40 @@ class Meta:
fields = HpcProjectCreateRequestAbstractSerializer.Meta.fields + [
"version",
]


class HpcUserLookupSerializer(serializers.ModelSerializer):
"""Serializer for HpcUser model for lookup purposes."""

primary_group = serializers.SlugRelatedField(slug_field="name", read_only=True)
username = serializers.CharField(read_only=True)
full_name = serializers.SerializerMethodField()

def get_full_name(self, obj) -> str:
return obj.user.name

class Meta:
model = HpcUser
fields = [
"id",
"username",
"primary_group",
"full_name",
]


class HpcAccessStatusSerializer(serializers.Serializer):
"""Serializer for HpcAccessStatus model."""

hpc_users = serializers.SerializerMethodField()
hpc_groups = serializers.SerializerMethodField()
hpc_projects = serializers.SerializerMethodField()

def get_hpc_users(self, obj):
return HpcUserStatusSerializer(obj.hpc_users, many=True).data

def get_hpc_groups(self, obj):
return HpcGroupStatusSerializer(obj.hpc_groups, many=True).data

def get_hpc_projects(self, obj):
return HpcProjectStatusSerializer(obj.hpc_projects, many=True).data
15 changes: 15 additions & 0 deletions utils/cli/hpc_access_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
TargetStateBuilder,
TargetStateComparison,
convert_to_hpcaccess_state,
convert_to_hpcaccess_state_v2,
deploy_hpcaccess_state,
fs_validation,
gather_hpcaccess_state,
Expand Down Expand Up @@ -79,6 +80,20 @@ def dump_data(
console_out.print_json(data=hpcaccess_state.model_dump(mode="json"))


@app.command("state-dump-v2")
def dump_data_v2(
config_path: Annotated[
str, typer.Option(..., help="path to configuration file")
] = "/etc/hpc-access-cli/config.json",
):
"""dump system state as hpc-access state"""
settings = load_settings(config_path)
console_err.print_json(data=settings.model_dump(mode="json"))
system_state = gather_system_state(settings)
hpcaccess_state = convert_to_hpcaccess_state_v2(system_state)
console_out.print_json(data=hpcaccess_state.model_dump(mode="json"))


@app.command("state-sync")
def sync_data(
config_path: Annotated[
Expand Down
Loading

0 comments on commit c943c94

Please sign in to comment.