Skip to content

Commit

Permalink
✨add preference to allow data collections form services (ITISFoundati…
Browse files Browse the repository at this point in the history
…on#5112)

Co-authored-by: Andrei Neagu <[email protected]>
  • Loading branch information
GitHK and Andrei Neagu authored Dec 11, 2023
1 parent 9302f8b commit 4cfa318
Show file tree
Hide file tree
Showing 14 changed files with 150 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .user_preferences import FrontendUserPreference


class AllowMetricsCollectionFrontendUserPreference(FrontendUserPreference):
preference_identifier: str = "allowMetricsCollection"
value: bool = True
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async def upsert_projects_networks(
self, project_id: ProjectID, networks_with_aliases: NetworksWithAliases
) -> None:
projects_networks_to_insert = ProjectsNetworks.parse_obj(
dict(project_uuid=project_id, networks_with_aliases=networks_with_aliases)
{"project_uuid": project_id, "networks_with_aliases": networks_with_aliases}
)

async with self.db_engine.acquire() as conn:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from models_library.products import ProductName
from models_library.user_preferences import FrontendUserPreference, PreferenceName
from models_library.users import UserID
from simcore_postgres_database.utils_user_preferences import FrontendUserPreferencesRepo

from ._base import BaseRepository


def _get_user_preference_name(user_id: UserID, preference_name: PreferenceName) -> str:
return f"{user_id}/{preference_name}"


class UserPreferencesFrontendRepository(BaseRepository):
async def get_user_preference(
self,
*,
user_id: UserID,
product_name: ProductName,
preference_class: type[FrontendUserPreference],
) -> FrontendUserPreference | None:
async with self.db_engine.acquire() as conn:
preference_payload: dict | None = await FrontendUserPreferencesRepo.load(
conn,
user_id=user_id,
preference_name=_get_user_preference_name(
user_id, preference_class.get_preference_name()
),
product_name=product_name,
)

return (
None
if preference_payload is None
else preference_class.parse_obj(preference_payload)
)
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
update_service_params_from_settings,
)

log = logging.getLogger(__name__)
_logger = logging.getLogger(__name__)


def extract_service_port_service_settings(
Expand All @@ -42,6 +42,7 @@ def _get_environment_variables(
app_settings: AppSettings,
*,
allow_internet_access: bool,
metrics_collection_allowed: bool,
) -> dict[str, str]:
registry_settings = app_settings.DIRECTOR_V2_DOCKER_REGISTRY
rabbit_settings = app_settings.DIRECTOR_V2_RABBITMQ
Expand All @@ -53,6 +54,16 @@ def _get_environment_variables(
if scheduler_data.paths_mapping.state_exclude is not None:
state_exclude = scheduler_data.paths_mapping.state_exclude

callbacks_mapping: CallbacksMapping = scheduler_data.callbacks_mapping

if not metrics_collection_allowed:
_logger.info(
"user=%s disabled metrics collection, disable prometheus metrics for node_id=%s",
scheduler_data.user_id,
scheduler_data.node_uuid,
)
callbacks_mapping.metrics = None

return {
# These environments will be captured by
# services/dynamic-sidecar/src/simcore_service_dynamic_sidecar/core/settings.py::ApplicationSettings
Expand All @@ -64,7 +75,7 @@ def _get_environment_variables(
"DY_SIDECAR_RUN_ID": scheduler_data.run_id,
"DY_SIDECAR_USER_SERVICES_HAVE_INTERNET_ACCESS": f"{allow_internet_access}",
"DY_SIDECAR_STATE_EXCLUDE": json_dumps(f"{x}" for x in state_exclude),
"DY_SIDECAR_CALLBACKS_MAPPING": scheduler_data.callbacks_mapping.json(),
"DY_SIDECAR_CALLBACKS_MAPPING": callbacks_mapping.json(),
"DY_SIDECAR_STATE_PATHS": json_dumps(
f"{x}" for x in scheduler_data.paths_mapping.state_paths
),
Expand Down Expand Up @@ -139,8 +150,10 @@ def get_dynamic_sidecar_spec(
swarm_network_id: str,
settings: SimcoreServiceSettingsLabel,
app_settings: AppSettings,
*,
has_quota_support: bool,
allow_internet_access: bool,
metrics_collection_allowed: bool,
) -> AioDockerServiceSpec:
"""
The dynamic-sidecar is responsible for managing the lifecycle
Expand Down Expand Up @@ -329,6 +342,7 @@ def get_dynamic_sidecar_spec(
scheduler_data,
app_settings,
allow_internet_access=allow_internet_access,
metrics_collection_allowed=metrics_collection_allowed,
),
"Hosts": [],
"Image": dynamic_sidecar_settings.DYNAMIC_SIDECAR_IMAGE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
are_all_user_services_containers_running,
attach_project_networks,
attempt_pod_removal_and_data_saving,
get_allow_metrics_collection,
get_director_v0_client,
parse_containers_inspect,
prepare_services_environment,
Expand Down Expand Up @@ -183,6 +184,12 @@ async def action(cls, app: FastAPI, scheduler_data: SchedulerData) -> None:
swarm_network_id: NetworkId = swarm_network["Id"]
swarm_network_name: str = swarm_network["Name"]

metrics_collection_allowed: bool = await get_allow_metrics_collection(
app,
user_id=scheduler_data.user_id,
product_name=scheduler_data.product_name,
)

# start dynamic-sidecar and run the proxy on the same node

# Each time a new dynamic-sidecar service is created
Expand All @@ -200,6 +207,7 @@ async def action(cls, app: FastAPI, scheduler_data: SchedulerData) -> None:
app_settings=app.state.settings,
has_quota_support=dynamic_services_scheduler_settings.DYNAMIC_SIDECAR_ENABLE_VOLUME_LIMITS,
allow_internet_access=allow_internet_access,
metrics_collection_allowed=metrics_collection_allowed,
)

catalog_client = CatalogClient.instance(app)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,22 @@

import json
import logging
from typing import Any
from typing import Any, cast

from fastapi import FastAPI
from models_library.products import ProductName
from models_library.projects_networks import ProjectsNetworks
from models_library.projects_nodes import NodeID
from models_library.projects_nodes_io import NodeIDStr
from models_library.rabbitmq_messages import InstrumentationRabbitMessage
from models_library.service_settings_labels import SimcoreServiceLabels
from models_library.services import ServiceKeyVersion
from models_library.shared_user_preferences import (
AllowMetricsCollectionFrontendUserPreference,
)
from models_library.sidecar_volumes import VolumeCategory, VolumeStatus
from models_library.user_preferences import FrontendUserPreference
from models_library.users import UserID
from servicelib.fastapi.long_running_tasks.client import (
ProgressCallback,
TaskClientResultError,
Expand Down Expand Up @@ -40,6 +46,9 @@
from ....api_keys_manager import safe_remove
from ....db.repositories.projects import ProjectsRepository
from ....db.repositories.projects_networks import ProjectsNetworksRepository
from ....db.repositories.user_preferences_frontend import (
UserPreferencesFrontendRepository,
)
from ....director_v0 import DirectorV0Client
from ...api_client import (
BaseClientHTTPError,
Expand Down Expand Up @@ -442,3 +451,24 @@ async def prepare_services_environment(
)

scheduler_data.dynamic_sidecar.is_service_environment_ready = True


async def get_allow_metrics_collection(
app: FastAPI, user_id: UserID, product_name: ProductName
) -> bool:
repo = get_repository(app, UserPreferencesFrontendRepository)
preference: FrontendUserPreference | None = await repo.get_user_preference(
user_id=user_id,
product_name=product_name,
preference_class=AllowMetricsCollectionFrontendUserPreference,
)

if preference is None:
return cast(
bool, AllowMetricsCollectionFrontendUserPreference.get_default_value()
)

allow_metrics_collection = AllowMetricsCollectionFrontendUserPreference.parse_obj(
preference
)
return cast(bool, allow_metrics_collection.value)
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def test_dynamic_sidecar_env_vars(
scheduler_data_from_http_request,
app_settings,
allow_internet_access=False,
metrics_collection_allowed=True,
)
print("dynamic_sidecar_env_vars:", dynamic_sidecar_env_vars)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ def test_get_dynamic_proxy_spec(
app_settings=minimal_app.state.settings,
has_quota_support=False,
allow_internet_access=False,
metrics_collection_allowed=True,
)

exclude_keys: Mapping[int | str, Any] = {
Expand Down Expand Up @@ -508,6 +509,7 @@ async def test_merge_dynamic_sidecar_specs_with_user_specific_specs(
app_settings=minimal_app.state.settings,
has_quota_support=False,
allow_internet_access=False,
metrics_collection_allowed=True,
)
assert dynamic_sidecar_spec
dynamic_sidecar_spec_dict = dynamic_sidecar_spec.dict()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ qx.Class.define("osparc.Preferences", {
init: 4,
event: "changeJobConcurrencyLimit",
apply: "__patchPreference"
},

allowMetricsCollection: {
nullable: false,
init: true,
check: "Boolean",
event: "changeAllowMetricsCollection",
apply: "__patchPreference"
}
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ qx.Class.define("osparc.desktop.preferences.pages.BasePage", {
/**
* Common layout for tooltip label
*/
_createHelpLabel: function(message=null) {
_createHelpLabel: function(message=null, font="text-13") {
const label = new qx.ui.basic.Label().set({
value: message,
alignX: "left",
rich: true,
font: "text-13"
font: font
});
return label;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,10 @@ qx.Class.define("osparc.desktop.preferences.pages.GeneralPage", {
const title = this.tr("General Settings");
this.base(arguments, title, iconSrc);

const walletIndicatorSettings = this.__createCreditsIndicatorSettings();
this.add(walletIndicatorSettings);

this.add(this.__createCreditsIndicatorSettings());
this.add(this.__createInactivitySetting());
this.add(this.__createJobConcurrencySetting());
this.add(this.__createUserPrivacySettings());
},

statics: {
Expand Down Expand Up @@ -57,11 +56,6 @@ qx.Class.define("osparc.desktop.preferences.pages.GeneralPage", {
// layout
const box = this._createSectionBox(this.tr("Credits Indicator"));

const label = this._createHelpLabel(this.tr(
"Choose when you want the Credits Indicator to be shown in the navigation bar:"
));
box.add(label);

const form = new qx.ui.form.Form();

const preferencesSettings = osparc.Preferences.getInstance();
Expand Down Expand Up @@ -89,7 +83,7 @@ qx.Class.define("osparc.desktop.preferences.pages.GeneralPage", {
const selectable = e.getData();
this.self().patchPreference("walletIndicatorVisibility", walletIndicatorVisibilitySB, selectable.getModel());
});
form.add(walletIndicatorVisibilitySB, this.tr("Show it"));
form.add(walletIndicatorVisibilitySB, this.tr("Show indicator"));

const creditsWarningThresholdField = new qx.ui.form.Spinner().set({
minimum: 100,
Expand All @@ -99,15 +93,15 @@ qx.Class.define("osparc.desktop.preferences.pages.GeneralPage", {
});
preferencesSettings.bind("creditsWarningThreshold", creditsWarningThresholdField, "value");
creditsWarningThresholdField.addListener("changeValue", e => this.self().patchPreference("creditsWarningThreshold", creditsWarningThresholdField, e.getData()));
form.add(creditsWarningThresholdField, this.tr("Warning threshold"));
form.add(creditsWarningThresholdField, this.tr("Show warning when credits below"));

box.add(new qx.ui.form.renderer.Single(form));

return box;
},
__createInactivitySetting: function() {
const box = this._createSectionBox(this.tr("Inactivity shutdown"));
const label = this._createHelpLabel(this.tr("Choose after how long should inactive studies be closed. A value of zero disables this function."));
const box = this._createSectionBox(this.tr("Automatic Shutdown of Idle Instances"));
const label = this._createHelpLabel(this.tr("Enter 0 to disable this function"), "text-13-italic");
box.add(label);
const form = new qx.ui.form.Form();
const inactivitySpinner = new qx.ui.form.Spinner().set({
Expand All @@ -126,9 +120,7 @@ qx.Class.define("osparc.desktop.preferences.pages.GeneralPage", {
return box;
},
__createJobConcurrencySetting: function() {
const box = this._createSectionBox(this.tr("Job concurrency"));
const label = this._createHelpLabel(this.tr("Choose how many jobs can run at the same time."));
box.add(label);
const box = this._createSectionBox(this.tr("Job Concurrency"));
const form = new qx.ui.form.Form();
const jobConcurrencySpinner = new qx.ui.form.Spinner().set({
minimum: 1,
Expand All @@ -140,8 +132,23 @@ qx.Class.define("osparc.desktop.preferences.pages.GeneralPage", {
const preferences = osparc.Preferences.getInstance();
preferences.bind("jobConcurrencyLimit", jobConcurrencySpinner, "value");
jobConcurrencySpinner.addListener("changeValue", e => this.self().patchPreference("jobConcurrencyLimit", jobConcurrencySpinner, e.getData()));
form.add(jobConcurrencySpinner, this.tr("Maximum concurrent jobs"));
form.add(jobConcurrencySpinner, this.tr("Maximum number of concurrent jobs"));
box.add(new qx.ui.form.renderer.Single(form));
return box;
},
__createUserPrivacySettings: function() {
const box = this._createSectionBox("Privacy Settings");

const label = this._createHelpLabel(this.tr("Help us improve Sim4Life user experience"), "text-13-italic");
box.add(label);

const preferencesSettings = osparc.Preferences.getInstance();

const cbAllowMetricsCollection = new qx.ui.form.CheckBox(this.tr("Share usage data"));
preferencesSettings.bind("allowMetricsCollection", cbAllowMetricsCollection, "value");
cbAllowMetricsCollection.addListener("changeValue", e => this.self().patchPreference("allowMetricsCollection", cbAllowMetricsCollection, e.getData()));
box.add(cbAllowMetricsCollection);

return box;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ qx.Theme.define("osparc.theme.Font", {
size: 13
},

"text-13-italic": {
include: "defaults",
size: 13,
italic: true
},

"text-12": {
include: "defaults",
size: 12
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from aiohttp import web
from models_library.products import ProductName
from models_library.user_preferences import AnyUserPreference, PreferenceName
from models_library.user_preferences import FrontendUserPreference, PreferenceName
from models_library.users import UserID
from simcore_postgres_database.utils_user_preferences import FrontendUserPreferencesRepo

Expand All @@ -16,8 +16,8 @@ async def get_user_preference(
*,
user_id: UserID,
product_name: ProductName,
preference_class: type[AnyUserPreference],
) -> AnyUserPreference | None:
preference_class: type[FrontendUserPreference],
) -> FrontendUserPreference | None:
async with get_database_engine(app).acquire() as conn:
preference_payload: dict | None = await FrontendUserPreferencesRepo.load(
conn,
Expand All @@ -40,7 +40,7 @@ async def set_user_preference(
*,
user_id: UserID,
product_name: ProductName,
preference: AnyUserPreference,
preference: FrontendUserPreference,
) -> None:
async with get_database_engine(app).acquire() as conn:
await FrontendUserPreferencesRepo.save(
Expand Down
Loading

0 comments on commit 4cfa318

Please sign in to comment.