diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2022-04-04 17:00:31 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-04-04 17:00:31 +0200 |
| commit | 38769373c7e89783d33849283586bfa0b62e8251 (patch) | |
| tree | 4fda128ee6b30018c1aa14c584cc53ade80e67f7 /opendc-web/opendc-web-api/opendc/models | |
| parent | 6021aa4278bebb34bf5603ead4b5daeabcdc4c19 (diff) | |
| parent | 527ae2230f5c2dd22f496f45d5d8e3bd4acdb854 (diff) | |
merge: Migrate to Quarkus-based web API
This pull request changes the web API to a Quarkus-based version. Currently, the OpenDC web API is written in Python (using Flask). Although Python is a powerful language to develop web services, having another language next to Kotlin/Java and JavaScript introduces some challenges.
For instance, the web API and UI lack integration with our Gradle-based build pipeline and require additional steps from the developer to start working with. Furthermore, deploying OpenDC requires having Python installed in addition to the JVM.
By converting the web API into a Quarkus application, we can enjoy further integration with our Gradle-based build pipeline and simplify the development/deployment process of OpenDC, by requiring only the JVM and Node to work with OpenDC.
## Implementation Notes :hammer_and_pick:
* Move build dependencies into version catalog
* Design unified communication protocol
* Add Quarkus API implementation
* Add new web client implementation
* Update runner to use new web client
* Fix compatibility with React.js UI
* Remove Python build steps from CI pipeline
* Update Docker deployment for new web API
* Remove obsolete database configuration
## External Dependencies :four_leaf_clover:
* Quarkus
## Breaking API Changes :warning:
* The new web API only supports SQL-based databases for storing user-data, as opposed to MongoDB currently. We intend to use H2 for development and Postgres for production.
Diffstat (limited to 'opendc-web/opendc-web-api/opendc/models')
| -rw-r--r-- | opendc-web/opendc-web-api/opendc/models/__init__.py | 0 | ||||
| -rw-r--r-- | opendc-web/opendc-web-api/opendc/models/model.py | 61 | ||||
| -rw-r--r-- | opendc-web/opendc-web-api/opendc/models/portfolio.py | 47 | ||||
| -rw-r--r-- | opendc-web/opendc-web-api/opendc/models/prefab.py | 31 | ||||
| -rw-r--r-- | opendc-web/opendc-web-api/opendc/models/project.py | 48 | ||||
| -rw-r--r-- | opendc-web/opendc-web-api/opendc/models/scenario.py | 93 | ||||
| -rw-r--r-- | opendc-web/opendc-web-api/opendc/models/topology.py | 108 | ||||
| -rw-r--r-- | opendc-web/opendc-web-api/opendc/models/trace.py | 16 |
8 files changed, 0 insertions, 404 deletions
diff --git a/opendc-web/opendc-web-api/opendc/models/__init__.py b/opendc-web/opendc-web-api/opendc/models/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/opendc-web/opendc-web-api/opendc/models/__init__.py +++ /dev/null diff --git a/opendc-web/opendc-web-api/opendc/models/model.py b/opendc-web/opendc-web-api/opendc/models/model.py deleted file mode 100644 index 28299453..00000000 --- a/opendc-web/opendc-web-api/opendc/models/model.py +++ /dev/null @@ -1,61 +0,0 @@ -from bson.objectid import ObjectId -from werkzeug.exceptions import NotFound - -from opendc.exts import db - - -class Model: - """Base class for all models.""" - - collection_name = '<specified in subclasses>' - - @classmethod - def from_id(cls, _id): - """Fetches the document with given ID from the collection.""" - if isinstance(_id, str) and len(_id) == 24: - _id = ObjectId(_id) - - return cls(db.fetch_one({'_id': _id}, cls.collection_name)) - - @classmethod - def get_all(cls): - """Fetches all documents from the collection.""" - return cls(db.fetch_all({}, cls.collection_name)) - - def __init__(self, obj): - self.obj = obj - - def get_id(self): - """Returns the ID of the enclosed object.""" - return self.obj['_id'] - - def check_exists(self): - """Raises an error if the enclosed object does not exist.""" - if self.obj is None: - raise NotFound('Entity not found.') - - def set_property(self, key, value): - """Sets the given property on the enclosed object, with support for simple nested access.""" - if '.' in key: - keys = key.split('.') - self.obj[keys[0]][keys[1]] = value - else: - self.obj[key] = value - - def insert(self): - """Inserts the enclosed object and generates a UUID for it.""" - self.obj['_id'] = ObjectId() - db.insert(self.obj, self.collection_name) - - def update(self): - """Updates the enclosed object and updates the internal reference to the newly inserted object.""" - db.update(self.get_id(), self.obj, self.collection_name) - - def delete(self): - """Deletes the enclosed object in the database, if it existed.""" - if self.obj is None: - return None - - old_object = self.obj.copy() - db.delete_one({'_id': self.get_id()}, self.collection_name) - return old_object diff --git a/opendc-web/opendc-web-api/opendc/models/portfolio.py b/opendc-web/opendc-web-api/opendc/models/portfolio.py deleted file mode 100644 index eb016947..00000000 --- a/opendc-web/opendc-web-api/opendc/models/portfolio.py +++ /dev/null @@ -1,47 +0,0 @@ -from bson import ObjectId -from marshmallow import Schema, fields - -from opendc.exts import db -from opendc.models.project import Project -from opendc.models.model import Model - - -class TargetSchema(Schema): - """ - Schema representing a target. - """ - enabledMetrics = fields.List(fields.String()) - repeatsPerScenario = fields.Integer(required=True) - - -class PortfolioSchema(Schema): - """ - Schema representing a portfolio. - """ - _id = fields.String(dump_only=True) - projectId = fields.String() - name = fields.String(required=True) - scenarioIds = fields.List(fields.String()) - targets = fields.Nested(TargetSchema) - - -class Portfolio(Model): - """Model representing a Portfolio.""" - - collection_name = 'portfolios' - - def check_user_access(self, user_id, edit_access): - """Raises an error if the user with given [user_id] has insufficient access. - - Checks access on the parent project. - - :param user_id: The User ID of the user. - :param edit_access: True when edit access should be checked, otherwise view access. - """ - project = Project.from_id(self.obj['projectId']) - project.check_user_access(user_id, edit_access) - - @classmethod - def get_for_project(cls, project_id): - """Get all portfolios for the specified project id.""" - return db.fetch_all({'projectId': ObjectId(project_id)}, cls.collection_name) diff --git a/opendc-web/opendc-web-api/opendc/models/prefab.py b/opendc-web/opendc-web-api/opendc/models/prefab.py deleted file mode 100644 index 5e4b81dc..00000000 --- a/opendc-web/opendc-web-api/opendc/models/prefab.py +++ /dev/null @@ -1,31 +0,0 @@ -from marshmallow import Schema, fields -from werkzeug.exceptions import Forbidden - -from opendc.models.topology import ObjectSchema -from opendc.models.model import Model - - -class PrefabSchema(Schema): - """ - Schema for a Prefab. - """ - _id = fields.String(dump_only=True) - authorId = fields.String(dump_only=True) - name = fields.String(required=True) - datetimeCreated = fields.DateTime() - datetimeLastEdited = fields.DateTime() - rack = fields.Nested(ObjectSchema) - - -class Prefab(Model): - """Model representing a Prefab.""" - - collection_name = 'prefabs' - - def check_user_access(self, user_id): - """Raises an error if the user with given [user_id] has insufficient access to view this prefab. - - :param user_id: The user ID of the user. - """ - if self.obj['authorId'] != user_id and self.obj['visibility'] == "private": - raise Forbidden("Forbidden from retrieving prefab.") diff --git a/opendc-web/opendc-web-api/opendc/models/project.py b/opendc-web/opendc-web-api/opendc/models/project.py deleted file mode 100644 index f2b3b564..00000000 --- a/opendc-web/opendc-web-api/opendc/models/project.py +++ /dev/null @@ -1,48 +0,0 @@ -from marshmallow import Schema, fields, validate -from werkzeug.exceptions import Forbidden - -from opendc.models.model import Model -from opendc.exts import db - - -class ProjectAuthorizations(Schema): - """ - Schema representing a project authorization. - """ - userId = fields.String(required=True) - level = fields.String(required=True, validate=validate.OneOf(["VIEW", "EDIT", "OWN"])) - - -class ProjectSchema(Schema): - """ - Schema representing a Project. - """ - _id = fields.String(dump_only=True) - name = fields.String(required=True) - datetimeCreated = fields.DateTime() - datetimeLastEdited = fields.DateTime() - topologyIds = fields.List(fields.String()) - portfolioIds = fields.List(fields.String()) - authorizations = fields.List(fields.Nested(ProjectAuthorizations)) - - -class Project(Model): - """Model representing a Project.""" - - collection_name = 'projects' - - def check_user_access(self, user_id, edit_access): - """Raises an error if the user with given [user_id] has insufficient access. - - :param user_id: The User ID of the user. - :param edit_access: True when edit access should be checked, otherwise view access. - """ - for authorization in self.obj['authorizations']: - if user_id == authorization['userId'] and authorization['level'] != 'VIEW' or not edit_access: - return - raise Forbidden("Forbidden from retrieving project.") - - @classmethod - def get_for_user(cls, user_id): - """Get all projects for the specified user id.""" - return db.fetch_all({'authorizations.userId': user_id}, Project.collection_name) diff --git a/opendc-web/opendc-web-api/opendc/models/scenario.py b/opendc-web/opendc-web-api/opendc/models/scenario.py deleted file mode 100644 index 47771e06..00000000 --- a/opendc-web/opendc-web-api/opendc/models/scenario.py +++ /dev/null @@ -1,93 +0,0 @@ -from datetime import datetime - -from bson import ObjectId -from marshmallow import Schema, fields - -from opendc.exts import db -from opendc.models.model import Model -from opendc.models.portfolio import Portfolio - - -class SimulationSchema(Schema): - """ - Simulation details. - """ - state = fields.String() - - -class TraceSchema(Schema): - """ - Schema for specifying the trace of a scenario. - """ - traceId = fields.String() - loadSamplingFraction = fields.Float() - - -class TopologySchema(Schema): - """ - Schema for topology specification for a scenario. - """ - topologyId = fields.String() - - -class OperationalSchema(Schema): - """ - Schema for the operational phenomena for a scenario. - """ - failuresEnabled = fields.Boolean() - performanceInterferenceEnabled = fields.Boolean() - schedulerName = fields.String() - - -class ScenarioSchema(Schema): - """ - Schema representing a scenario. - """ - _id = fields.String(dump_only=True) - portfolioId = fields.String() - name = fields.String(required=True) - trace = fields.Nested(TraceSchema) - topology = fields.Nested(TopologySchema) - operational = fields.Nested(OperationalSchema) - simulation = fields.Nested(SimulationSchema, dump_only=True) - results = fields.Dict(dump_only=True) - - -class Scenario(Model): - """Model representing a Scenario.""" - - collection_name = 'scenarios' - - def check_user_access(self, user_id, edit_access): - """Raises an error if the user with given [user_id] has insufficient access. - - Checks access on the parent project. - - :param user_id: The User ID of the user. - :param edit_access: True when edit access should be checked, otherwise view access. - """ - portfolio = Portfolio.from_id(self.obj['portfolioId']) - portfolio.check_user_access(user_id, edit_access) - - @classmethod - def get_jobs(cls): - """Obtain the scenarios that have been queued. - """ - return cls(db.fetch_all({'simulation.state': 'QUEUED'}, cls.collection_name)) - - @classmethod - def get_for_portfolio(cls, portfolio_id): - """Get all scenarios for the specified portfolio id.""" - return db.fetch_all({'portfolioId': ObjectId(portfolio_id)}, cls.collection_name) - - def update_state(self, new_state, results=None): - """Atomically update the state of the Scenario. - """ - update = {'$set': {'simulation.state': new_state, 'simulation.heartbeat': datetime.now()}} - if results: - update['$set']['results'] = results - return db.fetch_and_update( - query={'_id': self.obj['_id'], 'simulation.state': self.obj['simulation']['state']}, - update=update, - collection=self.collection_name - ) diff --git a/opendc-web/opendc-web-api/opendc/models/topology.py b/opendc-web/opendc-web-api/opendc/models/topology.py deleted file mode 100644 index 44994818..00000000 --- a/opendc-web/opendc-web-api/opendc/models/topology.py +++ /dev/null @@ -1,108 +0,0 @@ -from bson import ObjectId -from marshmallow import Schema, fields - -from opendc.exts import db -from opendc.models.project import Project -from opendc.models.model import Model - - -class MemorySchema(Schema): - """ - Schema representing a memory unit. - """ - _id = fields.String() - name = fields.String() - speedMbPerS = fields.Integer() - sizeMb = fields.Integer() - energyConsumptionW = fields.Integer() - - -class PuSchema(Schema): - """ - Schema representing a processing unit. - """ - _id = fields.String() - name = fields.String() - clockRateMhz = fields.Integer() - numberOfCores = fields.Integer() - energyConsumptionW = fields.Integer() - - -class MachineSchema(Schema): - """ - Schema representing a machine. - """ - _id = fields.String() - position = fields.Integer() - cpus = fields.List(fields.Nested(PuSchema)) - gpus = fields.List(fields.Nested(PuSchema)) - memories = fields.List(fields.Nested(MemorySchema)) - storages = fields.List(fields.Nested(MemorySchema)) - rackId = fields.String() - - -class ObjectSchema(Schema): - """ - Schema representing a room object. - """ - _id = fields.String() - name = fields.String() - capacity = fields.Integer() - powerCapacityW = fields.Integer() - machines = fields.List(fields.Nested(MachineSchema)) - tileId = fields.String() - - -class TileSchema(Schema): - """ - Schema representing a room tile. - """ - _id = fields.String() - topologyId = fields.String() - positionX = fields.Integer() - positionY = fields.Integer() - rack = fields.Nested(ObjectSchema) - roomId = fields.String() - - -class RoomSchema(Schema): - """ - Schema representing a room. - """ - _id = fields.String() - name = fields.String(required=True) - topologyId = fields.String() - tiles = fields.List(fields.Nested(TileSchema), required=True) - - -class TopologySchema(Schema): - """ - Schema representing a datacenter topology. - """ - _id = fields.String(dump_only=True) - projectId = fields.String() - name = fields.String(required=True) - rooms = fields.List(fields.Nested(RoomSchema), required=True) - datetimeLastEdited = fields.DateTime() - - -class Topology(Model): - """Model representing a Project.""" - - collection_name = 'topologies' - - def check_user_access(self, user_id, edit_access): - """Raises an error if the user with given [user_id] has insufficient access. - - Checks access on the parent project. - - :param user_id: The User ID of the user. - :param edit_access: True when edit access should be checked, otherwise view access. - """ - project = Project.from_id(self.obj['projectId']) - project.check_user_access(user_id, edit_access) - - @classmethod - def get_for_project(cls, project_id): - """Get all topologies for the specified project id.""" - return db.fetch_all({'projectId': ObjectId(project_id)}, cls.collection_name) diff --git a/opendc-web/opendc-web-api/opendc/models/trace.py b/opendc-web/opendc-web-api/opendc/models/trace.py deleted file mode 100644 index 69287f29..00000000 --- a/opendc-web/opendc-web-api/opendc/models/trace.py +++ /dev/null @@ -1,16 +0,0 @@ -from marshmallow import Schema, fields - -from opendc.models.model import Model - - -class TraceSchema(Schema): - """Schema for a Trace.""" - _id = fields.String(dump_only=True) - name = fields.String() - type = fields.String() - - -class Trace(Model): - """Model representing a Trace.""" - - collection_name = 'traces' |
