Loading common_lib/schema/types.py +10 −1 Original line number Diff line number Diff line Loading @@ -144,6 +144,13 @@ class FileInfoType(DjangoObjectType): definition = graphene.Field(ExerciseDefinitionType, required=False) class OpenSearchDataType(graphene.ObjectType): id = graphene.ID(required=True) exercise_id = graphene.ID(required=True) team_id = graphene.ID(required=True) data = graphene.JSONString(required=True) class ContentType(DjangoObjectType): class Meta: model = Content Loading Loading @@ -370,7 +377,9 @@ class TeamType(DjangoObjectType): team_questionnaire_states = graphene.List( graphene.NonNull(_("TeamQuestionnaireStateInterface")), required=True ) open_search_access = graphene.Field(_("OpenSearchAccessType"), required=False) open_search_access = graphene.Field( _("OpenSearchAccessType"), required=False ) def resolve_users(self, info): access = get_access_for(info.context, self.exercise_id) Loading exercise/lib/exercise_manager.py +7 −2 Original line number Diff line number Diff line Loading @@ -213,9 +213,14 @@ def create_exercise( create_drive_permissions(teams, definition.files.filter(is_drive=True)) if exercise.technical: from running_exercise.lib.opensearch_client import create_opensearch_exercise, create_opensearch_access from running_exercise.lib.opensearch_client import ( create_opensearch_exercise, create_opensearch_access, ) opensearch_credentials = create_opensearch_exercise([team.id for team in teams]) opensearch_credentials = create_opensearch_exercise( [team.id for team in teams] ) print("=== credentials ===") print(opensearch_credentials) Loading running_exercise/lib/opensearch_client.py +63 −18 Original line number Diff line number Diff line from typing import List from opensearchpy import OpenSearch from datetime import datetime from common_lib.schema.types import OpenSearchDataType from exercise.models import OpenSearchAccess host = "localhost" Loading Loading @@ -38,12 +40,14 @@ def create_opensearch_exercise(team_ids): username = f"team-{team_id}" # TODO: Generate a strong password for each user password = f"v&ery6#7st*ong78288732-pass889329word-aVUfg9" credentials.append({ credentials.append( { "team_id": team_id, "index_name": index_name, "username": username, "password": password, }) } ) user_body = { "password": password, } Loading @@ -53,21 +57,18 @@ def create_opensearch_exercise(team_ids): "cluster_permissions": [ "cluster:monitor/main", "cluster:admin/ingest/pipeline/get", "cluster:admin/ingest/pipeline/put" "cluster:admin/ingest/pipeline/put", ], "index_permissions": [ { "index_patterns": [ f"{index_name}" ], "index_patterns": [f"{index_name}"], "allowed_actions": [ "indices_all", ] ], } ] ], } role_mapping_body = { "users": [username], } Loading @@ -80,7 +81,9 @@ def create_opensearch_exercise(team_ids): # https://docs.opensearch.org/docs/latest/security/access-control/users-roles/#defining-users # https://docs.opensearch.org/docs/latest/security/access-control/api/#create-user user = client.security.create_user(username=username, body=user_body) user = client.security.create_user( username=username, body=user_body ) print(f"✓ User") # https://docs.opensearch.org/docs/latest/security/access-control/users-roles/#defining-roles Loading @@ -90,10 +93,13 @@ def create_opensearch_exercise(team_ids): # https://docs.opensearch.org/docs/latest/security/access-control/users-roles/#mapping-users-to-roles # https://docs.opensearch.org/docs/latest/security/access-control/api/#create-role-mapping role_mapping = client.security.create_role_mapping(role=role_name, body=role_mapping_body) role_mapping = client.security.create_role_mapping( role=role_name, body=role_mapping_body ) print(f"✓ Role mapping") except Exception as e: print(f"✗ Error") print(f"✗ Error {e}") # TODO: cleanup and re-raise return credentials return credentials Loading @@ -107,8 +113,47 @@ def create_opensearch_access(teams, opensearch_credentials): team=team, index_name=access["index_name"], username=access["username"], password=access["password"] password=access["password"], ), opensearch_credentials, ) ) def get_exercise_opensearch_data(exercise_id) -> List[OpenSearchDataType]: access_list = OpenSearchAccess.objects.filter(team__exercise_id=exercise_id) indices = access_list.values_list("index_name", flat=True) try: # return the last data entries from each index using OpenSearch client # https://docs.opensearch.org/docs/latest/api-reference/search-apis/multi-search/ msearch_body = [] for index in indices: msearch_body.append({"index": index}) msearch_body.append({"size": 5, "query": {"match_all": {}}}) msearch_result = client.msearch( body=msearch_body, index=",".join(indices), ) responses = msearch_result.get("responses", []) result = [] for response in responses: if "hits" in response and "hits" in response["hits"]: result.append( OpenSearchDataType( id=response["hits"]["hits"][0]["_id"], team_id=access_list.get( index_name=response["hits"]["hits"][0]["_index"] ).team.id, exercise_id=exercise_id, # TODO: process further data=response["hits"]["hits"], ) ) print(f"✓ Fetch") return result except Exception as e: print(f"Error {e}") raise e running_exercise/schema/query.py +24 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ from common_lib.schema.enums import LogTypeEnum from common_lib.schema.types import ( MilestoneStateType, EmailThreadType, OpenSearchDataType, TeamLearningObjectiveType, EmailParticipantType, EmailTemplateType, Loading Loading @@ -233,6 +234,11 @@ class Query(graphene.ObjectType): exercise_id=graphene.ID(required=True), description="Retrieve all drive files for the given exercise", ) opensearch_data = graphene.List( graphene.NonNull(OpenSearchDataType), exercise_id=graphene.ID(required=True), description="Retrieve some OpenSearch data for the given exercise", ) @protected(User.AuthGroup.TRAINEE) def resolve_team(self, info, team_id: str) -> Team: Loading Loading @@ -568,3 +574,21 @@ class Query(graphene.ObjectType): return FileInfo.objects.filter( is_drive=True, definition_id=exercise.definition_id ) @protected(User.AuthGroup.INSTRUCTOR) def resolve_opensearch_data( self, info, exercise_id: str ) -> QuerySet[OpenSearchDataType]: access = exercise_access(info.context, int(exercise_id)) if access.group < User.AuthGroup.INSTRUCTOR: # TODO: return empty set; OpenSearchDataType is graphene.ObjectType so no .objects is available raise PermissionDenied( "User does not have access to OpenSearch data for this exercise" ) from running_exercise.lib.opensearch_client import ( get_exercise_opensearch_data, ) data = get_exercise_opensearch_data(int(exercise_id)) return data Loading
common_lib/schema/types.py +10 −1 Original line number Diff line number Diff line Loading @@ -144,6 +144,13 @@ class FileInfoType(DjangoObjectType): definition = graphene.Field(ExerciseDefinitionType, required=False) class OpenSearchDataType(graphene.ObjectType): id = graphene.ID(required=True) exercise_id = graphene.ID(required=True) team_id = graphene.ID(required=True) data = graphene.JSONString(required=True) class ContentType(DjangoObjectType): class Meta: model = Content Loading Loading @@ -370,7 +377,9 @@ class TeamType(DjangoObjectType): team_questionnaire_states = graphene.List( graphene.NonNull(_("TeamQuestionnaireStateInterface")), required=True ) open_search_access = graphene.Field(_("OpenSearchAccessType"), required=False) open_search_access = graphene.Field( _("OpenSearchAccessType"), required=False ) def resolve_users(self, info): access = get_access_for(info.context, self.exercise_id) Loading
exercise/lib/exercise_manager.py +7 −2 Original line number Diff line number Diff line Loading @@ -213,9 +213,14 @@ def create_exercise( create_drive_permissions(teams, definition.files.filter(is_drive=True)) if exercise.technical: from running_exercise.lib.opensearch_client import create_opensearch_exercise, create_opensearch_access from running_exercise.lib.opensearch_client import ( create_opensearch_exercise, create_opensearch_access, ) opensearch_credentials = create_opensearch_exercise([team.id for team in teams]) opensearch_credentials = create_opensearch_exercise( [team.id for team in teams] ) print("=== credentials ===") print(opensearch_credentials) Loading
running_exercise/lib/opensearch_client.py +63 −18 Original line number Diff line number Diff line from typing import List from opensearchpy import OpenSearch from datetime import datetime from common_lib.schema.types import OpenSearchDataType from exercise.models import OpenSearchAccess host = "localhost" Loading Loading @@ -38,12 +40,14 @@ def create_opensearch_exercise(team_ids): username = f"team-{team_id}" # TODO: Generate a strong password for each user password = f"v&ery6#7st*ong78288732-pass889329word-aVUfg9" credentials.append({ credentials.append( { "team_id": team_id, "index_name": index_name, "username": username, "password": password, }) } ) user_body = { "password": password, } Loading @@ -53,21 +57,18 @@ def create_opensearch_exercise(team_ids): "cluster_permissions": [ "cluster:monitor/main", "cluster:admin/ingest/pipeline/get", "cluster:admin/ingest/pipeline/put" "cluster:admin/ingest/pipeline/put", ], "index_permissions": [ { "index_patterns": [ f"{index_name}" ], "index_patterns": [f"{index_name}"], "allowed_actions": [ "indices_all", ] ], } ] ], } role_mapping_body = { "users": [username], } Loading @@ -80,7 +81,9 @@ def create_opensearch_exercise(team_ids): # https://docs.opensearch.org/docs/latest/security/access-control/users-roles/#defining-users # https://docs.opensearch.org/docs/latest/security/access-control/api/#create-user user = client.security.create_user(username=username, body=user_body) user = client.security.create_user( username=username, body=user_body ) print(f"✓ User") # https://docs.opensearch.org/docs/latest/security/access-control/users-roles/#defining-roles Loading @@ -90,10 +93,13 @@ def create_opensearch_exercise(team_ids): # https://docs.opensearch.org/docs/latest/security/access-control/users-roles/#mapping-users-to-roles # https://docs.opensearch.org/docs/latest/security/access-control/api/#create-role-mapping role_mapping = client.security.create_role_mapping(role=role_name, body=role_mapping_body) role_mapping = client.security.create_role_mapping( role=role_name, body=role_mapping_body ) print(f"✓ Role mapping") except Exception as e: print(f"✗ Error") print(f"✗ Error {e}") # TODO: cleanup and re-raise return credentials return credentials Loading @@ -107,8 +113,47 @@ def create_opensearch_access(teams, opensearch_credentials): team=team, index_name=access["index_name"], username=access["username"], password=access["password"] password=access["password"], ), opensearch_credentials, ) ) def get_exercise_opensearch_data(exercise_id) -> List[OpenSearchDataType]: access_list = OpenSearchAccess.objects.filter(team__exercise_id=exercise_id) indices = access_list.values_list("index_name", flat=True) try: # return the last data entries from each index using OpenSearch client # https://docs.opensearch.org/docs/latest/api-reference/search-apis/multi-search/ msearch_body = [] for index in indices: msearch_body.append({"index": index}) msearch_body.append({"size": 5, "query": {"match_all": {}}}) msearch_result = client.msearch( body=msearch_body, index=",".join(indices), ) responses = msearch_result.get("responses", []) result = [] for response in responses: if "hits" in response and "hits" in response["hits"]: result.append( OpenSearchDataType( id=response["hits"]["hits"][0]["_id"], team_id=access_list.get( index_name=response["hits"]["hits"][0]["_index"] ).team.id, exercise_id=exercise_id, # TODO: process further data=response["hits"]["hits"], ) ) print(f"✓ Fetch") return result except Exception as e: print(f"Error {e}") raise e
running_exercise/schema/query.py +24 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ from common_lib.schema.enums import LogTypeEnum from common_lib.schema.types import ( MilestoneStateType, EmailThreadType, OpenSearchDataType, TeamLearningObjectiveType, EmailParticipantType, EmailTemplateType, Loading Loading @@ -233,6 +234,11 @@ class Query(graphene.ObjectType): exercise_id=graphene.ID(required=True), description="Retrieve all drive files for the given exercise", ) opensearch_data = graphene.List( graphene.NonNull(OpenSearchDataType), exercise_id=graphene.ID(required=True), description="Retrieve some OpenSearch data for the given exercise", ) @protected(User.AuthGroup.TRAINEE) def resolve_team(self, info, team_id: str) -> Team: Loading Loading @@ -568,3 +574,21 @@ class Query(graphene.ObjectType): return FileInfo.objects.filter( is_drive=True, definition_id=exercise.definition_id ) @protected(User.AuthGroup.INSTRUCTOR) def resolve_opensearch_data( self, info, exercise_id: str ) -> QuerySet[OpenSearchDataType]: access = exercise_access(info.context, int(exercise_id)) if access.group < User.AuthGroup.INSTRUCTOR: # TODO: return empty set; OpenSearchDataType is graphene.ObjectType so no .objects is available raise PermissionDenied( "User does not have access to OpenSearch data for this exercise" ) from running_exercise.lib.opensearch_client import ( get_exercise_opensearch_data, ) data = get_exercise_opensearch_data(int(exercise_id)) return data