From 4476688073caf653798a5efb3efb1619b181c969 Mon Sep 17 00:00:00 2001 From: "Michael J. Stealey" Date: Mon, 23 Dec 2024 15:41:30 -0500 Subject: [PATCH] [#90, #91] quotas, journey-tracker endpoints --- env.template | 3 + server/.swagger-codegen/VERSION | 2 +- server/swagger_server/__main__.py | 1 + .../controllers/journey_tracker_controller.py | 26 + .../controllers/quotas_controller.py | 90 ++++ .../swagger_server/database/models/quotas.py | 54 ++ server/swagger_server/models/__init__.py | 7 + .../models/journey_tracker_people.py | 142 +++++ .../models/journey_tracker_people_one.py | 244 +++++++++ server/swagger_server/models/projects_one.py | 29 +- server/swagger_server/models/quotas.py | 247 +++++++++ .../swagger_server/models/quotas_details.py | 142 +++++ server/swagger_server/models/quotas_one.py | 244 +++++++++ server/swagger_server/models/quotas_post.py | 174 +++++++ server/swagger_server/models/quotas_put.py | 166 ++++++ .../response_code/cors_response.py | 4 +- .../response_code/decorators.py | 3 + .../journey_tracker_controller.py | 87 ++++ .../response_code/journey_tracker_utils.py | 32 ++ .../response_code/projects_controller.py | 12 +- .../response_code/projects_utils.py | 22 + .../response_code/quotas_controller.py | 385 ++++++++++++++ .../response_code/quotas_utils.py | 51 ++ .../response_code/vouch_utils.py | 16 +- server/swagger_server/swagger/swagger.yaml | 488 +++++++++++++++++- .../test/test_journey_tracker_controller.py | 37 ++ .../test/test_quotas_controller.py | 92 ++++ 27 files changed, 2788 insertions(+), 12 deletions(-) create mode 100644 server/swagger_server/controllers/journey_tracker_controller.py create mode 100644 server/swagger_server/controllers/quotas_controller.py create mode 100644 server/swagger_server/database/models/quotas.py create mode 100644 server/swagger_server/models/journey_tracker_people.py create mode 100644 server/swagger_server/models/journey_tracker_people_one.py create mode 100644 server/swagger_server/models/quotas.py create mode 100644 server/swagger_server/models/quotas_details.py create mode 100644 server/swagger_server/models/quotas_one.py create mode 100644 server/swagger_server/models/quotas_post.py create mode 100644 server/swagger_server/models/quotas_put.py create mode 100644 server/swagger_server/response_code/journey_tracker_controller.py create mode 100644 server/swagger_server/response_code/journey_tracker_utils.py create mode 100644 server/swagger_server/response_code/quotas_controller.py create mode 100644 server/swagger_server/response_code/quotas_utils.py create mode 100644 server/swagger_server/test/test_journey_tracker_controller.py create mode 100644 server/swagger_server/test/test_quotas_controller.py diff --git a/env.template b/env.template index 05538b6..93280a7 100644 --- a/env.template +++ b/env.template @@ -91,6 +91,9 @@ export PROJECTS_RENEWAL_PERIOD_IN_DAYS=365 ### Ansible export ANSIBLE_AUTHORIZATION_TOKEN='xxxx-xxxx-xxxx-xxxx' +### Read Only Auth +export READONLY_AUTHORIZATION_TOKEN='xxxx-xxxx-xxxx-xxxx' + ### Services Auth export SERVICES_AUTHORIZATION_TOKEN='xxxx-xxxx-xxxx-xxxx' diff --git a/server/.swagger-codegen/VERSION b/server/.swagger-codegen/VERSION index b1d249f..8515c05 100644 --- a/server/.swagger-codegen/VERSION +++ b/server/.swagger-codegen/VERSION @@ -1 +1 @@ -3.0.56 \ No newline at end of file +3.0.64 \ No newline at end of file diff --git a/server/swagger_server/__main__.py b/server/swagger_server/__main__.py index 2747152..4d6b402 100644 --- a/server/swagger_server/__main__.py +++ b/server/swagger_server/__main__.py @@ -25,6 +25,7 @@ from swagger_server.database.models.storage import FabricStorage, StorageSites from swagger_server.database.models.tasktracker import TaskTimeoutTracker from swagger_server.database.models.core_api_metrics import CoreApiMetrics +from swagger_server.database.models.quotas import FabricQuotas """ ------------------------------------------------------------------------------- END: Imports needed for alembic and flask using multiple model definition files diff --git a/server/swagger_server/controllers/journey_tracker_controller.py b/server/swagger_server/controllers/journey_tracker_controller.py new file mode 100644 index 0000000..9a8e3b7 --- /dev/null +++ b/server/swagger_server/controllers/journey_tracker_controller.py @@ -0,0 +1,26 @@ +import connexion +import six + +from swagger_server.models.journey_tracker_people import JourneyTrackerPeople # noqa: E501 +from swagger_server.models.status400_bad_request import Status400BadRequest # noqa: E501 +from swagger_server.models.status401_unauthorized import Status401Unauthorized # noqa: E501 +from swagger_server.models.status403_forbidden import Status403Forbidden # noqa: E501 +from swagger_server.models.status404_not_found import Status404NotFound # noqa: E501 +from swagger_server.models.status500_internal_server_error import Status500InternalServerError # noqa: E501 +from swagger_server import util +from swagger_server.response_code import journey_tracker_controller as rc + + +def journey_tracker_people_get(since_date, until_date=None): # noqa: E501 + """Get people information for Journey Tracker + + Get people information for Journey Tracker # noqa: E501 + + :param since_date: starting date to search from + :type since_date: str + :param until_date: ending date to search to + :type until_date: str + + :rtype: JourneyTrackerPeople + """ + return rc.journey_tracker_people_get(since_date, until_date) diff --git a/server/swagger_server/controllers/quotas_controller.py b/server/swagger_server/controllers/quotas_controller.py new file mode 100644 index 0000000..18ccdee --- /dev/null +++ b/server/swagger_server/controllers/quotas_controller.py @@ -0,0 +1,90 @@ +import connexion +import six + +from swagger_server.models.quotas import Quotas # noqa: E501 +from swagger_server.models.quotas_details import QuotasDetails # noqa: E501 +from swagger_server.models.quotas_post import QuotasPost # noqa: E501 +from swagger_server.models.quotas_put import QuotasPut # noqa: E501 +from swagger_server.models.status200_ok_no_content import Status200OkNoContent # noqa: E501 +from swagger_server.models.status400_bad_request import Status400BadRequest # noqa: E501 +from swagger_server.models.status401_unauthorized import Status401Unauthorized # noqa: E501 +from swagger_server.models.status403_forbidden import Status403Forbidden # noqa: E501 +from swagger_server.models.status404_not_found import Status404NotFound # noqa: E501 +from swagger_server.models.status500_internal_server_error import Status500InternalServerError # noqa: E501 +from swagger_server import util +from swagger_server.response_code import quotas_controller as rc + + +def quotas_get(project_uuid=None, offset=None, limit=None): # noqa: E501 + """Get list of Resource Quotas + + Get list of Resource Quotas # noqa: E501 + + :param project_uuid: project uuid + :type project_uuid: str + :param offset: number of items to skip before starting to collect the result set + :type offset: int + :param limit: maximum number of results to return per page (1 or more) + :type limit: int + + :rtype: Quotas + """ + return rc.quotas_get(project_uuid, offset, limit) + + +def quotas_post(body=None): # noqa: E501 + """Create new Resource Quota + + Create new Resource Quota # noqa: E501 + + :param body: Create a new Resource Quota + :type body: dict | bytes + + :rtype: Quotas + """ + if connexion.request.is_json: + body = QuotasPost.from_dict(connexion.request.get_json()) # noqa: E501 + return rc.quotas_post(body) + + +def quotas_uuid_delete(uuid): # noqa: E501 + """Delete single Resource Quota by UUID + + Delete single Resource Quota by UUID # noqa: E501 + + :param uuid: universally unique identifier + :type uuid: str + + :rtype: Status200OkNoContent + """ + return rc.quotas_uuid_delete(uuid) + + +def quotas_uuid_get(uuid): # noqa: E501 + """Get single Resource Quota by UUID + + Get single Resource Quota by UUID # noqa: E501 + + :param uuid: universally unique identifier + :type uuid: str + + :rtype: QuotasDetails + """ + return rc.quotas_uuid_get(uuid) + + +def quotas_uuid_put(uuid, body=None): # noqa: E501 + """Update single Resource Quota by UUID + + Update single Resource Quota by UUID # noqa: E501 + + :param uuid: universally unique identifier + :type uuid: str + :param body: Update a Resource Quota + :type body: dict | bytes + + :rtype: Status200OkNoContent + """ + if connexion.request.is_json: + body = QuotasPut.from_dict(connexion.request.get_json()) # noqa: E501 + return rc.quotas_uuid_put(uuid, body) diff --git a/server/swagger_server/database/models/quotas.py b/server/swagger_server/database/models/quotas.py new file mode 100644 index 0000000..9688b0b --- /dev/null +++ b/server/swagger_server/database/models/quotas.py @@ -0,0 +1,54 @@ +import enum + +from sqlalchemy import UniqueConstraint + +from swagger_server.database.db import db + + +# Enum for Resource Types +class EnumResourceTypes(enum.Enum): + core = "Core" + disk = "Disk" + fpga = "FPGA" + gpu = "GPU" + nvme = "NVME" + p4 = "P4" + ram = "RAM" + sharednic = "SharedNIC" + smartnic = "SmartNIC" + storage = "Storage" + + +# Enum for Resource Units +class EnumResourceUnits(enum.Enum): + hours = "Hours" + + +class FabricQuotas(db.Model): + """ + Quotas - Control Framework quotas for projects + - created_at = UTC datetime + - id - primary key + - project_uuid = UUID as string + - quota_limit = Float + - quota_used = Float + - resource_type = in [p4, core, ram, disk, gpu, smartnic, sharednic, fpga, nvme, storage] as string + - resource_unit = in [hours, ...] as string + - updated_at = UTC datetime + - uuid = UUID as string + """ + query: db.Query + __tablename__ = 'quotas' + __table_args__ = ( + UniqueConstraint('project_uuid', 'resource_type', 'resource_unit', name='project_resource_type_unit'),) + __allow_unmapped__ = True + + created_at = db.Column(db.DateTime(timezone=True), nullable=False) + id = db.Column(db.Integer, nullable=False, primary_key=True) + project_uuid = db.Column(db.String(), nullable=False) + quota_limit = db.Column(db.Float, nullable=False) + quota_used = db.Column(db.Float, nullable=False) + resource_type = db.Column(db.Enum(EnumResourceTypes), default=EnumResourceTypes.core, nullable=False) + resource_unit = db.Column(db.Enum(EnumResourceUnits), default=EnumResourceUnits.hours, nullable=False) + updated_at = db.Column(db.DateTime(timezone=True), nullable=False) + uuid = db.Column(db.String(), primary_key=False, nullable=False) diff --git a/server/swagger_server/models/__init__.py b/server/swagger_server/models/__init__.py index bb13a12..d4aabcd 100644 --- a/server/swagger_server/models/__init__.py +++ b/server/swagger_server/models/__init__.py @@ -18,6 +18,8 @@ from swagger_server.models.check_cookie import CheckCookie from swagger_server.models.check_cookie_results import CheckCookieResults from swagger_server.models.core_api_metrics import CoreApiMetrics +from swagger_server.models.journey_tracker_people import JourneyTrackerPeople +from swagger_server.models.journey_tracker_people_one import JourneyTrackerPeopleOne from swagger_server.models.people import People from swagger_server.models.people_details import PeopleDetails from swagger_server.models.people_one import PeopleOne @@ -48,6 +50,11 @@ from swagger_server.models.projects_tags_patch import ProjectsTagsPatch from swagger_server.models.projects_token_holders_patch import ProjectsTokenHoldersPatch from swagger_server.models.projects_topics_patch import ProjectsTopicsPatch +from swagger_server.models.quotas import Quotas +from swagger_server.models.quotas_details import QuotasDetails +from swagger_server.models.quotas_one import QuotasOne +from swagger_server.models.quotas_post import QuotasPost +from swagger_server.models.quotas_put import QuotasPut from swagger_server.models.reference import Reference from swagger_server.models.service_auth_details import ServiceAuthDetails from swagger_server.models.service_auth_one import ServiceAuthOne diff --git a/server/swagger_server/models/journey_tracker_people.py b/server/swagger_server/models/journey_tracker_people.py new file mode 100644 index 0000000..a588053 --- /dev/null +++ b/server/swagger_server/models/journey_tracker_people.py @@ -0,0 +1,142 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from swagger_server.models.base_model_ import Model +from swagger_server.models.journey_tracker_people_one import JourneyTrackerPeopleOne # noqa: F401,E501 +from swagger_server.models.status200_ok_single import Status200OkSingle # noqa: F401,E501 +from swagger_server import util + + +class JourneyTrackerPeople(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, results: List[JourneyTrackerPeopleOne]=None, size: int=1, status: int=200, type: str=None): # noqa: E501 + """JourneyTrackerPeople - a model defined in Swagger + + :param results: The results of this JourneyTrackerPeople. # noqa: E501 + :type results: List[JourneyTrackerPeopleOne] + :param size: The size of this JourneyTrackerPeople. # noqa: E501 + :type size: int + :param status: The status of this JourneyTrackerPeople. # noqa: E501 + :type status: int + :param type: The type of this JourneyTrackerPeople. # noqa: E501 + :type type: str + """ + self.swagger_types = { + 'results': List[JourneyTrackerPeopleOne], + 'size': int, + 'status': int, + 'type': str + } + + self.attribute_map = { + 'results': 'results', + 'size': 'size', + 'status': 'status', + 'type': 'type' + } + self._results = results + self._size = size + self._status = status + self._type = type + + @classmethod + def from_dict(cls, dikt) -> 'JourneyTrackerPeople': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The journey_tracker_people of this JourneyTrackerPeople. # noqa: E501 + :rtype: JourneyTrackerPeople + """ + return util.deserialize_model(dikt, cls) + + @property + def results(self) -> List[JourneyTrackerPeopleOne]: + """Gets the results of this JourneyTrackerPeople. + + + :return: The results of this JourneyTrackerPeople. + :rtype: List[JourneyTrackerPeopleOne] + """ + return self._results + + @results.setter + def results(self, results: List[JourneyTrackerPeopleOne]): + """Sets the results of this JourneyTrackerPeople. + + + :param results: The results of this JourneyTrackerPeople. + :type results: List[JourneyTrackerPeopleOne] + """ + + self._results = results + + @property + def size(self) -> int: + """Gets the size of this JourneyTrackerPeople. + + + :return: The size of this JourneyTrackerPeople. + :rtype: int + """ + return self._size + + @size.setter + def size(self, size: int): + """Sets the size of this JourneyTrackerPeople. + + + :param size: The size of this JourneyTrackerPeople. + :type size: int + """ + + self._size = size + + @property + def status(self) -> int: + """Gets the status of this JourneyTrackerPeople. + + + :return: The status of this JourneyTrackerPeople. + :rtype: int + """ + return self._status + + @status.setter + def status(self, status: int): + """Sets the status of this JourneyTrackerPeople. + + + :param status: The status of this JourneyTrackerPeople. + :type status: int + """ + + self._status = status + + @property + def type(self) -> str: + """Gets the type of this JourneyTrackerPeople. + + + :return: The type of this JourneyTrackerPeople. + :rtype: str + """ + return self._type + + @type.setter + def type(self, type: str): + """Sets the type of this JourneyTrackerPeople. + + + :param type: The type of this JourneyTrackerPeople. + :type type: str + """ + + self._type = type diff --git a/server/swagger_server/models/journey_tracker_people_one.py b/server/swagger_server/models/journey_tracker_people_one.py new file mode 100644 index 0000000..fdf4d04 --- /dev/null +++ b/server/swagger_server/models/journey_tracker_people_one.py @@ -0,0 +1,244 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from swagger_server.models.base_model_ import Model +from swagger_server import util + + +class JourneyTrackerPeopleOne(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, active: bool=None, affiliation: str=None, deactivated_date: datetime=None, email_address: str=None, fabric_last_seen: datetime=None, fabric_roles: List[str]=None, fabric_uuid: str=None, name: str=None): # noqa: E501 + """JourneyTrackerPeopleOne - a model defined in Swagger + + :param active: The active of this JourneyTrackerPeopleOne. # noqa: E501 + :type active: bool + :param affiliation: The affiliation of this JourneyTrackerPeopleOne. # noqa: E501 + :type affiliation: str + :param deactivated_date: The deactivated_date of this JourneyTrackerPeopleOne. # noqa: E501 + :type deactivated_date: datetime + :param email_address: The email_address of this JourneyTrackerPeopleOne. # noqa: E501 + :type email_address: str + :param fabric_last_seen: The fabric_last_seen of this JourneyTrackerPeopleOne. # noqa: E501 + :type fabric_last_seen: datetime + :param fabric_roles: The fabric_roles of this JourneyTrackerPeopleOne. # noqa: E501 + :type fabric_roles: List[str] + :param fabric_uuid: The fabric_uuid of this JourneyTrackerPeopleOne. # noqa: E501 + :type fabric_uuid: str + :param name: The name of this JourneyTrackerPeopleOne. # noqa: E501 + :type name: str + """ + self.swagger_types = { + 'active': bool, + 'affiliation': str, + 'deactivated_date': datetime, + 'email_address': str, + 'fabric_last_seen': datetime, + 'fabric_roles': List[str], + 'fabric_uuid': str, + 'name': str + } + + self.attribute_map = { + 'active': 'active', + 'affiliation': 'affiliation', + 'deactivated_date': 'deactivated_date', + 'email_address': 'email_address', + 'fabric_last_seen': 'fabric_last_seen', + 'fabric_roles': 'fabric_roles', + 'fabric_uuid': 'fabric_uuid', + 'name': 'name' + } + self._active = active + self._affiliation = affiliation + self._deactivated_date = deactivated_date + self._email_address = email_address + self._fabric_last_seen = fabric_last_seen + self._fabric_roles = fabric_roles + self._fabric_uuid = fabric_uuid + self._name = name + + @classmethod + def from_dict(cls, dikt) -> 'JourneyTrackerPeopleOne': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The journey_tracker_people_one of this JourneyTrackerPeopleOne. # noqa: E501 + :rtype: JourneyTrackerPeopleOne + """ + return util.deserialize_model(dikt, cls) + + @property + def active(self) -> bool: + """Gets the active of this JourneyTrackerPeopleOne. + + + :return: The active of this JourneyTrackerPeopleOne. + :rtype: bool + """ + return self._active + + @active.setter + def active(self, active: bool): + """Sets the active of this JourneyTrackerPeopleOne. + + + :param active: The active of this JourneyTrackerPeopleOne. + :type active: bool + """ + + self._active = active + + @property + def affiliation(self) -> str: + """Gets the affiliation of this JourneyTrackerPeopleOne. + + + :return: The affiliation of this JourneyTrackerPeopleOne. + :rtype: str + """ + return self._affiliation + + @affiliation.setter + def affiliation(self, affiliation: str): + """Sets the affiliation of this JourneyTrackerPeopleOne. + + + :param affiliation: The affiliation of this JourneyTrackerPeopleOne. + :type affiliation: str + """ + + self._affiliation = affiliation + + @property + def deactivated_date(self) -> datetime: + """Gets the deactivated_date of this JourneyTrackerPeopleOne. + + + :return: The deactivated_date of this JourneyTrackerPeopleOne. + :rtype: datetime + """ + return self._deactivated_date + + @deactivated_date.setter + def deactivated_date(self, deactivated_date: datetime): + """Sets the deactivated_date of this JourneyTrackerPeopleOne. + + + :param deactivated_date: The deactivated_date of this JourneyTrackerPeopleOne. + :type deactivated_date: datetime + """ + + self._deactivated_date = deactivated_date + + @property + def email_address(self) -> str: + """Gets the email_address of this JourneyTrackerPeopleOne. + + + :return: The email_address of this JourneyTrackerPeopleOne. + :rtype: str + """ + return self._email_address + + @email_address.setter + def email_address(self, email_address: str): + """Sets the email_address of this JourneyTrackerPeopleOne. + + + :param email_address: The email_address of this JourneyTrackerPeopleOne. + :type email_address: str + """ + + self._email_address = email_address + + @property + def fabric_last_seen(self) -> datetime: + """Gets the fabric_last_seen of this JourneyTrackerPeopleOne. + + + :return: The fabric_last_seen of this JourneyTrackerPeopleOne. + :rtype: datetime + """ + return self._fabric_last_seen + + @fabric_last_seen.setter + def fabric_last_seen(self, fabric_last_seen: datetime): + """Sets the fabric_last_seen of this JourneyTrackerPeopleOne. + + + :param fabric_last_seen: The fabric_last_seen of this JourneyTrackerPeopleOne. + :type fabric_last_seen: datetime + """ + + self._fabric_last_seen = fabric_last_seen + + @property + def fabric_roles(self) -> List[str]: + """Gets the fabric_roles of this JourneyTrackerPeopleOne. + + + :return: The fabric_roles of this JourneyTrackerPeopleOne. + :rtype: List[str] + """ + return self._fabric_roles + + @fabric_roles.setter + def fabric_roles(self, fabric_roles: List[str]): + """Sets the fabric_roles of this JourneyTrackerPeopleOne. + + + :param fabric_roles: The fabric_roles of this JourneyTrackerPeopleOne. + :type fabric_roles: List[str] + """ + + self._fabric_roles = fabric_roles + + @property + def fabric_uuid(self) -> str: + """Gets the fabric_uuid of this JourneyTrackerPeopleOne. + + + :return: The fabric_uuid of this JourneyTrackerPeopleOne. + :rtype: str + """ + return self._fabric_uuid + + @fabric_uuid.setter + def fabric_uuid(self, fabric_uuid: str): + """Sets the fabric_uuid of this JourneyTrackerPeopleOne. + + + :param fabric_uuid: The fabric_uuid of this JourneyTrackerPeopleOne. + :type fabric_uuid: str + """ + + self._fabric_uuid = fabric_uuid + + @property + def name(self) -> str: + """Gets the name of this JourneyTrackerPeopleOne. + + + :return: The name of this JourneyTrackerPeopleOne. + :rtype: str + """ + return self._name + + @name.setter + def name(self, name: str): + """Sets the name of this JourneyTrackerPeopleOne. + + + :param name: The name of this JourneyTrackerPeopleOne. + :type name: str + """ + + self._name = name diff --git a/server/swagger_server/models/projects_one.py b/server/swagger_server/models/projects_one.py index e70b93c..702f401 100644 --- a/server/swagger_server/models/projects_one.py +++ b/server/swagger_server/models/projects_one.py @@ -11,6 +11,7 @@ from swagger_server.models.profile_projects import ProfileProjects # noqa: F401,E501 from swagger_server.models.project_funding import ProjectFunding # noqa: F401,E501 from swagger_server.models.project_membership import ProjectMembership # noqa: F401,E501 +from swagger_server.models.quotas_one import QuotasOne # noqa: F401,E501 from swagger_server.models.reference import Reference # noqa: F401,E501 from swagger_server.models.storage_one import StorageOne # noqa: F401,E501 from swagger_server import util @@ -21,7 +22,7 @@ class ProjectsOne(Model): Do not edit the class manually. """ - def __init__(self, active: bool=True, communities: List[str]=None, created: datetime=None, description: str=None, expires_on: str=None, fabric_matrix: Reference=None, facility: str=None, is_locked: bool=False, is_public: bool=True, memberships: ProjectMembership=None, modified: datetime=None, name: str=None, preferences: Preferences=None, profile: ProfileProjects=None, project_creators: List[Person]=None, project_funding: List[ProjectFunding]=None, project_members: List[Person]=None, project_owners: List[Person]=None, project_storage: List[StorageOne]=None, project_type: str=None, tags: List[str]=None, token_holders: List[Person]=None, topics: List[str]=None, uuid: str=None): # noqa: E501 + def __init__(self, active: bool=True, communities: List[str]=None, created: datetime=None, description: str=None, expires_on: str=None, fabric_matrix: Reference=None, facility: str=None, is_locked: bool=False, is_public: bool=True, memberships: ProjectMembership=None, modified: datetime=None, name: str=None, preferences: Preferences=None, profile: ProfileProjects=None, project_creators: List[Person]=None, project_funding: List[ProjectFunding]=None, project_members: List[Person]=None, project_owners: List[Person]=None, project_storage: List[StorageOne]=None, project_type: str=None, quotas: List[QuotasOne]=None, tags: List[str]=None, token_holders: List[Person]=None, topics: List[str]=None, uuid: str=None): # noqa: E501 """ProjectsOne - a model defined in Swagger :param active: The active of this ProjectsOne. # noqa: E501 @@ -64,6 +65,8 @@ def __init__(self, active: bool=True, communities: List[str]=None, created: date :type project_storage: List[StorageOne] :param project_type: The project_type of this ProjectsOne. # noqa: E501 :type project_type: str + :param quotas: The quotas of this ProjectsOne. # noqa: E501 + :type quotas: List[QuotasOne] :param tags: The tags of this ProjectsOne. # noqa: E501 :type tags: List[str] :param token_holders: The token_holders of this ProjectsOne. # noqa: E501 @@ -94,6 +97,7 @@ def __init__(self, active: bool=True, communities: List[str]=None, created: date 'project_owners': List[Person], 'project_storage': List[StorageOne], 'project_type': str, + 'quotas': List[QuotasOne], 'tags': List[str], 'token_holders': List[Person], 'topics': List[str], @@ -121,6 +125,7 @@ def __init__(self, active: bool=True, communities: List[str]=None, created: date 'project_owners': 'project_owners', 'project_storage': 'project_storage', 'project_type': 'project_type', + 'quotas': 'quotas', 'tags': 'tags', 'token_holders': 'token_holders', 'topics': 'topics', @@ -146,6 +151,7 @@ def __init__(self, active: bool=True, communities: List[str]=None, created: date self._project_owners = project_owners self._project_storage = project_storage self._project_type = project_type + self._quotas = quotas self._tags = tags self._token_holders = token_holders self._topics = topics @@ -594,6 +600,27 @@ def project_type(self, project_type: str): self._project_type = project_type + @property + def quotas(self) -> List[QuotasOne]: + """Gets the quotas of this ProjectsOne. + + + :return: The quotas of this ProjectsOne. + :rtype: List[QuotasOne] + """ + return self._quotas + + @quotas.setter + def quotas(self, quotas: List[QuotasOne]): + """Sets the quotas of this ProjectsOne. + + + :param quotas: The quotas of this ProjectsOne. + :type quotas: List[QuotasOne] + """ + + self._quotas = quotas + @property def tags(self) -> List[str]: """Gets the tags of this ProjectsOne. diff --git a/server/swagger_server/models/quotas.py b/server/swagger_server/models/quotas.py new file mode 100644 index 0000000..8aa5aa1 --- /dev/null +++ b/server/swagger_server/models/quotas.py @@ -0,0 +1,247 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from swagger_server.models.base_model_ import Model +from swagger_server.models.quotas_one import QuotasOne # noqa: F401,E501 +from swagger_server.models.status200_ok_paginated import Status200OkPaginated # noqa: F401,E501 +from swagger_server.models.status200_ok_paginated_links import Status200OkPaginatedLinks # noqa: F401,E501 +from swagger_server import util + + +class Quotas(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, results: List[QuotasOne]=None, limit: int=None, links: Status200OkPaginatedLinks=None, offset: int=None, size: int=None, status: int=200, total: int=None, type: str=None): # noqa: E501 + """Quotas - a model defined in Swagger + + :param results: The results of this Quotas. # noqa: E501 + :type results: List[QuotasOne] + :param limit: The limit of this Quotas. # noqa: E501 + :type limit: int + :param links: The links of this Quotas. # noqa: E501 + :type links: Status200OkPaginatedLinks + :param offset: The offset of this Quotas. # noqa: E501 + :type offset: int + :param size: The size of this Quotas. # noqa: E501 + :type size: int + :param status: The status of this Quotas. # noqa: E501 + :type status: int + :param total: The total of this Quotas. # noqa: E501 + :type total: int + :param type: The type of this Quotas. # noqa: E501 + :type type: str + """ + self.swagger_types = { + 'results': List[QuotasOne], + 'limit': int, + 'links': Status200OkPaginatedLinks, + 'offset': int, + 'size': int, + 'status': int, + 'total': int, + 'type': str + } + + self.attribute_map = { + 'results': 'results', + 'limit': 'limit', + 'links': 'links', + 'offset': 'offset', + 'size': 'size', + 'status': 'status', + 'total': 'total', + 'type': 'type' + } + self._results = results + self._limit = limit + self._links = links + self._offset = offset + self._size = size + self._status = status + self._total = total + self._type = type + + @classmethod + def from_dict(cls, dikt) -> 'Quotas': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The quotas of this Quotas. # noqa: E501 + :rtype: Quotas + """ + return util.deserialize_model(dikt, cls) + + @property + def results(self) -> List[QuotasOne]: + """Gets the results of this Quotas. + + + :return: The results of this Quotas. + :rtype: List[QuotasOne] + """ + return self._results + + @results.setter + def results(self, results: List[QuotasOne]): + """Sets the results of this Quotas. + + + :param results: The results of this Quotas. + :type results: List[QuotasOne] + """ + + self._results = results + + @property + def limit(self) -> int: + """Gets the limit of this Quotas. + + + :return: The limit of this Quotas. + :rtype: int + """ + return self._limit + + @limit.setter + def limit(self, limit: int): + """Sets the limit of this Quotas. + + + :param limit: The limit of this Quotas. + :type limit: int + """ + + self._limit = limit + + @property + def links(self) -> Status200OkPaginatedLinks: + """Gets the links of this Quotas. + + + :return: The links of this Quotas. + :rtype: Status200OkPaginatedLinks + """ + return self._links + + @links.setter + def links(self, links: Status200OkPaginatedLinks): + """Sets the links of this Quotas. + + + :param links: The links of this Quotas. + :type links: Status200OkPaginatedLinks + """ + + self._links = links + + @property + def offset(self) -> int: + """Gets the offset of this Quotas. + + + :return: The offset of this Quotas. + :rtype: int + """ + return self._offset + + @offset.setter + def offset(self, offset: int): + """Sets the offset of this Quotas. + + + :param offset: The offset of this Quotas. + :type offset: int + """ + + self._offset = offset + + @property + def size(self) -> int: + """Gets the size of this Quotas. + + + :return: The size of this Quotas. + :rtype: int + """ + return self._size + + @size.setter + def size(self, size: int): + """Sets the size of this Quotas. + + + :param size: The size of this Quotas. + :type size: int + """ + + self._size = size + + @property + def status(self) -> int: + """Gets the status of this Quotas. + + + :return: The status of this Quotas. + :rtype: int + """ + return self._status + + @status.setter + def status(self, status: int): + """Sets the status of this Quotas. + + + :param status: The status of this Quotas. + :type status: int + """ + + self._status = status + + @property + def total(self) -> int: + """Gets the total of this Quotas. + + + :return: The total of this Quotas. + :rtype: int + """ + return self._total + + @total.setter + def total(self, total: int): + """Sets the total of this Quotas. + + + :param total: The total of this Quotas. + :type total: int + """ + + self._total = total + + @property + def type(self) -> str: + """Gets the type of this Quotas. + + + :return: The type of this Quotas. + :rtype: str + """ + return self._type + + @type.setter + def type(self, type: str): + """Sets the type of this Quotas. + + + :param type: The type of this Quotas. + :type type: str + """ + + self._type = type diff --git a/server/swagger_server/models/quotas_details.py b/server/swagger_server/models/quotas_details.py new file mode 100644 index 0000000..003bff4 --- /dev/null +++ b/server/swagger_server/models/quotas_details.py @@ -0,0 +1,142 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from swagger_server.models.base_model_ import Model +from swagger_server.models.quotas_one import QuotasOne # noqa: F401,E501 +from swagger_server.models.status200_ok_single import Status200OkSingle # noqa: F401,E501 +from swagger_server import util + + +class QuotasDetails(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, results: List[QuotasOne]=None, size: int=1, status: int=200, type: str=None): # noqa: E501 + """QuotasDetails - a model defined in Swagger + + :param results: The results of this QuotasDetails. # noqa: E501 + :type results: List[QuotasOne] + :param size: The size of this QuotasDetails. # noqa: E501 + :type size: int + :param status: The status of this QuotasDetails. # noqa: E501 + :type status: int + :param type: The type of this QuotasDetails. # noqa: E501 + :type type: str + """ + self.swagger_types = { + 'results': List[QuotasOne], + 'size': int, + 'status': int, + 'type': str + } + + self.attribute_map = { + 'results': 'results', + 'size': 'size', + 'status': 'status', + 'type': 'type' + } + self._results = results + self._size = size + self._status = status + self._type = type + + @classmethod + def from_dict(cls, dikt) -> 'QuotasDetails': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The quotas_details of this QuotasDetails. # noqa: E501 + :rtype: QuotasDetails + """ + return util.deserialize_model(dikt, cls) + + @property + def results(self) -> List[QuotasOne]: + """Gets the results of this QuotasDetails. + + + :return: The results of this QuotasDetails. + :rtype: List[QuotasOne] + """ + return self._results + + @results.setter + def results(self, results: List[QuotasOne]): + """Sets the results of this QuotasDetails. + + + :param results: The results of this QuotasDetails. + :type results: List[QuotasOne] + """ + + self._results = results + + @property + def size(self) -> int: + """Gets the size of this QuotasDetails. + + + :return: The size of this QuotasDetails. + :rtype: int + """ + return self._size + + @size.setter + def size(self, size: int): + """Sets the size of this QuotasDetails. + + + :param size: The size of this QuotasDetails. + :type size: int + """ + + self._size = size + + @property + def status(self) -> int: + """Gets the status of this QuotasDetails. + + + :return: The status of this QuotasDetails. + :rtype: int + """ + return self._status + + @status.setter + def status(self, status: int): + """Sets the status of this QuotasDetails. + + + :param status: The status of this QuotasDetails. + :type status: int + """ + + self._status = status + + @property + def type(self) -> str: + """Gets the type of this QuotasDetails. + + + :return: The type of this QuotasDetails. + :rtype: str + """ + return self._type + + @type.setter + def type(self, type: str): + """Sets the type of this QuotasDetails. + + + :param type: The type of this QuotasDetails. + :type type: str + """ + + self._type = type diff --git a/server/swagger_server/models/quotas_one.py b/server/swagger_server/models/quotas_one.py new file mode 100644 index 0000000..0ebfe1f --- /dev/null +++ b/server/swagger_server/models/quotas_one.py @@ -0,0 +1,244 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from swagger_server.models.base_model_ import Model +from swagger_server import util + + +class QuotasOne(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, created_at: datetime=None, project_uuid: str=None, quota_limit: float=None, quota_used: float=None, resource_type: str=None, resource_unit: str=None, updated_at: datetime=None, uuid: str=None): # noqa: E501 + """QuotasOne - a model defined in Swagger + + :param created_at: The created_at of this QuotasOne. # noqa: E501 + :type created_at: datetime + :param project_uuid: The project_uuid of this QuotasOne. # noqa: E501 + :type project_uuid: str + :param quota_limit: The quota_limit of this QuotasOne. # noqa: E501 + :type quota_limit: float + :param quota_used: The quota_used of this QuotasOne. # noqa: E501 + :type quota_used: float + :param resource_type: The resource_type of this QuotasOne. # noqa: E501 + :type resource_type: str + :param resource_unit: The resource_unit of this QuotasOne. # noqa: E501 + :type resource_unit: str + :param updated_at: The updated_at of this QuotasOne. # noqa: E501 + :type updated_at: datetime + :param uuid: The uuid of this QuotasOne. # noqa: E501 + :type uuid: str + """ + self.swagger_types = { + 'created_at': datetime, + 'project_uuid': str, + 'quota_limit': float, + 'quota_used': float, + 'resource_type': str, + 'resource_unit': str, + 'updated_at': datetime, + 'uuid': str + } + + self.attribute_map = { + 'created_at': 'created_at', + 'project_uuid': 'project_uuid', + 'quota_limit': 'quota_limit', + 'quota_used': 'quota_used', + 'resource_type': 'resource_type', + 'resource_unit': 'resource_unit', + 'updated_at': 'updated_at', + 'uuid': 'uuid' + } + self._created_at = created_at + self._project_uuid = project_uuid + self._quota_limit = quota_limit + self._quota_used = quota_used + self._resource_type = resource_type + self._resource_unit = resource_unit + self._updated_at = updated_at + self._uuid = uuid + + @classmethod + def from_dict(cls, dikt) -> 'QuotasOne': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The quotas_one of this QuotasOne. # noqa: E501 + :rtype: QuotasOne + """ + return util.deserialize_model(dikt, cls) + + @property + def created_at(self) -> datetime: + """Gets the created_at of this QuotasOne. + + + :return: The created_at of this QuotasOne. + :rtype: datetime + """ + return self._created_at + + @created_at.setter + def created_at(self, created_at: datetime): + """Sets the created_at of this QuotasOne. + + + :param created_at: The created_at of this QuotasOne. + :type created_at: datetime + """ + + self._created_at = created_at + + @property + def project_uuid(self) -> str: + """Gets the project_uuid of this QuotasOne. + + + :return: The project_uuid of this QuotasOne. + :rtype: str + """ + return self._project_uuid + + @project_uuid.setter + def project_uuid(self, project_uuid: str): + """Sets the project_uuid of this QuotasOne. + + + :param project_uuid: The project_uuid of this QuotasOne. + :type project_uuid: str + """ + + self._project_uuid = project_uuid + + @property + def quota_limit(self) -> float: + """Gets the quota_limit of this QuotasOne. + + + :return: The quota_limit of this QuotasOne. + :rtype: float + """ + return self._quota_limit + + @quota_limit.setter + def quota_limit(self, quota_limit: float): + """Sets the quota_limit of this QuotasOne. + + + :param quota_limit: The quota_limit of this QuotasOne. + :type quota_limit: float + """ + + self._quota_limit = quota_limit + + @property + def quota_used(self) -> float: + """Gets the quota_used of this QuotasOne. + + + :return: The quota_used of this QuotasOne. + :rtype: float + """ + return self._quota_used + + @quota_used.setter + def quota_used(self, quota_used: float): + """Sets the quota_used of this QuotasOne. + + + :param quota_used: The quota_used of this QuotasOne. + :type quota_used: float + """ + + self._quota_used = quota_used + + @property + def resource_type(self) -> str: + """Gets the resource_type of this QuotasOne. + + + :return: The resource_type of this QuotasOne. + :rtype: str + """ + return self._resource_type + + @resource_type.setter + def resource_type(self, resource_type: str): + """Sets the resource_type of this QuotasOne. + + + :param resource_type: The resource_type of this QuotasOne. + :type resource_type: str + """ + + self._resource_type = resource_type + + @property + def resource_unit(self) -> str: + """Gets the resource_unit of this QuotasOne. + + + :return: The resource_unit of this QuotasOne. + :rtype: str + """ + return self._resource_unit + + @resource_unit.setter + def resource_unit(self, resource_unit: str): + """Sets the resource_unit of this QuotasOne. + + + :param resource_unit: The resource_unit of this QuotasOne. + :type resource_unit: str + """ + + self._resource_unit = resource_unit + + @property + def updated_at(self) -> datetime: + """Gets the updated_at of this QuotasOne. + + + :return: The updated_at of this QuotasOne. + :rtype: datetime + """ + return self._updated_at + + @updated_at.setter + def updated_at(self, updated_at: datetime): + """Sets the updated_at of this QuotasOne. + + + :param updated_at: The updated_at of this QuotasOne. + :type updated_at: datetime + """ + + self._updated_at = updated_at + + @property + def uuid(self) -> str: + """Gets the uuid of this QuotasOne. + + + :return: The uuid of this QuotasOne. + :rtype: str + """ + return self._uuid + + @uuid.setter + def uuid(self, uuid: str): + """Sets the uuid of this QuotasOne. + + + :param uuid: The uuid of this QuotasOne. + :type uuid: str + """ + + self._uuid = uuid diff --git a/server/swagger_server/models/quotas_post.py b/server/swagger_server/models/quotas_post.py new file mode 100644 index 0000000..25ae250 --- /dev/null +++ b/server/swagger_server/models/quotas_post.py @@ -0,0 +1,174 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from swagger_server.models.base_model_ import Model +from swagger_server import util + + +class QuotasPost(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, project_uuid: str=None, quota_limit: float=None, quota_used: float=0, resource_type: str=None, resource_unit: str=None): # noqa: E501 + """QuotasPost - a model defined in Swagger + + :param project_uuid: The project_uuid of this QuotasPost. # noqa: E501 + :type project_uuid: str + :param quota_limit: The quota_limit of this QuotasPost. # noqa: E501 + :type quota_limit: float + :param quota_used: The quota_used of this QuotasPost. # noqa: E501 + :type quota_used: float + :param resource_type: The resource_type of this QuotasPost. # noqa: E501 + :type resource_type: str + :param resource_unit: The resource_unit of this QuotasPost. # noqa: E501 + :type resource_unit: str + """ + self.swagger_types = { + 'project_uuid': str, + 'quota_limit': float, + 'quota_used': float, + 'resource_type': str, + 'resource_unit': str + } + + self.attribute_map = { + 'project_uuid': 'project_uuid', + 'quota_limit': 'quota_limit', + 'quota_used': 'quota_used', + 'resource_type': 'resource_type', + 'resource_unit': 'resource_unit' + } + self._project_uuid = project_uuid + self._quota_limit = quota_limit + self._quota_used = quota_used + self._resource_type = resource_type + self._resource_unit = resource_unit + + @classmethod + def from_dict(cls, dikt) -> 'QuotasPost': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The quotas_post of this QuotasPost. # noqa: E501 + :rtype: QuotasPost + """ + return util.deserialize_model(dikt, cls) + + @property + def project_uuid(self) -> str: + """Gets the project_uuid of this QuotasPost. + + + :return: The project_uuid of this QuotasPost. + :rtype: str + """ + return self._project_uuid + + @project_uuid.setter + def project_uuid(self, project_uuid: str): + """Sets the project_uuid of this QuotasPost. + + + :param project_uuid: The project_uuid of this QuotasPost. + :type project_uuid: str + """ + if project_uuid is None: + raise ValueError("Invalid value for `project_uuid`, must not be `None`") # noqa: E501 + + self._project_uuid = project_uuid + + @property + def quota_limit(self) -> float: + """Gets the quota_limit of this QuotasPost. + + + :return: The quota_limit of this QuotasPost. + :rtype: float + """ + return self._quota_limit + + @quota_limit.setter + def quota_limit(self, quota_limit: float): + """Sets the quota_limit of this QuotasPost. + + + :param quota_limit: The quota_limit of this QuotasPost. + :type quota_limit: float + """ + if quota_limit is None: + raise ValueError("Invalid value for `quota_limit`, must not be `None`") # noqa: E501 + + self._quota_limit = quota_limit + + @property + def quota_used(self) -> float: + """Gets the quota_used of this QuotasPost. + + + :return: The quota_used of this QuotasPost. + :rtype: float + """ + return self._quota_used + + @quota_used.setter + def quota_used(self, quota_used: float): + """Sets the quota_used of this QuotasPost. + + + :param quota_used: The quota_used of this QuotasPost. + :type quota_used: float + """ + + self._quota_used = quota_used + + @property + def resource_type(self) -> str: + """Gets the resource_type of this QuotasPost. + + + :return: The resource_type of this QuotasPost. + :rtype: str + """ + return self._resource_type + + @resource_type.setter + def resource_type(self, resource_type: str): + """Sets the resource_type of this QuotasPost. + + + :param resource_type: The resource_type of this QuotasPost. + :type resource_type: str + """ + if resource_type is None: + raise ValueError("Invalid value for `resource_type`, must not be `None`") # noqa: E501 + + self._resource_type = resource_type + + @property + def resource_unit(self) -> str: + """Gets the resource_unit of this QuotasPost. + + + :return: The resource_unit of this QuotasPost. + :rtype: str + """ + return self._resource_unit + + @resource_unit.setter + def resource_unit(self, resource_unit: str): + """Sets the resource_unit of this QuotasPost. + + + :param resource_unit: The resource_unit of this QuotasPost. + :type resource_unit: str + """ + if resource_unit is None: + raise ValueError("Invalid value for `resource_unit`, must not be `None`") # noqa: E501 + + self._resource_unit = resource_unit diff --git a/server/swagger_server/models/quotas_put.py b/server/swagger_server/models/quotas_put.py new file mode 100644 index 0000000..37ff56d --- /dev/null +++ b/server/swagger_server/models/quotas_put.py @@ -0,0 +1,166 @@ +# coding: utf-8 + +from __future__ import absolute_import +from datetime import date, datetime # noqa: F401 + +from typing import List, Dict # noqa: F401 + +from swagger_server.models.base_model_ import Model +from swagger_server import util + + +class QuotasPut(Model): + """NOTE: This class is auto generated by the swagger code generator program. + + Do not edit the class manually. + """ + def __init__(self, project_uuid: str=None, quota_limit: float=None, quota_used: float=None, resource_type: str=None, resource_unit: str=None): # noqa: E501 + """QuotasPut - a model defined in Swagger + + :param project_uuid: The project_uuid of this QuotasPut. # noqa: E501 + :type project_uuid: str + :param quota_limit: The quota_limit of this QuotasPut. # noqa: E501 + :type quota_limit: float + :param quota_used: The quota_used of this QuotasPut. # noqa: E501 + :type quota_used: float + :param resource_type: The resource_type of this QuotasPut. # noqa: E501 + :type resource_type: str + :param resource_unit: The resource_unit of this QuotasPut. # noqa: E501 + :type resource_unit: str + """ + self.swagger_types = { + 'project_uuid': str, + 'quota_limit': float, + 'quota_used': float, + 'resource_type': str, + 'resource_unit': str + } + + self.attribute_map = { + 'project_uuid': 'project_uuid', + 'quota_limit': 'quota_limit', + 'quota_used': 'quota_used', + 'resource_type': 'resource_type', + 'resource_unit': 'resource_unit' + } + self._project_uuid = project_uuid + self._quota_limit = quota_limit + self._quota_used = quota_used + self._resource_type = resource_type + self._resource_unit = resource_unit + + @classmethod + def from_dict(cls, dikt) -> 'QuotasPut': + """Returns the dict as a model + + :param dikt: A dict. + :type: dict + :return: The quotas_put of this QuotasPut. # noqa: E501 + :rtype: QuotasPut + """ + return util.deserialize_model(dikt, cls) + + @property + def project_uuid(self) -> str: + """Gets the project_uuid of this QuotasPut. + + + :return: The project_uuid of this QuotasPut. + :rtype: str + """ + return self._project_uuid + + @project_uuid.setter + def project_uuid(self, project_uuid: str): + """Sets the project_uuid of this QuotasPut. + + + :param project_uuid: The project_uuid of this QuotasPut. + :type project_uuid: str + """ + + self._project_uuid = project_uuid + + @property + def quota_limit(self) -> float: + """Gets the quota_limit of this QuotasPut. + + + :return: The quota_limit of this QuotasPut. + :rtype: float + """ + return self._quota_limit + + @quota_limit.setter + def quota_limit(self, quota_limit: float): + """Sets the quota_limit of this QuotasPut. + + + :param quota_limit: The quota_limit of this QuotasPut. + :type quota_limit: float + """ + + self._quota_limit = quota_limit + + @property + def quota_used(self) -> float: + """Gets the quota_used of this QuotasPut. + + + :return: The quota_used of this QuotasPut. + :rtype: float + """ + return self._quota_used + + @quota_used.setter + def quota_used(self, quota_used: float): + """Sets the quota_used of this QuotasPut. + + + :param quota_used: The quota_used of this QuotasPut. + :type quota_used: float + """ + + self._quota_used = quota_used + + @property + def resource_type(self) -> str: + """Gets the resource_type of this QuotasPut. + + + :return: The resource_type of this QuotasPut. + :rtype: str + """ + return self._resource_type + + @resource_type.setter + def resource_type(self, resource_type: str): + """Sets the resource_type of this QuotasPut. + + + :param resource_type: The resource_type of this QuotasPut. + :type resource_type: str + """ + + self._resource_type = resource_type + + @property + def resource_unit(self) -> str: + """Gets the resource_unit of this QuotasPut. + + + :return: The resource_unit of this QuotasPut. + :rtype: str + """ + return self._resource_unit + + @resource_unit.setter + def resource_unit(self, resource_unit: str): + """Sets the resource_unit of this QuotasPut. + + + :param resource_unit: The resource_unit of this QuotasPut. + :type resource_unit: str + """ + + self._resource_unit = resource_unit diff --git a/server/swagger_server/response_code/cors_response.py b/server/swagger_server/response_code/cors_response.py index d34dc36..d4d497e 100644 --- a/server/swagger_server/response_code/cors_response.py +++ b/server/swagger_server/response_code/cors_response.py @@ -14,6 +14,8 @@ from swagger_server.models.people_details import PeopleDetails from swagger_server.models.projects import Projects from swagger_server.models.projects_details import ProjectsDetails +from swagger_server.models.quotas import Quotas +from swagger_server.models.quotas_details import QuotasDetails from swagger_server.models.service_auth_details import ServiceAuthDetails from swagger_server.models.sshkey_pair import SshkeyPair from swagger_server.models.sshkeys import Sshkeys @@ -77,7 +79,7 @@ def cors_response(req: request, status_code: int = 200, body: object = None, x_e def cors_200(response_body: Union[ ApiOptions, Bastionkeys, CheckCookie, CoreApiMetrics, People, PeopleDetails, Projects, ProjectsDetails, ServiceAuthDetails, SshkeyPair, Sshkeys, Status200OkNoContent, Announcements, Version, Whoami, - AnnouncementsDetails, TestbedInfo, StorageMany, Storage + AnnouncementsDetails, TestbedInfo, StorageMany, Storage, Quotas, QuotasDetails ] = None) -> cors_response: """ Return 200 - OK diff --git a/server/swagger_server/response_code/decorators.py b/server/swagger_server/response_code/decorators.py index 1633469..f25dd03 100644 --- a/server/swagger_server/response_code/decorators.py +++ b/server/swagger_server/response_code/decorators.py @@ -81,6 +81,9 @@ def validate_authorization_token(token: str) -> bool: # account for Ansible script which uses a static token for now if token == 'Bearer {0}'.format(os.getenv('ANSIBLE_AUTHORIZATION_TOKEN')): is_valid = True + # account for Read Only Auth which uses a static token for now + elif token == 'Bearer {0}'.format(os.getenv('READONLY_AUTHORIZATION_TOKEN')): + is_valid = True # account for Services Auth which uses a static token for now elif token == 'Bearer {0}'.format(os.getenv('SERVICES_AUTHORIZATION_TOKEN')): is_valid = True diff --git a/server/swagger_server/response_code/journey_tracker_controller.py b/server/swagger_server/response_code/journey_tracker_controller.py new file mode 100644 index 0000000..49bf79d --- /dev/null +++ b/server/swagger_server/response_code/journey_tracker_controller.py @@ -0,0 +1,87 @@ +import re +from datetime import datetime, timezone + +from swagger_server.api_logger import consoleLogger +from swagger_server.models.journey_tracker_people import JourneyTrackerPeople # noqa: E501 +from swagger_server.response_code.cors_response import cors_200, cors_400, cors_401, cors_500 +from swagger_server.response_code.decorators import login_or_token_required +from swagger_server.response_code.people_utils import get_person_by_login_claims +from swagger_server.response_code.journey_tracker_utils import journey_tracker_people_by_since_date +from swagger_server.response_code.vouch_utils import IdSourceEnum + +TZISO = r"^.+\+[\d]{2}:[\d]{2}$" +TZPYTHON = r"^.+\+[\d]{4}$" +DESCRIPTION_REGEX = r"^[\w\s\-'\.@_()/]{5,255}$" +COMMENT_LENGTH = 100 + + +@login_or_token_required +def journey_tracker_people_get(since_date, until_date=None): # noqa: E501 + """Get people information for Journey Tracker + + Get people information for Journey Tracker # noqa: E501 + + :param since_date: starting date to search from + :type since_date: str + :param until_date: ending date to search to + :type until_date: str + + :rtype: JourneyTrackerPeople + """ + + # get api_user + api_user, id_source = get_person_by_login_claims() + if id_source is IdSourceEnum.READONLY.value or api_user.is_facility_operator(): + try: + try: + # validate since_date + if since_date: + since_date = str(since_date).strip() + # with +00:00 + if re.match(TZISO, since_date) is not None: + s_date = datetime.fromisoformat(since_date) + # with +0000 + elif re.match(TZPYTHON, since_date) is not None: + s_date = datetime.strptime(since_date, "%Y-%m-%d %H:%M:%S%z") + # perhaps no TZ info? add as if UTC + else: + s_date = datetime.strptime(since_date + "+0000", "%Y-%m-%d %H:%M:%S%z") + # convert to UTC + s_date = s_date.astimezone(timezone.utc) + else: + s_date = datetime.now(timezone.utc) + # validate until_date + if until_date is not None: + until_date = str(until_date).strip() + # with +00:00 + if re.match(TZISO, until_date) is not None: + u_date = datetime.fromisoformat(until_date) + # with +0000 + elif re.match(TZPYTHON, until_date) is not None: + u_date = datetime.strptime(until_date, "%Y-%m-%d %H:%M:%S%z") + # perhaps no TZ info? add as if UTC + else: + u_date = datetime.strptime(until_date + "+0000", "%Y-%m-%d %H:%M:%S%z") + # convert to UTC + u_date = u_date.astimezone(timezone.utc) + else: + u_date = datetime.now(timezone.utc) + except ValueError as exc: + details = 'Exception: since_date / until_date: {0}'.format(exc) + consoleLogger.error(details) + return cors_400(details=details) + # generate journey_tracker_people_get response + response = JourneyTrackerPeople() + response.results = journey_tracker_people_by_since_date(since_date=s_date, until_date=u_date) + response.size = len(response.results) + response.status = 200 + response.type = 'journey_tracker_people' + return cors_200(response_body=response) + except Exception as exc: + details = 'Oops! something went wrong with journey_tracker_people_get(): {0}'.format(exc) + consoleLogger.error(details) + return cors_500(details=details) + else: + return cors_401( + details="Permission Denied: must be fabric read-only service user or fabric facility operator", + ) diff --git a/server/swagger_server/response_code/journey_tracker_utils.py b/server/swagger_server/response_code/journey_tracker_utils.py new file mode 100644 index 0000000..14886c8 --- /dev/null +++ b/server/swagger_server/response_code/journey_tracker_utils.py @@ -0,0 +1,32 @@ +from datetime import datetime + +from swagger_server.api_logger import consoleLogger +from swagger_server.database.models.people import FabricPeople, Organizations, FabricRoles +from swagger_server.models.journey_tracker_people_one import JourneyTrackerPeopleOne + + +def journey_tracker_people_by_since_date(since_date: datetime = None, until_date: datetime = None) -> [ + JourneyTrackerPeopleOne]: + jt_people = [] + results = FabricPeople.query.filter( + FabricPeople.updated >= since_date, + FabricPeople.updated <= until_date, + ).order_by(FabricPeople.updated.desc()).all() + for res in results: + try: + p = JourneyTrackerPeopleOne() + p.active = res.active + org = Organizations.query.filter_by(id=res.org_affiliation).one_or_none() + p.affiliation = org.organization if org else None + p.deactivated_date = None + p.email_address = res.preferred_email + p.fabric_last_seen = str(res.updated) + roles = FabricRoles.query.filter_by(people_id=res.id).order_by('name').all() + p.fabric_roles = [r.name for r in roles] + p.fabric_uuid = res.uuid + p.name = res.display_name + jt_people.append(p) + except Exception as exc: + consoleLogger.error('journey_tracker_utils.journey_tracker_people_by_since_date: {0}'.format(exc)) + consoleLogger.info('journey_tracker_utils.journey_tracker_people_by_since_date: {0} records returned'.format(len(jt_people))) + return jt_people diff --git a/server/swagger_server/response_code/projects_controller.py b/server/swagger_server/response_code/projects_controller.py index 262bcfc..b7d5848 100644 --- a/server/swagger_server/response_code/projects_controller.py +++ b/server/swagger_server/response_code/projects_controller.py @@ -42,7 +42,7 @@ from swagger_server.response_code.profiles_utils import delete_profile_projects, get_fabric_matrix, \ get_profile_projects, update_profiles_projects_keywords, update_profiles_projects_references from swagger_server.response_code.projects_utils import create_fabric_project_from_api, get_project_membership, \ - get_project_tags, get_projects_personnel, get_projects_storage, update_projects_communities, \ + get_project_tags, get_projects_personnel, get_projects_quotas, get_projects_storage, update_projects_communities, \ update_projects_personnel, update_projects_project_funding, update_projects_tags, update_projects_token_holders, \ update_projects_topics from swagger_server.response_code.response_utils import is_valid_url @@ -675,10 +675,12 @@ def projects_uuid_delete(uuid: str): # noqa: E501 # delete Profile delete_profile_projects(api_user=api_user, fab_project=fab_project) # remove project_creators - update_projects_personnel(api_user=api_user, fab_project=fab_project, personnel=[], personnel_type='creators', + update_projects_personnel(api_user=api_user, fab_project=fab_project, personnel=[], + personnel_type='creators', operation='batch') # remove project_members - update_projects_personnel(api_user=api_user, fab_project=fab_project, personnel=[], personnel_type='members', + update_projects_personnel(api_user=api_user, fab_project=fab_project, personnel=[], + personnel_type='members', operation='batch') # remove project_owners update_projects_personnel(api_user=api_user, fab_project=fab_project, personnel=[], personnel_type='owners', @@ -930,6 +932,7 @@ def projects_uuid_get(uuid: str) -> ProjectsDetails: # noqa: E501 project_one.project_members = get_projects_personnel(fab_project=fab_project, personnel_type='members') project_one.project_owners = get_projects_personnel(fab_project=fab_project, personnel_type='owners') project_one.project_storage = get_projects_storage(fab_project=fab_project) + project_one.quotas = get_projects_quotas(fab_project=fab_project) project_one.tags = [t.tag for t in fab_project.tags] project_one.token_holders = get_projects_personnel(fab_project=fab_project, personnel_type='tokens') # set remaining attributes for everyone else @@ -1114,7 +1117,8 @@ def projects_uuid_patch(uuid: str = None, body: ProjectsPatch = None) -> Status2 # check for project type try: if len(body.project_type) != 0: - if body.project_type in [EnumProjectTypes.maintenance.name, EnumProjectTypes.industry.name] and not api_user.is_facility_operator(): + if body.project_type in [EnumProjectTypes.maintenance.name, + EnumProjectTypes.industry.name] and not api_user.is_facility_operator(): details = 'Invalid project type: {0}, must be set by facility-operator'.format(body.project_type) consoleLogger.error(details) return cors_400(details=details) diff --git a/server/swagger_server/response_code/projects_utils.py b/server/swagger_server/response_code/projects_utils.py index 488a58b..3d705f6 100644 --- a/server/swagger_server/response_code/projects_utils.py +++ b/server/swagger_server/response_code/projects_utils.py @@ -16,6 +16,8 @@ from swagger_server.response_code.preferences_utils import create_projects_preferences from swagger_server.response_code.profiles_utils import create_profile_projects from swagger_server.response_code.response_utils import array_difference +from swagger_server.models.quotas_one import QuotasOne +from swagger_server.database.models.quotas import FabricQuotas def project_funding_to_array(n): @@ -271,6 +273,26 @@ def get_projects_personnel(fab_project: FabricProjects = None, personnel_type: s return personnel_data +# Storage - Projects +def get_projects_quotas(fab_project: FabricProjects = None) -> [QuotasOne]: + """ + Retrieve quota objects associated to the project + """ + project_quotas = [] + fab_quotas = FabricQuotas.query.filter_by(project_uuid=fab_project.uuid).order_by('resource_type').all() + for q in fab_quotas: + # set quota attributes + quota = QuotasOne() + quota.quota_limit = q.quota_limit + quota.quota_used = q.quota_used + quota.resource_type = q.resource_type.value + quota.resource_unit = q.resource_unit.value + # add quota to project_quotas + project_quotas.append(quota) + + return project_quotas + + # Storage - Projects def get_projects_storage(fab_project: FabricProjects = None) -> [StorageOne]: """ diff --git a/server/swagger_server/response_code/quotas_controller.py b/server/swagger_server/response_code/quotas_controller.py new file mode 100644 index 0000000..79324c8 --- /dev/null +++ b/server/swagger_server/response_code/quotas_controller.py @@ -0,0 +1,385 @@ +import math +import os +from datetime import datetime, timezone + +from swagger_server.api_logger import consoleLogger +from swagger_server.database.db import db +from swagger_server.database.models.projects import FabricProjects +from swagger_server.database.models.quotas import EnumResourceTypes, EnumResourceUnits, FabricQuotas +from swagger_server.models.quotas import Quotas # noqa: E501 +from swagger_server.models.quotas_details import QuotasDetails +from swagger_server.models.quotas_one import QuotasOne +from swagger_server.models.quotas_post import QuotasPost # noqa: E501 +from swagger_server.models.status200_ok_no_content import Status200OkNoContent, \ + Status200OkNoContentResults # noqa: E501 +from swagger_server.models.status200_ok_paginated import Status200OkPaginatedLinks +from swagger_server.response_code.cors_response import cors_200, cors_400, cors_401, cors_404, cors_500 +from swagger_server.response_code.decorators import login_or_token_required +from swagger_server.response_code.people_utils import get_person_by_login_claims +from swagger_server.response_code.quotas_utils import create_fabric_quota_from_api +from swagger_server.response_code.vouch_utils import IdSourceEnum + +# Constants +_SERVER_URL = os.getenv('CORE_API_SERVER_URL', '') + + +@login_or_token_required +def quotas_get(project_uuid: str = None, offset: int = None, limit: int = None) -> Quotas: # noqa: E501 + """Get list of Resource Quotas + + Get list of Resource Quotas # noqa: E501 + + :param project_uuid: project uuid + :type project_uuid: str + :param offset: number of items to skip before starting to collect the result set + :type offset: int + :param limit: maximum number of results to return per page (1 or more) + :type limit: int + + :rtype: Quotas + { + "created_at": "2024-12-20T15:59:22.559Z", + "project_uuid": "string", + "quota_limit": 0, + "quota_used": 0, + "resource_type": "string", + "resource_unit": "string", + "updated_at": "2024-12-20T15:59:22.559Z", + "uuid": "string" + } + """ + # get api_user + api_user, id_source = get_person_by_login_claims() + if id_source is IdSourceEnum.SERVICES.value or api_user.is_facility_operator(): + try: + # set page to retrieve + _page = int((offset + limit) / limit) + # get paginated search results + if project_uuid: + fab_project = FabricProjects.query.filter_by(uuid=project_uuid).one_or_none() + if fab_project: + results_page = FabricQuotas.query.filter( + FabricQuotas.project_uuid == fab_project.uuid + ).order_by(FabricQuotas.resource_type.name).paginate(page=_page, per_page=limit, error_out=False) + else: + return cors_404(details="No match for Project with uuid = '{0}'".format(project_uuid)) + else: + results_page = FabricQuotas.query.filter( + FabricQuotas.project_uuid is not None + ).order_by( + FabricQuotas.resource_type.name + ).paginate(page=_page, per_page=limit, error_out=False) + # set quotas response + response = Quotas() + response.results = [] + for item in results_page.items: + quota = QuotasOne() + quota.created_at = str(item.created_at) + quota.project_uuid = item.project_uuid + quota.quota_limit = item.quota_limit + quota.quota_used = item.quota_used + quota.resource_type = item.resource_type.value + quota.resource_unit = item.resource_unit.value + quota.updated_at = str(item.updated_at) + quota.uuid = item.uuid + # add quota to quotas response + response.results.append(quota) + # set links + response.links = Status200OkPaginatedLinks() + _URL_OFFSET_LIMIT = '{0}offset={1}&limit={2}' + base = '{0}/quotas?'.format(_SERVER_URL) if not project_uuid else '{0}/quotas?project_uuid={1}&'.format( + _SERVER_URL, project_uuid) + response.links.first = _URL_OFFSET_LIMIT.format(base, 0, limit) if results_page.pages > 0 else None + response.links.last = _URL_OFFSET_LIMIT.format(base, int((results_page.pages - 1) * limit), + limit) if results_page.pages > 0 else None + response.links.next = _URL_OFFSET_LIMIT.format(base, int(offset + limit), + limit) if results_page.has_next else None + response.links.prev = _URL_OFFSET_LIMIT.format(base, int(offset - limit), + limit) if results_page.has_prev else None + # set offset, limit and size + response.limit = limit + response.offset = offset + response.total = results_page.total + response.size = len(response.results) + response.type = 'storage' + return cors_200(response_body=response) + except Exception as exc: + details = 'Oops! something went wrong with quotas_get(): {0}'.format(exc) + consoleLogger.error(details) + return cors_500(details=details) + else: + return cors_401( + details="Permission Denied: must be fabric service user or fabric facility operator", + ) + + +def quotas_post(body: QuotasPost = None): # noqa: E501 + """Create new Resource Quota + + Create new Resource Quota # noqa: E501 + + :param body: Create a new Resource Quota + :type body: dict | bytes + { + "project_uuid": "string", + "quota_limit": 0, + "quota_used": 0, + "resource_type": "string", + "resource_unit": "string" + } + + :rtype: Quotas + """ + # get api_user + api_user, id_source = get_person_by_login_claims() + if id_source is IdSourceEnum.SERVICES.value or api_user.is_facility_operator(): + try: + # validate project_uuid + if body.project_uuid and len(body.project_uuid) != 0: + fab_project = FabricProjects.query.filter_by(uuid=body.project_uuid).one_or_none() + if not fab_project: + return cors_404(details="No match for Project with uuid = '{0}'".format(body.project_uuid)) + else: + details = "Quotas POST: provide a valid project uuid" + consoleLogger.error(details) + return cors_400(details=details) + # validate quota_limit + if not body.quota_limit and not math.isclose(float(body.quota_limit), 0.0): + details = "Quotas POST: provide a valid quota limit" + consoleLogger.error(details) + return cors_400(details=details) + # validate quota_used <-- optional - no check needed + # validate resource_type + if not body.resource_type or str(body.resource_type).casefold() not in [x.name for x in EnumResourceTypes]: + details = "Quotas POST: provide a valid resource_type" + consoleLogger.error(details) + return cors_400(details=details) + # validate resource_unit + if not body.resource_unit or str(body.resource_unit).casefold() not in [x.name for x in EnumResourceUnits]: + details = "Quotas POST: provide a valid resource_unit" + consoleLogger.error(details) + return cors_400(details=details) + # create Quota + fab_quota = create_fabric_quota_from_api(body=body) + db.session.commit() + return quotas_uuid_get(uuid=str(fab_quota.uuid)) + except Exception as exc: + details = 'Oops! something went wrong with quotas_post(): {0}'.format(exc) + consoleLogger.error(details) + return cors_500(details=details) + else: + return cors_401( + details="Permission Denied: must be fabric service user or fabric facility operator", + ) + + +def quotas_uuid_delete(uuid: str): # noqa: E501 + """Delete single Resource Quota by UUID + + Delete single Resource Quota by UUID # noqa: E501 + + :param uuid: universally unique identifier + :type uuid: str + + :rtype: Status200OkNoContent + """ + # get api_user + api_user, id_source = get_person_by_login_claims() + if id_source is IdSourceEnum.SERVICES.value or api_user.is_facility_operator(): + try: + # get Quota by uuid + fab_quota = FabricQuotas.query.filter_by(uuid=uuid).one_or_none() + if not fab_quota: + return cors_404(details="No match for Quota with uuid = '{0}'".format(uuid)) + # delete Announcement + details = "Quota: '{0}' has been successfully deleted".format(str(fab_quota.uuid)) + db.session.delete(fab_quota) + db.session.commit() + consoleLogger.info(details) + # create response + patch_info = Status200OkNoContentResults() + patch_info.details = details + response = Status200OkNoContent() + response.results = [patch_info] + response.size = len(response.results) + response.status = 200 + response.type = 'no_content' + return cors_200(response_body=response) + except Exception as exc: + details = 'Oops! something went wrong with quotas_uuid_delete(): {0}'.format(exc) + consoleLogger.error(details) + return cors_500(details=details) + else: + return cors_401( + details="Permission Denied: must be fabric service user or fabric facility operator", + ) + + +def quotas_uuid_get(uuid: str): # noqa: E501 + """Get single Resource Quota by UUID + + Get single Resource Quota by UUID # noqa: E501 + + :param uuid: universally unique identifier + :type uuid: str + + :rtype: QuotasDetails + { + "created_at": "2024-12-20T15:59:22.559Z", + "project_uuid": "string", + "quota_limit": 0, + "quota_used": 0, + "resource_type": "string", + "resource_unit": "string", + "updated_at": "2024-12-20T15:59:22.559Z", + "uuid": "string" + } + """ + # get api_user + api_user, id_source = get_person_by_login_claims() + if id_source is IdSourceEnum.SERVICES.value or api_user.is_facility_operator(): + try: + # get Quotas by uuid + fab_quota = FabricQuotas.query.filter_by(uuid=uuid).one_or_none() + if not fab_quota: + return cors_404(details="No match for Quota with uuid = '{0}'".format(uuid)) + # set QuotasOne object + quota_one = QuotasOne() + quota_one.created_at = str(fab_quota.created_at) + quota_one.project_uuid = fab_quota.project_uuid + quota_one.quota_limit = fab_quota.quota_limit + quota_one.quota_used = fab_quota.quota_used + quota_one.resource_type = fab_quota.resource_type.value + quota_one.resource_unit = fab_quota.resource_unit.value + quota_one.updated_at = str(fab_quota.updated_at) + quota_one.uuid = fab_quota.uuid + # set quota_details response + response = QuotasDetails() + response.results = [quota_one] + response.size = len(response.results) + response.status = 200 + response.type = 'quotas.details' + return cors_200(response_body=response) + except Exception as exc: + details = 'Oops! something went wrong with quotas_uuid_get(): {0}'.format(exc) + consoleLogger.error(details) + return cors_500(details=details) + else: + return cors_401( + details="Permission Denied: must be fabric service user or fabric facility operator", + ) + + +def quotas_uuid_put(uuid, body=None): # noqa: E501 + """Update single Resource Quota by UUID + + Update single Resource Quota by UUID # noqa: E501 + + :param uuid: universally unique identifier + :type uuid: str + :param body: Update a Resource Quota + :type body: dict | bytes + { + "project_uuid": "string", + "quota_limit": 0, + "quota_used": 0, + "resource_type": "string", + "resource_unit": "string" + } + + :rtype: Status200OkNoContent + """ + # get api_user + api_user, id_source = get_person_by_login_claims() + if id_source is IdSourceEnum.SERVICES.value or api_user.is_facility_operator(): + try: + # get Announcement by uuid + fab_quota = FabricQuotas.query.filter_by(uuid=uuid).one_or_none() + if not fab_quota: + return cors_404(details="No match for Quota with uuid = '{0}'".format(uuid)) + fab_quota_modified = False + # check for project_uuid + try: + if len(body.project_uuid) != 0: + fab_project = FabricProjects.query.filter_by(uuid=body.project_uuid).one_or_none() + if not fab_project: + return cors_404(details="No match for Project with uuid = '{0}'".format(body.project_uuid)) + fab_quota.project_uuid = body.project_uuid + fab_quota_modified = True + consoleLogger.info('UPDATE: FabricQuotas: uuid={0}, project_uuid={1}'.format( + fab_quota.uuid, fab_quota.project_uuid)) + except Exception as exc: + consoleLogger.info("NOP: quotas_uuid_put(): 'project_uuid' - {0}".format(exc)) + # check for quota_limit + try: + if len(body.quota_limit) != 0: + fab_quota.quota_limit = body.quota_limit + fab_quota_modified = True + consoleLogger.info('UPDATE: FabricQuotas: uuid={0}, quota_limit={1}'.format( + fab_quota.uuid, fab_quota.quota_limit)) + except Exception as exc: + consoleLogger.info("NOP: quotas_uuid_put(): 'quota_limit' - {0}".format(exc)) + # check for quota_used + try: + if len(body.quota_used) != 0: + fab_quota.quota_used = body.quota_used + fab_quota_modified = True + consoleLogger.info('UPDATE: FabricQuotas: uuid={0}, quota_used={1}'.format( + fab_quota.uuid, fab_quota.quota_used)) + except Exception as exc: + consoleLogger.info("NOP: quotas_uuid_put(): 'quota_used' - {0}".format(exc)) + # check for resource_type + try: + if len(body.resource_type) != 0: + if str(body.resource_type).casefold() not in [x.name for x in EnumResourceTypes]: + details = \ + "Quotas PUT: '{0}' is not a valid resource_type".format(body.resource_type) + consoleLogger.error(details) + return cors_400(details=details) + fab_quota.resource_type = str(body.resource_type).casefold() + fab_quota_modified = True + consoleLogger.info('UPDATE: FabricAnnouncements: uuid={0}, resource_type={1}'.format( + fab_quota.uuid, fab_quota.resource_type)) + except Exception as exc: + consoleLogger.info("NOP: quotas_uuid_put(): 'resource_type' - {0}".format(exc)) + # check for resource_unit + try: + if len(body.resource_unit) != 0: + if str(body.resource_unit).casefold() not in [x.name for x in EnumResourceUnits]: + details = \ + "Quotas PUT: '{0}' is not a valid resource_unit".format(body.resource_unit) + consoleLogger.error(details) + return cors_400(details=details) + fab_quota.resource_unit = str(body.resource_unit).casefold() + fab_quota_modified = True + consoleLogger.info('UPDATE: FabricAnnouncements: uuid={0}, resource_unit={1}'.format( + fab_quota.uuid, fab_quota.resource_type)) + except Exception as exc: + consoleLogger.info("NOP: quotas_uuid_put(): 'resource_unit' - {0}".format(exc)) + # save modified quota + try: + if fab_quota_modified: + fab_quota.updated_at = datetime.now(timezone.utc) + db.session.commit() + except Exception as exc: + db.session.rollback() + details = 'Oops! something went wrong with quotas_uuid_put(): {0}'.format(exc) + consoleLogger.error(details) + return cors_500(details=details) + # create response + patch_info = Status200OkNoContentResults() + patch_info.details = "Quotas: '{0}' has been successfully updated".format(str(fab_quota.uuid)) + response = Status200OkNoContent() + response.results = [patch_info] + response.size = len(response.results) + response.status = 200 + response.type = 'no_content' + return cors_200(response_body=response) + except Exception as exc: + details = 'Oops! something went wrong with quotas_uuid_put(): {0}'.format(exc) + consoleLogger.error(details) + return cors_500(details=details) + else: + return cors_401( + details="Permission Denied: must be fabric service user or fabric facility operator", + ) diff --git a/server/swagger_server/response_code/quotas_utils.py b/server/swagger_server/response_code/quotas_utils.py new file mode 100644 index 0000000..162affb --- /dev/null +++ b/server/swagger_server/response_code/quotas_utils.py @@ -0,0 +1,51 @@ +from datetime import datetime, timezone +from uuid import uuid4 + +from swagger_server.api_logger import consoleLogger +from swagger_server.database.db import db +from swagger_server.database.models.quotas import FabricQuotas +from swagger_server.models.quotas_post import QuotasPost +from swagger_server.response_code.cors_response import cors_500 + + +def create_fabric_quota_from_api(body: QuotasPost) -> FabricQuotas: + """ + Quotas - Control Framework quotas for projects + - created_at = UTC datetime + - id - primary key + - project_uuid = UUID as string + - quota_limit = Float + - quota_used = Float + - resource_type = in [p4, core, ram, disk, gpu, smartnic, sharednic, fpga, nvme, storage] as string + - resource_unit = in [hours, ...] as string + - updated_at = UTC datetime + - uuid = UUID as string + + { + "project_uuid": "10c0094a-abaf-4ef9-a532-2be53e2a896b", + "quota_limit": 1000.0, + "quota_used": 0.0, + "resource_type": "core", + "resource_unit": "hours" + } + """ + # create Quota + now = datetime.now(timezone.utc) + fab_quota = FabricQuotas() + fab_quota.created_at = now + fab_quota.project_uuid = body.project_uuid + fab_quota.quota_limit = body.quota_limit + fab_quota.quota_used = body.quota_used if body.quota_used else 0.0 + fab_quota.resource_type = str(body.resource_type).casefold() + fab_quota.resource_unit = str(body.resource_unit).casefold() + fab_quota.updated_at = now + fab_quota.uuid = str(uuid4()) + try: + db.session.add(fab_quota) + db.session.commit() + except Exception as exc: + db.session.rollback() + details = 'Oops! something went wrong with announcements_uuid_get(): {0}'.format(exc) + consoleLogger.error(details) + return cors_500(details=details) + return fab_quota diff --git a/server/swagger_server/response_code/vouch_utils.py b/server/swagger_server/response_code/vouch_utils.py index 5d97626..8b86cec 100644 --- a/server/swagger_server/response_code/vouch_utils.py +++ b/server/swagger_server/response_code/vouch_utils.py @@ -26,8 +26,9 @@ class IdSourceEnum(Enum): - COOKIE = 'cookie-vouch-proxy' ANSIBLE = 'token-ansible' + COOKIE = 'cookie-vouch-proxy' + READONLY = 'token-readonly' SERVICES = 'token-services' USER = 'token-user' @@ -139,6 +140,19 @@ def token_get_custom_claims(token: str) -> dict: if token == format(os.getenv('ANSIBLE_AUTHORIZATION_TOKEN')): # print('ANSIBLE AUTHORIZATION TOKEN') claims = first_valid_facility_operator() + # account for Ansible script which uses a static token for now + if token == format(os.getenv('READONLY_AUTHORIZATION_TOKEN')): + # print('SERVICES AUTHORIZATION TOKEN') + claims = { + 'aud': 'FABRIC', + 'email': None, + 'family_name': 'Services', + 'given_name': 'READONLY', + 'iss': 'core-api', + 'name': 'FABRIC Read Only', + 'source': IdSourceEnum.READONLY.value, + 'sub': None + } # account for CF Services which uses a static token for now elif token == format(os.getenv('SERVICES_AUTHORIZATION_TOKEN')): # print('SERVICES AUTHORIZATION TOKEN') diff --git a/server/swagger_server/swagger/swagger.yaml b/server/swagger_server/swagger/swagger.yaml index 13593cf..0ef3f0f 100644 --- a/server/swagger_server/swagger/swagger.yaml +++ b/server/swagger_server/swagger/swagger.yaml @@ -8,11 +8,11 @@ info: license: name: MIT url: https://opensource.org/licenses/mit-license.html - version: 1.7.0 + version: 1.8.0 servers: - url: https://127.0.0.1:8443 description: local development server (nginx) -- url: https://virtserver.swaggerhub.com/MICHAELJSTEALEY/fabric-core-api/1.7.0 +- url: https://virtserver.swaggerhub.com/MICHAELJSTEALEY/fabric-core-api/1.8.0 description: SwaggerHub API Auto Mocking tags: - name: announcements @@ -21,10 +21,14 @@ tags: description: FABRIC Check Cookie - name: core-api-metrics description: Core-API Metrics +- name: journey-tracker + description: Journey Tracker - name: people description: FABRIC People - name: projects description: FABRIC Projects +- name: quotas + description: FABRIC Resource Quotas - name: sshkeys description: FABRIC Public SSH Keys - name: storage @@ -356,6 +360,70 @@ paths: schema: $ref: '#/components/schemas/status_500_internal_server_error' x-openapi-router-controller: swagger_server.controllers.core_api_metrics_controller + /journey-tracker/people: + get: + tags: + - journey-tracker + summary: Get people information for Journey Tracker + description: Get people information for Journey Tracker + operationId: journey_tracker_people_get + parameters: + - name: since_date + in: query + description: starting date to search from + required: true + style: form + explode: true + schema: + type: string + example: 2023-01-01 16:20:15 +00:00 + - name: until_date + in: query + description: ending date to search to + required: false + style: form + explode: true + schema: + type: string + example: 2023-01-01 16:20:15 +00:00 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/journey_tracker_people' + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/status_400_bad_request' + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/status_401_unauthorized' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/status_403_forbidden' + "404": + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/status_404_not_found' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/status_500_internal_server_error' + x-openapi-router-controller: swagger_server.controllers.journey_tracker_controller /people: get: tags: @@ -2079,6 +2147,268 @@ paths: schema: $ref: '#/components/schemas/status_500_internal_server_error' x-openapi-router-controller: swagger_server.controllers.projects_controller + /quotas: + get: + tags: + - quotas + summary: Get list of Resource Quotas + description: Get list of Resource Quotas + operationId: quotas_get + parameters: + - name: project_uuid + in: query + description: project uuid + required: false + style: form + explode: true + schema: + type: string + - name: offset + in: query + description: number of items to skip before starting to collect the result + set + required: false + style: form + explode: true + schema: + minimum: 0 + type: integer + format: int32 + default: 0 + - name: limit + in: query + description: maximum number of results to return per page (1 or more) + required: false + style: form + explode: true + schema: + maximum: 200 + minimum: 1 + type: integer + format: int32 + default: 5 + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/quotas' + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/status_400_bad_request' + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/status_401_unauthorized' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/status_403_forbidden' + "404": + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/status_404_not_found' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/status_500_internal_server_error' + x-openapi-router-controller: swagger_server.controllers.quotas_controller + post: + tags: + - quotas + summary: Create new Resource Quota + description: Create new Resource Quota + operationId: quotas_post + requestBody: + $ref: '#/components/requestBodies/payload_quotas_post' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/quotas' + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/status_400_bad_request' + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/status_401_unauthorized' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/status_403_forbidden' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/status_500_internal_server_error' + x-openapi-router-controller: swagger_server.controllers.quotas_controller + /quotas/{uuid}: + get: + tags: + - quotas + summary: Get single Resource Quota by UUID + description: Get single Resource Quota by UUID + operationId: quotas_uuid_get + parameters: + - name: uuid + in: path + description: universally unique identifier + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/quotas_details' + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/status_401_unauthorized' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/status_403_forbidden' + "404": + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/status_404_not_found' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/status_500_internal_server_error' + x-openapi-router-controller: swagger_server.controllers.quotas_controller + put: + tags: + - quotas + summary: Update single Resource Quota by UUID + description: Update single Resource Quota by UUID + operationId: quotas_uuid_put + parameters: + - name: uuid + in: path + description: universally unique identifier + required: true + style: simple + explode: false + schema: + type: string + requestBody: + $ref: '#/components/requestBodies/payload_quotas_put' + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/status_200_ok_no_content' + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/status_400_bad_request' + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/status_401_unauthorized' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/status_403_forbidden' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/status_500_internal_server_error' + x-openapi-router-controller: swagger_server.controllers.quotas_controller + delete: + tags: + - quotas + summary: Delete single Resource Quota by UUID + description: Delete single Resource Quota by UUID + operationId: quotas_uuid_delete + parameters: + - name: uuid + in: path + description: universally unique identifier + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/status_200_ok_no_content' + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/status_401_unauthorized' + "403": + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/status_403_forbidden' + "404": + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/status_404_not_found' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/status_500_internal_server_error' + x-openapi-router-controller: swagger_server.controllers.quotas_controller /bastionkeys: get: tags: @@ -3016,6 +3346,39 @@ components: type: array items: type: object + journey_tracker_people: + type: object + allOf: + - $ref: '#/components/schemas/status_200_ok_single' + - type: object + properties: + results: + type: array + items: + $ref: '#/components/schemas/journey_tracker_people_one' + journey_tracker_people_one: + type: object + properties: + active: + type: boolean + affiliation: + type: string + deactivated_date: + type: string + format: date-time + email_address: + type: string + fabric_last_seen: + type: string + format: date-time + fabric_roles: + type: array + items: + type: string + fabric_uuid: + type: string + name: + type: string person: required: - name @@ -3268,6 +3631,10 @@ components: $ref: '#/components/schemas/storage_one' project_type: type: string + quotas: + type: array + items: + $ref: '#/components/schemas/quotas_one' tags: type: array items: @@ -3371,6 +3738,47 @@ components: type: string url: type: string + quotas: + type: object + allOf: + - $ref: '#/components/schemas/status_200_ok_paginated' + - type: object + properties: + results: + type: array + items: + $ref: '#/components/schemas/quotas_one' + quotas_details: + type: object + allOf: + - $ref: '#/components/schemas/status_200_ok_single' + - type: object + properties: + results: + type: array + items: + $ref: '#/components/schemas/quotas_one' + quotas_one: + type: object + properties: + created_at: + type: string + format: date-time + project_uuid: + type: string + quota_limit: + type: number + quota_used: + type: number + resource_type: + type: string + resource_unit: + type: string + updated_at: + type: string + format: date-time + uuid: + type: string sshkey_pair: type: object allOf: @@ -3701,6 +4109,38 @@ components: type: array items: $ref: '#/components/schemas/projects_funding_patch_project_funding' + quotas_post: + required: + - project_uuid + - quota_limit + - resource_type + - resource_unit + type: object + properties: + project_uuid: + type: string + quota_limit: + type: number + quota_used: + type: number + default: 0 + resource_type: + type: string + resource_unit: + type: string + quotas_put: + type: object + properties: + project_uuid: + type: string + quota_limit: + type: number + quota_used: + type: number + resource_type: + type: string + resource_unit: + type: string sshkeys_post: required: - comment @@ -4027,6 +4467,12 @@ components: directorate: type: string responses: + "200_bastionkeys": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/bastionkeys' "200_check_cookie": description: OK content: @@ -4039,6 +4485,12 @@ components: application/json: schema: $ref: '#/components/schemas/core_api_metrics' + "200_journey_tracker_people": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/journey_tracker_people' "200_people": description: OK content: @@ -4069,12 +4521,18 @@ components: application/json: schema: $ref: '#/components/schemas/projects_details' - "200_bastionkeys": + "200_quotas": description: OK content: application/json: schema: - $ref: '#/components/schemas/bastionkeys' + $ref: '#/components/schemas/quotas' + "200_quotas_details": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/quotas_details' "200_sshkeys": description: OK content: @@ -4315,6 +4773,16 @@ components: schema: type: string example: 2023-01-01 16:20:15 +00:00 + until_date: + name: until_date + in: query + description: ending date to search to + required: false + style: form + explode: true + schema: + type: string + example: 2023-01-01 16:20:15 +00:00 sort_by: name: sort_by in: query @@ -4457,6 +4925,18 @@ components: application/json: schema: $ref: '#/components/schemas/projects_funding_patch' + payload_quotas_post: + description: Create a new Resource Quota + content: + application/json: + schema: + $ref: '#/components/schemas/quotas_post' + payload_quotas_put: + description: Update a Resource Quota + content: + application/json: + schema: + $ref: '#/components/schemas/quotas_put' payload_sshkeys_post: description: Create a public/private SSH Key Pair content: diff --git a/server/swagger_server/test/test_journey_tracker_controller.py b/server/swagger_server/test/test_journey_tracker_controller.py new file mode 100644 index 0000000..ead2c9f --- /dev/null +++ b/server/swagger_server/test/test_journey_tracker_controller.py @@ -0,0 +1,37 @@ +# coding: utf-8 + +from __future__ import absolute_import + +from flask import json +from six import BytesIO + +from swagger_server.models.journey_tracker_people import JourneyTrackerPeople # noqa: E501 +from swagger_server.models.status400_bad_request import Status400BadRequest # noqa: E501 +from swagger_server.models.status401_unauthorized import Status401Unauthorized # noqa: E501 +from swagger_server.models.status403_forbidden import Status403Forbidden # noqa: E501 +from swagger_server.models.status404_not_found import Status404NotFound # noqa: E501 +from swagger_server.models.status500_internal_server_error import Status500InternalServerError # noqa: E501 +from swagger_server.test import BaseTestCase + + +class TestJourneyTrackerController(BaseTestCase): + """JourneyTrackerController integration test stubs""" + + def test_journey_tracker_people_get(self): + """Test case for journey_tracker_people_get + + Get people information for Journey Tracker + """ + query_string = [('since_date', 'since_date_example'), + ('until_date', 'until_date_example')] + response = self.client.open( + '/journey-tracker/people', + method='GET', + query_string=query_string) + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/server/swagger_server/test/test_quotas_controller.py b/server/swagger_server/test/test_quotas_controller.py new file mode 100644 index 0000000..9b79653 --- /dev/null +++ b/server/swagger_server/test/test_quotas_controller.py @@ -0,0 +1,92 @@ +# coding: utf-8 + +from __future__ import absolute_import + +from flask import json +from six import BytesIO + +from swagger_server.models.quotas import Quotas # noqa: E501 +from swagger_server.models.quotas_details import QuotasDetails # noqa: E501 +from swagger_server.models.quotas_post import QuotasPost # noqa: E501 +from swagger_server.models.quotas_put import QuotasPut # noqa: E501 +from swagger_server.models.status200_ok_no_content import Status200OkNoContent # noqa: E501 +from swagger_server.models.status400_bad_request import Status400BadRequest # noqa: E501 +from swagger_server.models.status401_unauthorized import Status401Unauthorized # noqa: E501 +from swagger_server.models.status403_forbidden import Status403Forbidden # noqa: E501 +from swagger_server.models.status404_not_found import Status404NotFound # noqa: E501 +from swagger_server.models.status500_internal_server_error import Status500InternalServerError # noqa: E501 +from swagger_server.test import BaseTestCase + + +class TestQuotasController(BaseTestCase): + """QuotasController integration test stubs""" + + def test_quotas_get(self): + """Test case for quotas_get + + Get list of Resource Quotas + """ + query_string = [('project_uuid', 'project_uuid_example'), + ('offset', 1), + ('limit', 200)] + response = self.client.open( + '/quotas', + method='GET', + query_string=query_string) + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_quotas_post(self): + """Test case for quotas_post + + Create new Resource Quota + """ + body = QuotasPost() + response = self.client.open( + '/quotas', + method='POST', + data=json.dumps(body), + content_type='application/json') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_quotas_uuid_delete(self): + """Test case for quotas_uuid_delete + + Delete single Resource Quota by UUID + """ + response = self.client.open( + '/quotas/{uuid}'.format(uuid='uuid_example'), + method='DELETE') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_quotas_uuid_get(self): + """Test case for quotas_uuid_get + + Get single Resource Quota by UUID + """ + response = self.client.open( + '/quotas/{uuid}'.format(uuid='uuid_example'), + method='GET') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + def test_quotas_uuid_put(self): + """Test case for quotas_uuid_put + + Update single Resource Quota by UUID + """ + body = QuotasPut() + response = self.client.open( + '/quotas/{uuid}'.format(uuid='uuid_example'), + method='PUT', + data=json.dumps(body), + content_type='application/json') + self.assert200(response, + 'Response body is : ' + response.data.decode('utf-8')) + + +if __name__ == '__main__': + import unittest + unittest.main()