Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-tenancy #159

Merged
merged 19 commits into from Aug 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f67c9ab
add account table and according data migration script
nhoening Aug 12, 2021
8d0ecc1
fix the preservation of generic_asset.owner_id information between up…
nhoening Aug 13, 2021
48425a7
mention account name on user page and listings
nhoening Aug 13, 2021
7b805a0
change name of /account page to /logged-in-user to avoid confusion
nhoening Aug 14, 2021
e499728
adapt data.services.users:create_user to deal with the account_id; ma…
nhoening Aug 14, 2021
beed6ea
display account name on logged-in-user page, not account ID
nhoening Aug 14, 2021
d754a33
give automatically generated generoc assets the correct account ID (d…
nhoening Aug 15, 2021
ffddd6d
Support adding & deleting accounts in CLI, also add --account-id to t…
nhoening Aug 15, 2021
fe6c0f3
API: protect change in asset.owner_id from switching accounts
nhoening Aug 16, 2021
e798ea5
a few user/asset API docs improvements found on the way, also adding …
nhoening Aug 16, 2021
3c2ff34
Update change_log.rst
Flix6x Aug 23, 2021
0e03b0f
Show which users and generic assets would be deleted when CLI task is…
nhoening Aug 23, 2021
1c8c882
add account_id to GenericAsset schema and also to the CLI task for ad…
nhoening Aug 23, 2021
17727fc
minor improvements from review
nhoening Aug 23, 2021
b95811f
ffix test
nhoening Aug 24, 2021
c6df816
add changelog entry
nhoening Aug 26, 2021
a2e3d3a
document make show-data-model better & make --dev work for the --sche…
nhoening Aug 26, 2021
d839d7c
add info about make show-data-model --uml --dev to changelog
nhoening Aug 26, 2021
49b1cf0
more help with db upgrading in version 0.6.0
nhoening Aug 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 additions & 1 deletion Makefile
Expand Up @@ -74,4 +74,8 @@ upgrade-db:
flask db current

show-data-model:
./flexmeasures/data/scripts/visualize_data_model.py --uml --store # also try with --schema for database model
# This generates the data model, as currently written in code, as a PNG picture.
# Also try with --schema for the database model.
# With --dev, you'll see the currently experimental parts, as well.
# Use --help to learn more.
./flexmeasures/data/scripts/visualize_data_model.py --uml --store
2 changes: 1 addition & 1 deletion documentation/api/introduction.rst
Expand Up @@ -102,7 +102,7 @@ A fresh "<token>" can be generated on the user's profile after logging in:

.. code-block:: html

https://company.flexmeasures.io/account
https://company.flexmeasures.io/logged-in-user

or through a POST request to the following endpoint:

Expand Down
7 changes: 5 additions & 2 deletions documentation/changelog.rst
Expand Up @@ -2,16 +2,17 @@
FlexMeasures Changelog
**********************

v0.6.0 | July XX, 2021
v0.6.0 | August XX, 2021
===========================

.. warning:: Upgrading to this version requires running ``flexmeasures db upgrade`` (you can create a backup first with ``flexmeasures db-ops dump``).
In case you are using experimental developer features and have previously set up sensors, be sure to check out the upgrade instructions in `PR #157 <https://github.com/SeitaBV/flexmeasures/pull/157>`_.
In case you are using experimental developer features and have previously set up sensors, be sure to check out the upgrade instructions in `PR #157 <https://github.com/SeitaBV/flexmeasures/pull/157>`_. Furthermore, if you want to create custom user/account relationships while upgrading (otherwise the upgrade script creates accounts based on email domains), check out the upgrade instructions in `PR #159 <https://github.com/SeitaBV/flexmeasures/pull/159>`_. If you want to use both of these custom upgrade features, do the upgrade in two steps. First, as described in PR 157 and upgrading up to revision b6d49ed7cceb, then as described in PR 159 for the rest.

New features
-----------
* Analytics view offers grouping of all assets by location [see `PR #148 <http://www.github.com/SeitaBV/flexmeasures/pull/148>`_]
* Add (experimental) endpoint to post sensor data for any sensor. Also supports our ongoing integration with data internally represented using the `timely beliefs <https://github.com/SeitaBV/timely-beliefs>`_ lib [see `PR #147 <http://www.github.com/SeitaBV/flexmeasures/pull/147>`_]
* Multi-tenancy: Supporting multiple customers per FlexMeasures server, by introducing the `Account` concept. Accounts have users and assets associated. [see `PR #159 <http://www.github.com/SeitaBV/flexmeasures/pull/159>`_]

Bugfixes
-----------
Expand All @@ -23,6 +24,8 @@ Infrastructure / Support
* Add CLI task to monitor if tasks ran successfully and recently enough [see `PR #146 <http://www.github.com/SeitaBV/flexmeasures/pull/146>`_]
* Document how to use a custom favicon in plugins [see `PR #152 <http://www.github.com/SeitaBV/flexmeasures/pull/152>`_]
* Continue experimental integration with `timely beliefs <https://github.com/SeitaBV/timely-beliefs>`_ lib: link multiple sensors to a single asset [see `PR #157 <https://github.com/SeitaBV/flexmeasures/pull/157>`_]
* The experimental parts of the data model can now be visualised, as well, via `make show-data-model --uml --dev` [also in `PR #157 <https://github.com/SeitaBV/flexmeasures/pull/157>`_]



v0.5.0 | June 7, 2021
Expand Down
9 changes: 8 additions & 1 deletion documentation/cli/change_log.rst
Expand Up @@ -4,6 +4,13 @@
FlexMeasures CLI Changelog
Flix6x marked this conversation as resolved.
Show resolved Hide resolved
**********************


since v0.6.0 | April 2, 2021
=====================

* add ``flexmeasures add account``, ``flexmeasures delete account``, and the ``--account-id`` param to ``flexmeasures add user``.


since v0.4.0 | April 2, 2021
=====================

Expand All @@ -15,4 +22,4 @@ since v0.3.0 | April 2, 2021

* Refactor CLI into the main groups ``add``, ``delete``, ``jobs`` and ``db-ops``
* Add ``flexmeasures add asset``, ``flexmeasures add user`` and ``flexmeasures add weather-sensor``
* split the ``populate-db`` command into ``flexmeasures add structure`` and ``flexmeasures add forecasts``
* split the ``populate-db`` command into ``flexmeasures add structure`` and ``flexmeasures add forecasts``
2 changes: 2 additions & 0 deletions documentation/cli/commands.rst
Expand Up @@ -25,6 +25,7 @@ of which some are referred to in this documentation.
================================================= =======================================
``flexmeasures add structure`` Initialize structural data like asset types,
market types and weather sensor types.
``flexmeasures add account`` Create a FlexMeasures tenant account.
``flexmeasures add user`` Create a FlexMeasures user.
``flexmeasures add asset`` Create a new asset.
``flexmeasures add weather-sensor`` Add a weather sensor.
Expand All @@ -39,6 +40,7 @@ of which some are referred to in this documentation.
================================================= =======================================
``flexmeasures delete structure`` Delete all structural (non time-series) data like assets (types),
markets (types) and weather sensors (types) and users.
``flexmeasures delete account`` Delete a tenant account & also their users (with assets and power measurements).
``flexmeasures delete user`` Delete a user & also their assets and power measurements.
``flexmeasures delete measurements`` Delete measurements (with horizon <= 0).
``flexmeasures delete prognoses`` Delete forecasts and schedules (forecasts > 0).
Expand Down
18 changes: 13 additions & 5 deletions documentation/getting-started.rst
Expand Up @@ -96,14 +96,22 @@ This suffices for a quick start.



Add a user
^^^^^^^^^^
Add an account & user
^^^^^^^^^^^^^^^^^^^^^

FlexMeasures is a tenant-based platform ― multiple clients can enjoy its services on one server. Let's create a tenant account first:

.. code-block::

flexmeasures add account --name "Some company"

This command will tell us the ID of this account. Let's assume it was ``2``.

FlexMeasures is a web-based platform, so we need a user account:
FlexMeasures is also a web-based platform, so we need to create a user to authenticate:

.. code-block::

flexmeasures add user --username <your-username> --email <your-email-address> --roles=admin
flexmeasures add user --username <your-username> --email <your-email-address> --account-id 2 --roles=admin


* This will ask you to set a password for the user.
Expand Down Expand Up @@ -215,4 +223,4 @@ More information (e.g. for installing on Windows) on `the Cbc website <https://p
Install and configure Redis
^^^^^^^^^^^^^^^^^^^^^^^

To let FlexMeasures queue forecasting and scheduling jobs, install a `Redis <https://redis.io/>`_ server (or rent one) and configure access to it within FlexMeasures' config file (see above). You can find the necessary settings in :ref:`redis-config`.
To let FlexMeasures queue forecasting and scheduling jobs, install a `Redis <https://redis.io/>`_ server (or rent one) and configure access to it within FlexMeasures' config file (see above). You can find the necessary settings in :ref:`redis-config`.
4 changes: 2 additions & 2 deletions documentation/views/admin.rst
Expand Up @@ -4,7 +4,7 @@
Administration
**************

The administrator can edit assets and user accounts here.
The administrator can edit assets and users here.

Assets
------
Expand Down Expand Up @@ -36,4 +36,4 @@ Viewing one user:

.. image:: https://github.com/SeitaBV/screenshots/raw/main/screenshot_user.png
:align: center
.. :scale: 40%
.. :scale: 40%
3 changes: 2 additions & 1 deletion flexmeasures/api/tests/conftest.py
Expand Up @@ -8,7 +8,7 @@


@pytest.fixture(scope="module", autouse=True)
def setup_api_test_data(db, setup_roles_users):
def setup_api_test_data(db, setup_account, setup_roles_users):
"""
Adding the task-runner
"""
Expand All @@ -31,6 +31,7 @@ def setup_api_test_data(db, setup_roles_users):
username="test user",
email="task_runner@seita.nl",
password=hash_password("testtest"),
account_id=setup_account.id,
)
user_datastore.add_role_to_user(test_task_runner, test_task_runner_role)

Expand Down
4 changes: 3 additions & 1 deletion flexmeasures/api/v1/tests/conftest.py
Expand Up @@ -10,7 +10,7 @@


@pytest.fixture(scope="module", autouse=True)
def setup_api_test_data(db, setup_roles_users, add_market_prices):
def setup_api_test_data(db, setup_account, setup_roles_users, add_market_prices):
"""
Set up data for API v1 tests.
"""
Expand All @@ -24,6 +24,7 @@ def setup_api_test_data(db, setup_roles_users, add_market_prices):
username="anonymous user with Prosumer role",
email="demo@seita.nl",
password=hash_password("testtest"),
account_name=setup_account.name,
user_roles=[
"Prosumer",
dict(name="anonymous", description="Anonymous test user"),
Expand Down Expand Up @@ -54,6 +55,7 @@ def setup_api_test_data(db, setup_roles_users, add_market_prices):
username="test user without roles",
email="test_user@seita.nl",
password=hash_password("testtest"),
account_name=setup_account.name,
)

# Create 5 test assets for the test_prosumer user
Expand Down
4 changes: 3 additions & 1 deletion flexmeasures/api/v1_1/tests/conftest.py
Expand Up @@ -12,7 +12,7 @@


@pytest.fixture(scope="module")
def setup_api_test_data(db, setup_roles_users, add_market_prices):
def setup_api_test_data(db, setup_account, setup_roles_users, add_market_prices):
"""
Set up data for API v1.1 tests.
"""
Expand All @@ -28,6 +28,7 @@ def setup_api_test_data(db, setup_roles_users, add_market_prices):
username="test user with improper registration",
email="test_improper_user@seita.nl",
password=hash_password("testtest"),
account_id=setup_account.id,
)
role = user_datastore.find_role("Prosumer")
user_datastore.add_role_to_user(user, role)
Expand All @@ -37,6 +38,7 @@ def setup_api_test_data(db, setup_roles_users, add_market_prices):
username="test user without roles",
email="test_user@seita.nl",
password=hash_password("testtest"),
account_name=setup_account.name,
)

# Create 3 test assets for the test_prosumer user
Expand Down
16 changes: 16 additions & 0 deletions flexmeasures/api/v2_0/implementations/assets.py
Expand Up @@ -9,6 +9,7 @@
from marshmallow import fields

from flexmeasures.data.services.resources import get_assets
from flexmeasures.data.models.user import User
from flexmeasures.data.models.assets import Asset as AssetModel
from flexmeasures.data.schemas.assets import AssetSchema
from flexmeasures.data.auth_setup import unauthorized_handler
Expand Down Expand Up @@ -115,6 +116,19 @@ def decorated_endpoint(*args, **kwargs):
return wrapper


def ensure_asset_remains_in_account(db_asset: AssetModel, new_owner_id: int):
"""
Temporary protection of information kept in two places
(Asset.owner_id, GenericAsset.account_id) until we use GenericAssets throughout.
"""
new_owner = User.query.get(new_owner_id)
if new_owner and new_owner.account != db_asset.owner.account:
raise abort(
400,
f"New owner {new_owner_id} not allowed, belongs to different account than current owner.",
)


@load_asset()
@as_json
def fetch_one(asset):
Expand All @@ -129,6 +143,8 @@ def patch(db_asset, asset_data):
"""Update an asset given its identifier"""
ignored_fields = ["id"]
for k, v in [(k, v) for k, v in asset_data.items() if k not in ignored_fields]:
if k == "owner_id":
ensure_asset_remains_in_account(db_asset, v)
setattr(db_asset, k, v)
db.session.add(db_asset)
try:
Expand Down
28 changes: 15 additions & 13 deletions flexmeasures/api/v2_0/routes.py
Expand Up @@ -47,14 +47,14 @@
)
v2_0_service_listing["services"].append(
{
"name": "PATCH /assets/<id>",
"name": "PATCH /asset/<id>",
"access": [],
"description": "Edit an asset.",
},
)
v2_0_service_listing["services"].append(
{
"name": "DELETE /assets/<id>",
"name": "DELETE /asset/<id>",
"access": [],
"description": "Delete an asset and its data.",
},
Expand Down Expand Up @@ -117,11 +117,11 @@ def get_assets():
"id": 1,
"latitude": 10,
"longitude": 100,
"market": 1,
"market_id": 1,
"max_soc_in_mwh": 5,
"min_soc_in_mwh": 0,
"name": "Test battery",
"owner": 2,
"owner_id": 2,
"soc_datetime": "2015-01-01T00:00:00+00:00",
"soc_in_mwh": 2.5,
"soc_udi_event_id": 203,
Expand Down Expand Up @@ -166,8 +166,8 @@ def post_assets():
"name": "Test battery",
"asset_type": "battery",
"unit": "kW",
"owner": 2,
"market": 1,
"owner_id": 2,
"market_id": 1,
"event_resolution": 5,
"capacity_in_mw": 4.2,
"latitude": 40,
Expand Down Expand Up @@ -197,8 +197,8 @@ def post_assets():
"max_soc_in_mwh": 5,
"min_soc_in_mwh": 0,
"name": "Test battery",
"owner": 2,
"market": 1,
"owner_id": 2,
"market_id": 1,
"soc_datetime": null,
"soc_in_mwh": null,
"soc_udi_event_id": null
Expand Down Expand Up @@ -238,11 +238,11 @@ def get_asset(id: int):
"id": 1,
"latitude": 10,
"longitude": 100,
"market": 1,
"market_id": 1,
"max_soc_in_mwh": 5,
"min_soc_in_mwh": 0,
"name": "Test battery",
"owner": 2,
"owner_id": 2,
"soc_datetime": "2015-01-01T00:00:00+00:00",
"soc_in_mwh": 2.5,
"soc_udi_event_id": 203,
Expand Down Expand Up @@ -300,11 +300,11 @@ def patch_asset(id: int):
"id": 1,
"latitude": 11.1,
"longitude": 99.9,
"market": 1,
"market_id": 1,
"max_soc_in_mwh": 5,
"min_soc_in_mwh": 0,
"name": "Test battery",
"owner": 2,
"owner_id": 2,
"soc_datetime": "2015-01-01T00:00:00+00:00",
"soc_in_mwh": 2.5,
"soc_udi_event_id": 203,
Expand Down Expand Up @@ -403,6 +403,7 @@ def get_user(id: int):
.. sourcecode:: json

{
'account_id': 1,
'active': True,
'email': 'test_prosumer@seita.nl',
'flexmeasures_roles': [1, 3],
Expand Down Expand Up @@ -435,7 +436,7 @@ def patch_user(id: int):
Only the user themselves or admins are allowed to update its data,
while a non-admin can only edit a few of their own fields.

Several fields are not allowed to be updated, e.g. id. They are ignored.
Several fields are not allowed to be updated, e.g. id and account_id. They are ignored.

**Example request**

Expand All @@ -452,6 +453,7 @@ def patch_user(id: int):
.. sourcecode:: json

{
'account_id': 1,
'active': True,
'email': 'test_prosumer@seita.nl',
'flexmeasures_roles': [1, 3],
Expand Down
3 changes: 2 additions & 1 deletion flexmeasures/api/v2_0/tests/conftest.py
Expand Up @@ -23,7 +23,7 @@ def setup_api_test_data(db, setup_roles_users, add_market_prices, add_battery_as


@pytest.fixture(scope="module")
def setup_inactive_user(db, setup_roles_users):
def setup_inactive_user(db, setup_account, setup_roles_users):
"""
Set up one inactive user.
"""
Expand All @@ -34,5 +34,6 @@ def setup_inactive_user(db, setup_roles_users):
username="inactive test user",
email="inactive@seita.nl",
password=hash_password("testtest"),
account_id=setup_account.id,
active=False,
)