diff --git a/packages/models-library/src/models_library/api_schemas_directorv2/services.py b/packages/models-library/src/models_library/api_schemas_directorv2/services.py index 3d2fb51f302d..01e691d12a47 100644 --- a/packages/models-library/src/models_library/api_schemas_directorv2/services.py +++ b/packages/models-library/src/models_library/api_schemas_directorv2/services.py @@ -67,9 +67,7 @@ class ServiceExtras(BaseModel): json_schema_extra={ "examples": [ {"node_requirements": node_example} - for node_example in NodeRequirements.model_config["json_schema_extra"][ - "examples" - ] # type: ignore[index,union-attr] + for node_example in NodeRequirements.model_json_schema()["examples"] ] + [ { @@ -80,9 +78,7 @@ class ServiceExtras(BaseModel): "vcs_url": "git@github.com:ITISFoundation/osparc-simcore.git", }, } - for node_example in NodeRequirements.model_config["json_schema_extra"][ - "examples" - ] # type: ignore[index,dict-item, union-attr] + for node_example in NodeRequirements.model_json_schema()["examples"] ] + [ { @@ -94,9 +90,7 @@ class ServiceExtras(BaseModel): }, "container_spec": {"Command": ["run", "subcommand"]}, } - for node_example in NodeRequirements.model_config["json_schema_extra"][ - "examples" - ] # type: ignore[index,union-attr] + for node_example in NodeRequirements.model_json_schema()["examples"] ] } ) diff --git a/services/catalog/openapi.json b/services/catalog/openapi.json index 896399a63b95..3389e912cf81 100644 --- a/services/catalog/openapi.json +++ b/services/catalog/openapi.json @@ -190,6 +190,59 @@ } } }, + "/v0/services/{service_key}/{service_version}/extras": { + "get": { + "tags": [ + "services" + ], + "summary": "Get Service Extras", + "operationId": "get_service_extras_v0_services__service_key___service_version__extras_get", + "parameters": [ + { + "name": "service_key", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "title": "Service Key" + } + }, + { + "name": "service_version", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", + "title": "Service Version" + } + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceExtras" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, "/v0/services/{service_key}/{service_version}/specifications": { "get": { "tags": [ @@ -905,9 +958,9 @@ "type": "object", "title": "Config1" }, - "ContainerSpec": { + "CredentialSpec": { "properties": { - "Image": { + "Config": { "anyOf": [ { "type": "string" @@ -916,67 +969,69 @@ "type": "null" } ], - "title": "Image", - "description": "The image name to use for the container" + "title": "Config", + "description": "Load credential spec from a Swarm Config with the given ID.\nThe specified config must also be present in the Configs\nfield with the Runtime property set.\n\n


\n\n\n> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`,\n> and `CredentialSpec.Config` are mutually exclusive.\n" }, - "Labels": { + "File": { "anyOf": [ { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "type": "string" }, { "type": "null" } ], - "title": "Labels", - "description": "User-defined key/value data." + "title": "File", + "description": "Load credential spec from this file. The file is read by\nthe daemon, and must be present in the `CredentialSpecs`\nsubdirectory in the docker data directory, which defaults\nto `C:\\ProgramData\\Docker\\` on Windows.\n\nFor example, specifying `spec.json` loads\n`C:\\ProgramData\\Docker\\CredentialSpecs\\spec.json`.\n\n


\n\n> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`,\n> and `CredentialSpec.Config` are mutually exclusive.\n" }, - "Command": { + "Registry": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "type": "string" }, { "type": "null" } ], - "title": "Command", - "description": "The command to be run in the image." - }, - "Args": { + "title": "Registry", + "description": "Load credential spec from this value in the Windows\nregistry. The specified registry value must be located in:\n\n`HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Virtualization\\Containers\\CredentialSpecs`\n\n


\n\n\n> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`,\n> and `CredentialSpec.Config` are mutually exclusive.\n" + } + }, + "type": "object", + "title": "CredentialSpec", + "description": "CredentialSpec for managed service account (Windows only)" + }, + "DiscreteResourceSpec": { + "properties": { + "Kind": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "type": "string" }, { "type": "null" } ], - "title": "Args", - "description": "Arguments to the command." + "title": "Kind" }, - "Hostname": { + "Value": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Hostname", - "description": "The hostname to use for the container, as a valid\n[RFC 1123](https://tools.ietf.org/html/rfc1123) hostname.\n" - }, - "Env": { + "title": "Value" + } + }, + "type": "object", + "title": "DiscreteResourceSpec" + }, + "DnsConfig": { + "properties": { + "Nameservers": { "anyOf": [ { "items": { @@ -988,34 +1043,25 @@ "type": "null" } ], - "title": "Env", - "description": "A list of environment variables in the form `VAR=value`.\n" - }, - "Dir": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Dir", - "description": "The working directory for commands to run in." + "title": "Nameservers", + "description": "The IP addresses of the name servers." }, - "User": { + "Search": { "anyOf": [ { - "type": "string" + "items": { + "type": "string" + }, + "type": "array" }, { "type": "null" } ], - "title": "User", - "description": "The user inside the container." + "title": "Search", + "description": "A search list for host-name lookup." }, - "Groups": { + "Options": { "anyOf": [ { "items": { @@ -1027,110 +1073,130 @@ "type": "null" } ], - "title": "Groups", - "description": "A list of additional groups that the container process will run as.\n" - }, - "Privileges": { + "title": "Options", + "description": "A list of internal resolver variables to be modified (e.g.,\n`debug`, `ndots:3`, etc.).\n" + } + }, + "type": "object", + "title": "DnsConfig", + "description": "Specification for DNS related configurations in resolver configuration\nfile (`resolv.conf`)." + }, + "DriverConfig": { + "properties": { + "Name": { "anyOf": [ { - "$ref": "#/components/schemas/Privileges" + "type": "string" }, { "type": "null" } ], - "description": "Security options for the container" + "title": "Name", + "description": "Name of the driver to use to create the volume." }, - "TTY": { + "Options": { "anyOf": [ { - "type": "boolean" + "additionalProperties": { + "type": "string" + }, + "type": "object" }, { "type": "null" } ], - "title": "Tty", - "description": "Whether a pseudo-TTY should be allocated." - }, - "OpenStdin": { + "title": "Options", + "description": "key/value map of driver specific options." + } + }, + "type": "object", + "title": "DriverConfig", + "description": "Map of driver specific options" + }, + "EndpointPortConfig": { + "properties": { + "Name": { "anyOf": [ { - "type": "boolean" + "type": "string" }, { "type": "null" } ], - "title": "Openstdin", - "description": "Open `stdin`" + "title": "Name" }, - "ReadOnly": { + "Protocol": { "anyOf": [ { - "type": "boolean" + "$ref": "#/components/schemas/Type" }, { "type": "null" } - ], - "title": "Readonly", - "description": "Mount the container's root filesystem as read only." + ] }, - "Mounts": { + "TargetPort": { "anyOf": [ { - "items": { - "$ref": "#/components/schemas/Mount" - }, - "type": "array" + "type": "integer" }, { "type": "null" } ], - "title": "Mounts", - "description": "Specification for mounts to be added to containers created as part\nof the service.\n" + "title": "Targetport", + "description": "The port inside the container." }, - "StopSignal": { + "PublishedPort": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Stopsignal", - "description": "Signal to stop the container." + "title": "Publishedport", + "description": "The port on the swarm hosts." }, - "StopGracePeriod": { + "PublishMode": { "anyOf": [ { - "type": "integer" + "$ref": "#/components/schemas/PublishMode" }, { "type": "null" } ], - "title": "Stopgraceperiod", - "description": "Amount of time to wait for the container to terminate before\nforcefully killing it.\n" - }, - "HealthCheck": { + "description": "The mode in which port is published.\n\n


\n\n- \"ingress\" makes the target port accessible on every node,\n regardless of whether there is a task for the service running on\n that node or not.\n- \"host\" bypasses the routing mesh and publish the port directly on\n the swarm node where that service is running.\n", + "default": "ingress" + } + }, + "type": "object", + "title": "EndpointPortConfig" + }, + "EndpointSpec": { + "properties": { + "Mode": { "anyOf": [ { - "$ref": "#/components/schemas/HealthConfig" + "$ref": "#/components/schemas/Mode1" }, { "type": "null" } - ] + ], + "description": "The mode of resolution to use for internal load balancing between tasks.\n", + "default": "vip" }, - "Hosts": { + "Ports": { "anyOf": [ { "items": { - "type": "string" + "$ref": "#/components/schemas/EndpointPortConfig" }, "type": "array" }, @@ -1138,212 +1204,193 @@ "type": "null" } ], - "title": "Hosts", - "description": "A list of hostname/IP mappings to add to the container's `hosts`\nfile. The format of extra hosts is specified in the\n[hosts(5)](http://man7.org/linux/man-pages/man5/hosts.5.html)\nman page:\n\n IP_address canonical_hostname [aliases...]\n" - }, - "DNSConfig": { - "anyOf": [ - { - "$ref": "#/components/schemas/DnsConfig" - }, - { - "type": "null" - } - ], - "description": "Specification for DNS related configurations in resolver configuration\nfile (`resolv.conf`).\n" - }, - "Secrets": { + "title": "Ports", + "description": "List of exposed ports that this service is accessible on from the\noutside. Ports can only be provided if `vip` resolution mode is used.\n" + } + }, + "type": "object", + "title": "EndpointSpec", + "description": "Properties that can be configured to access and load balance a service." + }, + "FailureAction": { + "type": "string", + "enum": [ + "continue", + "pause", + "rollback" + ], + "title": "FailureAction", + "description": "Action to take if an updated task fails to run, or stops running\nduring the update." + }, + "FailureAction1": { + "type": "string", + "enum": [ + "continue", + "pause" + ], + "title": "FailureAction1", + "description": "Action to take if an rolled back task fails to run, or stops\nrunning during the rollback." + }, + "File": { + "properties": { + "Name": { "anyOf": [ { - "items": { - "$ref": "#/components/schemas/Secret" - }, - "type": "array" + "type": "string" }, { "type": "null" } ], - "title": "Secrets", - "description": "Secrets contains references to zero or more secrets that will be\nexposed to the service.\n" + "title": "Name", + "description": "Name represents the final filename in the filesystem.\n" }, - "Configs": { + "UID": { "anyOf": [ { - "items": { - "$ref": "#/components/schemas/Config1" - }, - "type": "array" + "type": "string" }, { "type": "null" } ], - "title": "Configs", - "description": "Configs contains references to zero or more configs that will be\nexposed to the service.\n" + "title": "Uid", + "description": "UID represents the file UID." }, - "Isolation": { + "GID": { "anyOf": [ { - "$ref": "#/components/schemas/Isolation1" + "type": "string" }, { "type": "null" } ], - "description": "Isolation technology of the containers running the service.\n(Windows only)\n" + "title": "Gid", + "description": "GID represents the file GID." }, - "Init": { + "Mode": { "anyOf": [ { - "type": "boolean" + "type": "integer" }, { "type": "null" } ], - "title": "Init", - "description": "Run an init inside the container that forwards signals and reaps\nprocesses. This field is omitted if empty, and the default (as\nconfigured on the daemon) is used.\n" - }, - "Sysctls": { + "title": "Mode", + "description": "Mode represents the FileMode of the file." + } + }, + "type": "object", + "title": "File", + "description": "File represents a specific target that is backed by a file." + }, + "File1": { + "properties": { + "Name": { "anyOf": [ { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "type": "string" }, { "type": "null" } ], - "title": "Sysctls", - "description": "Set kernel namedspaced parameters (sysctls) in the container.\nThe Sysctls option on services accepts the same sysctls as the\nare supported on containers. Note that while the same sysctls are\nsupported, no guarantees or checks are made about their\nsuitability for a clustered environment, and it's up to the user\nto determine whether a given sysctl will work properly in a\nService.\n" + "title": "Name", + "description": "Name represents the final filename in the filesystem.\n" }, - "CapabilityAdd": { + "UID": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "type": "string" }, { "type": "null" } ], - "title": "Capabilityadd", - "description": "A list of kernel capabilities to add to the default set\nfor the container.\n" + "title": "Uid", + "description": "UID represents the file UID." }, - "CapabilityDrop": { + "GID": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "type": "string" }, { "type": "null" } ], - "title": "Capabilitydrop", - "description": "A list of kernel capabilities to drop from the default set\nfor the container.\n" + "title": "Gid", + "description": "GID represents the file GID." }, - "Ulimits": { + "Mode": { "anyOf": [ { - "items": { - "$ref": "#/components/schemas/Ulimit" - }, - "type": "array" + "type": "integer" }, { "type": "null" } ], - "title": "Ulimits", - "description": "A list of resource limits to set in the container. For example: `{\"Name\": \"nofile\", \"Soft\": 1024, \"Hard\": 2048}`\"\n" + "title": "Mode", + "description": "Mode represents the FileMode of the file." } }, "type": "object", - "title": "ContainerSpec", - "description": "Container spec for the service.\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`." + "title": "File1", + "description": "File represents a specific target that is backed by a file.\n\n


\n\n> **Note**: `Configs.File` and `Configs.Runtime` are mutually exclusive" }, - "CredentialSpec": { + "GenericResource": { "properties": { - "Config": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Config", - "description": "Load credential spec from a Swarm Config with the given ID.\nThe specified config must also be present in the Configs\nfield with the Runtime property set.\n\n


\n\n\n> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`,\n> and `CredentialSpec.Config` are mutually exclusive.\n" - }, - "File": { + "NamedResourceSpec": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/NamedResourceSpec" }, { "type": "null" } - ], - "title": "File", - "description": "Load credential spec from this file. The file is read by\nthe daemon, and must be present in the `CredentialSpecs`\nsubdirectory in the docker data directory, which defaults\nto `C:\\ProgramData\\Docker\\` on Windows.\n\nFor example, specifying `spec.json` loads\n`C:\\ProgramData\\Docker\\CredentialSpecs\\spec.json`.\n\n


\n\n> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`,\n> and `CredentialSpec.Config` are mutually exclusive.\n" + ] }, - "Registry": { + "DiscreteResourceSpec": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/DiscreteResourceSpec" }, { "type": "null" } - ], - "title": "Registry", - "description": "Load credential spec from this value in the Windows\nregistry. The specified registry value must be located in:\n\n`HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Virtualization\\Containers\\CredentialSpecs`\n\n


\n\n\n> **Note**: `CredentialSpec.File`, `CredentialSpec.Registry`,\n> and `CredentialSpec.Config` are mutually exclusive.\n" + ] } }, "type": "object", - "title": "CredentialSpec", - "description": "CredentialSpec for managed service account (Windows only)" + "title": "GenericResource" }, - "DiscreteResourceSpec": { + "GenericResources": { + "items": { + "$ref": "#/components/schemas/GenericResource" + }, + "type": "array", + "title": "GenericResources", + "description": "User-defined resources can be either Integer resources (e.g, `SSD=3`) or\nString resources (e.g, `GPU=UUID1`)." + }, + "HTTPValidationError": { "properties": { - "Kind": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Kind" - }, - "Value": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Value" + "errors": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Validation errors" } }, "type": "object", - "title": "DiscreteResourceSpec" + "title": "HTTPValidationError" }, - "DnsConfig": { + "HealthConfig": { "properties": { - "Nameservers": { + "Test": { "anyOf": [ { "items": { @@ -1355,102 +1402,156 @@ "type": "null" } ], - "title": "Nameservers", - "description": "The IP addresses of the name servers." + "title": "Test", + "description": "The test to perform. Possible values are:\n\n- `[]` inherit healthcheck from image or parent image\n- `[\"NONE\"]` disable healthcheck\n- `[\"CMD\", args...]` exec arguments directly\n- `[\"CMD-SHELL\", command]` run command with system's default shell\n" }, - "Search": { + "Interval": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "type": "integer" }, { "type": "null" } ], - "title": "Search", - "description": "A search list for host-name lookup." + "title": "Interval", + "description": "The time to wait between checks in nanoseconds. It should be 0 or at\nleast 1000000 (1 ms). 0 means inherit.\n" }, - "Options": { + "Timeout": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "type": "integer" }, { "type": "null" } ], - "title": "Options", - "description": "A list of internal resolver variables to be modified (e.g.,\n`debug`, `ndots:3`, etc.).\n" - } - }, - "type": "object", - "title": "DnsConfig", - "description": "Specification for DNS related configurations in resolver configuration\nfile (`resolv.conf`)." - }, - "DriverConfig": { - "properties": { - "Name": { + "title": "Timeout", + "description": "The time to wait before considering the check to have hung. It should\nbe 0 or at least 1000000 (1 ms). 0 means inherit.\n" + }, + "Retries": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Name", - "description": "Name of the driver to use to create the volume." + "title": "Retries", + "description": "The number of consecutive failures needed to consider a container as\nunhealthy. 0 means inherit.\n" }, - "Options": { + "StartPeriod": { "anyOf": [ { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "type": "integer" }, { "type": "null" } ], - "title": "Options", - "description": "key/value map of driver specific options." + "title": "Startperiod", + "description": "Start period for the container to initialize before starting\nhealth-retries countdown in nanoseconds. It should be 0 or at least\n1000000 (1 ms). 0 means inherit.\n" } }, "type": "object", - "title": "DriverConfig", - "description": "Map of driver specific options" + "title": "HealthConfig", + "description": "A test to perform to check that the container is healthy." }, - "EndpointPortConfig": { + "ImageResources": { "properties": { - "Name": { + "image": { + "type": "string", + "pattern": "^(?:([a-z0-9-]+(?:\\.[a-z0-9-]+)+(?::\\d+)?|[a-z0-9-]+:\\d+)/)?((?:[a-z0-9][a-z0-9_.-]*/)*[a-z0-9-_]+[a-z0-9])(?::([\\w][\\w.-]{0,127}))?(\\@sha256:[a-fA-F0-9]{32,64})?$", + "title": "Image", + "description": "Used by the frontend to provide a context for the users.Services with a docker-compose spec will have multiple entries.Using the `image:version` instead of the docker-compose spec is more helpful for the end user." + }, + "resources": { + "additionalProperties": { + "$ref": "#/components/schemas/ResourceValue" + }, + "type": "object", + "title": "Resources" + }, + "boot_modes": { + "items": { + "$ref": "#/components/schemas/BootMode" + }, + "type": "array", + "title": "Boot Modes", + "description": "describe how a service shall be booted, using CPU, MPI, openMP or GPU", + "default": [ + "CPU" + ] + } + }, + "type": "object", + "required": [ + "image", + "resources" + ], + "title": "ImageResources", + "example": { + "image": "simcore/service/dynamic/pretty-intense:1.0.0", + "resources": { + "AIRAM": { + "limit": 1, + "reservation": 1 + }, + "ANY_resource": { + "limit": "some_value", + "reservation": "some_value" + }, + "CPU": { + "limit": 4, + "reservation": 0.1 + }, + "RAM": { + "limit": 103079215104, + "reservation": 536870912 + }, + "VRAM": { + "limit": 1, + "reservation": 1 + } + } + } + }, + "Isolation1": { + "type": "string", + "enum": [ + "default", + "process", + "hyperv" + ], + "title": "Isolation1", + "description": "Isolation technology of the containers running the service.\n(Windows only)" + }, + "Limit": { + "properties": { + "NanoCPUs": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Name" + "title": "Nanocpus" }, - "Protocol": { + "MemoryBytes": { "anyOf": [ { - "$ref": "#/components/schemas/Type" + "type": "integer" }, { "type": "null" } - ] + ], + "title": "Memorybytes" }, - "TargetPort": { + "Pids": { "anyOf": [ { "type": "integer" @@ -1459,93 +1560,110 @@ "type": "null" } ], - "title": "Targetport", - "description": "The port inside the container." - }, - "PublishedPort": { + "title": "Pids", + "description": "Limits the maximum number of PIDs in the container. Set `0` for unlimited.\n", + "default": 0 + } + }, + "type": "object", + "title": "Limit", + "description": "An object describing a limit on resources which can be requested by a task." + }, + "LogDriver1": { + "properties": { + "Name": { "anyOf": [ { - "type": "integer" + "type": "string" }, { "type": "null" } ], - "title": "Publishedport", - "description": "The port on the swarm hosts." + "title": "Name" }, - "PublishMode": { + "Options": { "anyOf": [ { - "$ref": "#/components/schemas/PublishMode" + "additionalProperties": { + "type": "string" + }, + "type": "object" }, { "type": "null" } ], - "description": "The mode in which port is published.\n\n


\n\n- \"ingress\" makes the target port accessible on every node,\n regardless of whether there is a task for the service running on\n that node or not.\n- \"host\" bypasses the routing mesh and publish the port directly on\n the swarm node where that service is running.\n", - "default": "ingress" + "title": "Options" } }, "type": "object", - "title": "EndpointPortConfig" + "title": "LogDriver1", + "description": "Specifies the log driver to use for tasks created from this spec. If\nnot present, the default one for the swarm will be used, finally\nfalling back to the engine default if not specified." }, - "EndpointSpec": { + "Mode": { "properties": { - "Mode": { + "Replicated": { "anyOf": [ { - "$ref": "#/components/schemas/Mode1" + "$ref": "#/components/schemas/Replicated" + }, + { + "type": "null" + } + ] + }, + "Global": { + "anyOf": [ + { + "type": "object" }, { "type": "null" } ], - "description": "The mode of resolution to use for internal load balancing between tasks.\n", - "default": "vip" + "title": "Global" }, - "Ports": { + "ReplicatedJob": { "anyOf": [ { - "items": { - "$ref": "#/components/schemas/EndpointPortConfig" - }, - "type": "array" + "$ref": "#/components/schemas/ReplicatedJob" }, { "type": "null" } ], - "title": "Ports", - "description": "List of exposed ports that this service is accessible on from the\noutside. Ports can only be provided if `vip` resolution mode is used.\n" + "description": "The mode used for services with a finite number of tasks that run\nto a completed state.\n" + }, + "GlobalJob": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Globaljob", + "description": "The mode used for services which run a task to the completed state\non each valid node.\n" } }, "type": "object", - "title": "EndpointSpec", - "description": "Properties that can be configured to access and load balance a service." - }, - "FailureAction": { - "type": "string", - "enum": [ - "continue", - "pause", - "rollback" - ], - "title": "FailureAction", - "description": "Action to take if an updated task fails to run, or stops running\nduring the update." + "title": "Mode", + "description": "Scheduling mode for the service." }, - "FailureAction1": { + "Mode1": { "type": "string", "enum": [ - "continue", - "pause" + "vip", + "dnsrr" ], - "title": "FailureAction1", - "description": "Action to take if an rolled back task fails to run, or stops\nrunning during the rollback." + "title": "Mode1", + "description": "The mode of resolution to use for internal load balancing between tasks." }, - "File": { + "Mount": { "properties": { - "Name": { + "Target": { "anyOf": [ { "type": "string" @@ -1554,10 +1672,10 @@ "type": "null" } ], - "title": "Name", - "description": "Name represents the final filename in the filesystem.\n" + "title": "Target", + "description": "Container path." }, - "UID": { + "Source": { "anyOf": [ { "type": "string" @@ -1566,41 +1684,33 @@ "type": "null" } ], - "title": "Uid", - "description": "UID represents the file UID." + "title": "Source", + "description": "Mount source (e.g. a volume name, a host path)." }, - "GID": { + "Type": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/Type2" }, { "type": "null" } ], - "title": "Gid", - "description": "GID represents the file GID." + "description": "The mount type. Available types:\n\n- `bind` Mounts a file or directory from the host into the container. Must exist prior to creating the container.\n- `volume` Creates a volume with the given name and options (or uses a pre-existing volume with the same name and options). These are **not** removed when the container is removed.\n- `tmpfs` Create a tmpfs with the given options. The mount source cannot be specified for tmpfs.\n- `npipe` Mounts a named pipe from the host into the container. Must exist prior to creating the container.\n" }, - "Mode": { + "ReadOnly": { "anyOf": [ { - "type": "integer" + "type": "boolean" }, { "type": "null" } ], - "title": "Mode", - "description": "Mode represents the FileMode of the file." - } - }, - "type": "object", - "title": "File", - "description": "File represents a specific target that is backed by a file." - }, - "File1": { - "properties": { - "Name": { + "title": "Readonly", + "description": "Whether the mount should be read-only." + }, + "Consistency": { "anyOf": [ { "type": "string" @@ -1609,100 +1719,89 @@ "type": "null" } ], - "title": "Name", - "description": "Name represents the final filename in the filesystem.\n" + "title": "Consistency", + "description": "The consistency requirement for the mount: `default`, `consistent`, `cached`, or `delegated`." }, - "UID": { + "BindOptions": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/BindOptions" }, { "type": "null" } ], - "title": "Uid", - "description": "UID represents the file UID." + "description": "Optional configuration for the `bind` type." }, - "GID": { + "VolumeOptions": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/VolumeOptions" }, { "type": "null" } ], - "title": "Gid", - "description": "GID represents the file GID." + "description": "Optional configuration for the `volume` type." }, - "Mode": { + "TmpfsOptions": { "anyOf": [ { - "type": "integer" + "$ref": "#/components/schemas/TmpfsOptions" }, { "type": "null" } ], - "title": "Mode", - "description": "Mode represents the FileMode of the file." + "description": "Optional configuration for the `tmpfs` type." } }, "type": "object", - "title": "File1", - "description": "File represents a specific target that is backed by a file.\n\n


\n\n> **Note**: `Configs.File` and `Configs.Runtime` are mutually exclusive" + "title": "Mount" }, - "GenericResource": { + "NamedResourceSpec": { "properties": { - "NamedResourceSpec": { + "Kind": { "anyOf": [ { - "$ref": "#/components/schemas/NamedResourceSpec" + "type": "string" }, { "type": "null" } - ] + ], + "title": "Kind" }, - "DiscreteResourceSpec": { + "Value": { "anyOf": [ { - "$ref": "#/components/schemas/DiscreteResourceSpec" + "type": "string" }, { "type": "null" } - ] - } - }, - "type": "object", - "title": "GenericResource" - }, - "GenericResources": { - "items": { - "$ref": "#/components/schemas/GenericResource" - }, - "type": "array", - "title": "GenericResources", - "description": "User-defined resources can be either Integer resources (e.g, `SSD=3`) or\nString resources (e.g, `GPU=UUID1`)." - }, - "HTTPValidationError": { - "properties": { - "errors": { - "items": { - "$ref": "#/components/schemas/ValidationError" - }, - "type": "array", - "title": "Validation errors" + ], + "title": "Value" } }, "type": "object", - "title": "HTTPValidationError" + "title": "NamedResourceSpec" }, - "HealthConfig": { + "NetworkAttachmentConfig": { "properties": { - "Test": { + "Target": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Target", + "description": "The target network for attachment. Must be a network name or ID.\n" + }, + "Aliases": { "anyOf": [ { "items": { @@ -1714,156 +1813,148 @@ "type": "null" } ], - "title": "Test", - "description": "The test to perform. Possible values are:\n\n- `[]` inherit healthcheck from image or parent image\n- `[\"NONE\"]` disable healthcheck\n- `[\"CMD\", args...]` exec arguments directly\n- `[\"CMD-SHELL\", command]` run command with system's default shell\n" + "title": "Aliases", + "description": "Discoverable alternate names for the service on this network.\n" }, - "Interval": { + "DriverOpts": { "anyOf": [ { - "type": "integer" + "additionalProperties": { + "type": "string" + }, + "type": "object" }, { "type": "null" } ], - "title": "Interval", - "description": "The time to wait between checks in nanoseconds. It should be 0 or at\nleast 1000000 (1 ms). 0 means inherit.\n" - }, - "Timeout": { + "title": "Driveropts", + "description": "Driver attachment options for the network target.\n" + } + }, + "type": "object", + "title": "NetworkAttachmentConfig", + "description": "Specifies how a service should be attached to a particular network." + }, + "NetworkAttachmentSpec": { + "properties": { + "ContainerID": { "anyOf": [ { - "type": "integer" + "type": "string" }, { "type": "null" } ], - "title": "Timeout", - "description": "The time to wait before considering the check to have hung. It should\nbe 0 or at least 1000000 (1 ms). 0 means inherit.\n" + "title": "Containerid", + "description": "ID of the container represented by this task" + } + }, + "type": "object", + "title": "NetworkAttachmentSpec", + "description": "Read-only spec type for non-swarm containers attached to swarm overlay\nnetworks.\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`." + }, + "NodeRequirements": { + "properties": { + "CPU": { + "type": "number", + "exclusiveMinimum": true, + "title": "Cpu", + "description": "defines the required (maximum) CPU shares for running the services", + "minimum": 0.0 }, - "Retries": { + "GPU": { "anyOf": [ { - "type": "integer" + "type": "integer", + "minimum": 0 }, { "type": "null" } ], - "title": "Retries", - "description": "The number of consecutive failures needed to consider a container as\nunhealthy. 0 means inherit.\n" + "title": "Gpu", + "description": "defines the required (maximum) GPU for running the services" }, - "StartPeriod": { + "RAM": { + "type": "integer", + "minimum": 0, + "title": "Ram", + "description": "defines the required (maximum) amount of RAM for running the services" + }, + "VRAM": { "anyOf": [ { - "type": "integer" + "type": "integer", + "minimum": 0 }, { "type": "null" } ], - "title": "Startperiod", - "description": "Start period for the container to initialize before starting\nhealth-retries countdown in nanoseconds. It should be 0 or at least\n1000000 (1 ms). 0 means inherit.\n" - } - }, - "type": "object", - "title": "HealthConfig", - "description": "A test to perform to check that the container is healthy." - }, - "ImageResources": { - "properties": { - "image": { - "type": "string", - "pattern": "^(?:([a-z0-9-]+(?:\\.[a-z0-9-]+)+(?::\\d+)?|[a-z0-9-]+:\\d+)/)?((?:[a-z0-9][a-z0-9_.-]*/)*[a-z0-9-_]+[a-z0-9])(?::([\\w][\\w.-]{0,127}))?(\\@sha256:[a-fA-F0-9]{32,64})?$", - "title": "Image", - "description": "Used by the frontend to provide a context for the users.Services with a docker-compose spec will have multiple entries.Using the `image:version` instead of the docker-compose spec is more helpful for the end user." - }, - "resources": { - "additionalProperties": { - "$ref": "#/components/schemas/ResourceValue" - }, - "type": "object", - "title": "Resources" - }, - "boot_modes": { - "items": { - "$ref": "#/components/schemas/BootMode" - }, - "type": "array", - "title": "Boot Modes", - "description": "describe how a service shall be booted, using CPU, MPI, openMP or GPU", - "default": [ - "CPU" - ] + "title": "Vram", + "description": "defines the required (maximum) amount of VRAM for running the services" } }, "type": "object", "required": [ - "image", - "resources" + "CPU", + "RAM" ], - "title": "ImageResources", - "example": { - "image": "simcore/service/dynamic/pretty-intense:1.0.0", - "resources": { - "AIRAM": { - "limit": 1, - "reservation": 1 - }, - "ANY_resource": { - "limit": "some_value", - "reservation": "some_value" - }, - "CPU": { - "limit": 4, - "reservation": 0.1 - }, - "RAM": { - "limit": 103079215104, - "reservation": 536870912 - }, - "VRAM": { - "limit": 1, - "reservation": 1 - } - } - } + "title": "NodeRequirements" }, - "Isolation1": { + "Order": { "type": "string", "enum": [ - "default", - "process", - "hyperv" + "stop-first", + "start-first" ], - "title": "Isolation1", - "description": "Isolation technology of the containers running the service.\n(Windows only)" + "title": "Order", + "description": "The order of operations when rolling out an updated task. Either\nthe old task is shut down before the new task is started, or the\nnew task is started before the old task is shut down." }, - "Limit": { + "Order1": { + "type": "string", + "enum": [ + "stop-first", + "start-first" + ], + "title": "Order1", + "description": "The order of operations when rolling back a task. Either the old\ntask is shut down before the new task is started, or the new task\nis started before the old task is shut down." + }, + "Placement": { "properties": { - "NanoCPUs": { + "Constraints": { "anyOf": [ { - "type": "integer" + "items": { + "type": "string" + }, + "type": "array" }, { "type": "null" } ], - "title": "Nanocpus" + "title": "Constraints", + "description": "An array of constraint expressions to limit the set of nodes where\na task can be scheduled. Constraint expressions can either use a\n_match_ (`==`) or _exclude_ (`!=`) rule. Multiple constraints find\nnodes that satisfy every expression (AND match). Constraints can\nmatch node or Docker Engine labels as follows:\n\nnode attribute | matches | example\n---------------------|--------------------------------|-----------------------------------------------\n`node.id` | Node ID | `node.id==2ivku8v2gvtg4`\n`node.hostname` | Node hostname | `node.hostname!=node-2`\n`node.role` | Node role (`manager`/`worker`) | `node.role==manager`\n`node.platform.os` | Node operating system | `node.platform.os==windows`\n`node.platform.arch` | Node architecture | `node.platform.arch==x86_64`\n`node.labels` | User-defined node labels | `node.labels.security==high`\n`engine.labels` | Docker Engine's labels | `engine.labels.operatingsystem==ubuntu-14.04`\n\n`engine.labels` apply to Docker Engine labels like operating system,\ndrivers, etc. Swarm administrators add `node.labels` for operational\npurposes by using the [`node update endpoint`](#operation/NodeUpdate).\n" }, - "MemoryBytes": { + "Preferences": { "anyOf": [ { - "type": "integer" + "items": { + "$ref": "#/components/schemas/Preference" + }, + "type": "array" }, { "type": "null" } ], - "title": "Memorybytes" + "title": "Preferences", + "description": "Preferences provide a way to make the scheduler aware of factors\nsuch as topology. They are provided in order from highest to\nlowest precedence.\n" }, - "Pids": { + "MaxReplicas": { "anyOf": [ { "type": "integer" @@ -1872,18 +1963,32 @@ "type": "null" } ], - "title": "Pids", - "description": "Limits the maximum number of PIDs in the container. Set `0` for unlimited.\n", + "title": "Maxreplicas", + "description": "Maximum number of replicas for per node (default value is 0, which\nis unlimited)\n", "default": 0 + }, + "Platforms": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/Platform" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Platforms", + "description": "Platforms stores all the platforms that the service's image can\nrun on. This field is used in the platform filter for scheduling.\nIf empty, then the platform filter is off, meaning there are no\nscheduling restrictions.\n" } }, "type": "object", - "title": "Limit", - "description": "An object describing a limit on resources which can be requested by a task." + "title": "Placement" }, - "LogDriver1": { + "Platform": { "properties": { - "Name": { + "Architecture": { "anyOf": [ { "type": "string" @@ -1892,90 +1997,72 @@ "type": "null" } ], - "title": "Name" + "title": "Architecture", + "description": "Architecture represents the hardware architecture (for example,\n`x86_64`).\n" }, - "Options": { + "OS": { "anyOf": [ { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "type": "string" }, { "type": "null" } ], - "title": "Options" + "title": "Os", + "description": "OS represents the Operating System (for example, `linux` or `windows`).\n" } }, "type": "object", - "title": "LogDriver1", - "description": "Specifies the log driver to use for tasks created from this spec. If\nnot present, the default one for the swarm will be used, finally\nfalling back to the engine default if not specified." + "title": "Platform", + "description": "Platform represents the platform (Arch/OS)." }, - "Mode": { + "PluginPrivilege": { "properties": { - "Replicated": { - "anyOf": [ - { - "$ref": "#/components/schemas/Replicated" - }, - { - "type": "null" - } - ] - }, - "Global": { + "Name": { "anyOf": [ { - "type": "object" + "type": "string" }, { "type": "null" } ], - "title": "Global" + "title": "Name" }, - "ReplicatedJob": { + "Description": { "anyOf": [ { - "$ref": "#/components/schemas/ReplicatedJob" + "type": "string" }, { "type": "null" } ], - "description": "The mode used for services with a finite number of tasks that run\nto a completed state.\n" + "title": "Description" }, - "GlobalJob": { + "Value": { "anyOf": [ { - "type": "object" + "items": { + "type": "string" + }, + "type": "array" }, { "type": "null" } ], - "title": "Globaljob", - "description": "The mode used for services which run a task to the completed state\non each valid node.\n" + "title": "Value" } }, "type": "object", - "title": "Mode", - "description": "Scheduling mode for the service." - }, - "Mode1": { - "type": "string", - "enum": [ - "vip", - "dnsrr" - ], - "title": "Mode1", - "description": "The mode of resolution to use for internal load balancing between tasks." + "title": "PluginPrivilege", + "description": "Describes a permission the user has to accept upon installing\nthe plugin." }, - "Mount": { + "PluginSpec": { "properties": { - "Target": { + "Name": { "anyOf": [ { "type": "string" @@ -1984,10 +2071,10 @@ "type": "null" } ], - "title": "Target", - "description": "Container path." + "title": "Name", + "description": "The name or 'alias' to use for the plugin." }, - "Source": { + "Remote": { "anyOf": [ { "type": "string" @@ -1996,228 +2083,288 @@ "type": "null" } ], - "title": "Source", - "description": "Mount source (e.g. a volume name, a host path)." + "title": "Remote", + "description": "The plugin image reference to use." }, - "Type": { + "Disabled": { "anyOf": [ { - "$ref": "#/components/schemas/Type2" + "type": "boolean" }, { "type": "null" } ], - "description": "The mount type. Available types:\n\n- `bind` Mounts a file or directory from the host into the container. Must exist prior to creating the container.\n- `volume` Creates a volume with the given name and options (or uses a pre-existing volume with the same name and options). These are **not** removed when the container is removed.\n- `tmpfs` Create a tmpfs with the given options. The mount source cannot be specified for tmpfs.\n- `npipe` Mounts a named pipe from the host into the container. Must exist prior to creating the container.\n" + "title": "Disabled", + "description": "Disable the plugin once scheduled." }, - "ReadOnly": { + "PluginPrivilege": { "anyOf": [ { - "type": "boolean" + "items": { + "$ref": "#/components/schemas/PluginPrivilege" + }, + "type": "array" }, { "type": "null" } ], - "title": "Readonly", - "description": "Whether the mount should be read-only." - }, - "Consistency": { + "title": "Pluginprivilege" + } + }, + "type": "object", + "title": "PluginSpec", + "description": "Plugin spec for the service. *(Experimental release only.)*\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`." + }, + "Preference": { + "properties": { + "Spread": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/Spread" }, { "type": "null" } - ], - "title": "Consistency", - "description": "The consistency requirement for the mount: `default`, `consistent`, `cached`, or `delegated`." - }, - "BindOptions": { + ] + } + }, + "type": "object", + "title": "Preference" + }, + "Privileges": { + "properties": { + "CredentialSpec": { "anyOf": [ { - "$ref": "#/components/schemas/BindOptions" + "$ref": "#/components/schemas/CredentialSpec" }, { "type": "null" } ], - "description": "Optional configuration for the `bind` type." + "description": "CredentialSpec for managed service account (Windows only)" }, - "VolumeOptions": { + "SELinuxContext": { "anyOf": [ { - "$ref": "#/components/schemas/VolumeOptions" + "$ref": "#/components/schemas/SeLinuxContext" }, { "type": "null" } ], - "description": "Optional configuration for the `volume` type." - }, - "TmpfsOptions": { + "description": "SELinux labels of the container" + } + }, + "type": "object", + "title": "Privileges", + "description": "Security options for the container" + }, + "Propagation": { + "type": "string", + "enum": [ + "private", + "rprivate", + "shared", + "rshared", + "slave", + "rslave" + ], + "title": "Propagation", + "description": "A propagation mode with the value `[r]private`, `[r]shared`, or `[r]slave`." + }, + "PublishMode": { + "type": "string", + "enum": [ + "ingress", + "host" + ], + "title": "PublishMode", + "description": "The mode in which port is published.\n\n


\n\n- \"ingress\" makes the target port accessible on every node,\n regardless of whether there is a task for the service running on\n that node or not.\n- \"host\" bypasses the routing mesh and publish the port directly on\n the swarm node where that service is running." + }, + "Replicated": { + "properties": { + "Replicas": { "anyOf": [ { - "$ref": "#/components/schemas/TmpfsOptions" + "type": "integer" }, { "type": "null" } ], - "description": "Optional configuration for the `tmpfs` type." + "title": "Replicas" } }, "type": "object", - "title": "Mount" + "title": "Replicated" }, - "NamedResourceSpec": { + "ReplicatedJob": { "properties": { - "Kind": { + "MaxConcurrent": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Kind" + "title": "Maxconcurrent", + "description": "The maximum number of replicas to run simultaneously.\n", + "default": 1 }, - "Value": { + "TotalCompletions": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Value" + "title": "Totalcompletions", + "description": "The total number of replicas desired to reach the Completed\nstate. If unset, will default to the value of `MaxConcurrent`\n" } }, "type": "object", - "title": "NamedResourceSpec" + "title": "ReplicatedJob", + "description": "The mode used for services with a finite number of tasks that run\nto a completed state." }, - "NetworkAttachmentConfig": { + "ResourceObject": { "properties": { - "Target": { + "NanoCPUs": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Target", - "description": "The target network for attachment. Must be a network name or ID.\n" + "title": "Nanocpus" }, - "Aliases": { + "MemoryBytes": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "type": "integer" }, { "type": "null" } ], - "title": "Aliases", - "description": "Discoverable alternate names for the service on this network.\n" + "title": "Memorybytes" }, - "DriverOpts": { + "GenericResources": { "anyOf": [ { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "$ref": "#/components/schemas/GenericResources" }, { "type": "null" } - ], - "title": "Driveropts", - "description": "Driver attachment options for the network target.\n" + ] } }, "type": "object", - "title": "NetworkAttachmentConfig", - "description": "Specifies how a service should be attached to a particular network." + "title": "ResourceObject", + "description": "An object describing the resources which can be advertised by a node and\nrequested by a task." }, - "NetworkAttachmentSpec": { + "ResourceValue": { "properties": { - "ContainerID": { + "limit": { "anyOf": [ + { + "type": "integer" + }, + { + "type": "number" + }, { "type": "string" + } + ], + "title": "Limit" + }, + "reservation": { + "anyOf": [ + { + "type": "integer" }, { - "type": "null" + "type": "number" + }, + { + "type": "string" } ], - "title": "Containerid", - "description": "ID of the container represented by this task" + "title": "Reservation" } }, "type": "object", - "title": "NetworkAttachmentSpec", - "description": "Read-only spec type for non-swarm containers attached to swarm overlay\nnetworks.\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`." - }, - "Order": { - "type": "string", - "enum": [ - "stop-first", - "start-first" + "required": [ + "limit", + "reservation" ], - "title": "Order", - "description": "The order of operations when rolling out an updated task. Either\nthe old task is shut down before the new task is started, or the\nnew task is started before the old task is shut down." + "title": "ResourceValue" }, - "Order1": { - "type": "string", - "enum": [ - "stop-first", - "start-first" - ], - "title": "Order1", - "description": "The order of operations when rolling back a task. Either the old\ntask is shut down before the new task is started, or the new task\nis started before the old task is shut down." + "Resources1": { + "properties": { + "Limits": { + "anyOf": [ + { + "$ref": "#/components/schemas/Limit" + }, + { + "type": "null" + } + ], + "description": "Define resources limits." + }, + "Reservations": { + "anyOf": [ + { + "$ref": "#/components/schemas/ResourceObject" + }, + { + "type": "null" + } + ], + "description": "Define resources reservation." + } + }, + "type": "object", + "title": "Resources1", + "description": "Resource requirements which apply to each individual container created\nas part of the service." }, - "Placement": { + "RestartPolicy1": { "properties": { - "Constraints": { + "Condition": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "$ref": "#/components/schemas/Condition" }, { "type": "null" } ], - "title": "Constraints", - "description": "An array of constraint expressions to limit the set of nodes where\na task can be scheduled. Constraint expressions can either use a\n_match_ (`==`) or _exclude_ (`!=`) rule. Multiple constraints find\nnodes that satisfy every expression (AND match). Constraints can\nmatch node or Docker Engine labels as follows:\n\nnode attribute | matches | example\n---------------------|--------------------------------|-----------------------------------------------\n`node.id` | Node ID | `node.id==2ivku8v2gvtg4`\n`node.hostname` | Node hostname | `node.hostname!=node-2`\n`node.role` | Node role (`manager`/`worker`) | `node.role==manager`\n`node.platform.os` | Node operating system | `node.platform.os==windows`\n`node.platform.arch` | Node architecture | `node.platform.arch==x86_64`\n`node.labels` | User-defined node labels | `node.labels.security==high`\n`engine.labels` | Docker Engine's labels | `engine.labels.operatingsystem==ubuntu-14.04`\n\n`engine.labels` apply to Docker Engine labels like operating system,\ndrivers, etc. Swarm administrators add `node.labels` for operational\npurposes by using the [`node update endpoint`](#operation/NodeUpdate).\n" + "description": "Condition for restart." }, - "Preferences": { + "Delay": { "anyOf": [ { - "items": { - "$ref": "#/components/schemas/Preference" - }, - "type": "array" + "type": "integer" }, { "type": "null" } ], - "title": "Preferences", - "description": "Preferences provide a way to make the scheduler aware of factors\nsuch as topology. They are provided in order from highest to\nlowest precedence.\n" + "title": "Delay", + "description": "Delay between restart attempts." }, - "MaxReplicas": { + "MaxAttempts": { "anyOf": [ { "type": "integer" @@ -2226,118 +2373,121 @@ "type": "null" } ], - "title": "Maxreplicas", - "description": "Maximum number of replicas for per node (default value is 0, which\nis unlimited)\n", + "title": "Maxattempts", + "description": "Maximum attempts to restart a given container before giving up\n(default value is 0, which is ignored).\n", "default": 0 }, - "Platforms": { + "Window": { "anyOf": [ { - "items": { - "$ref": "#/components/schemas/Platform" - }, - "type": "array" + "type": "integer" }, { "type": "null" } ], - "title": "Platforms", - "description": "Platforms stores all the platforms that the service's image can\nrun on. This field is used in the platform filter for scheduling.\nIf empty, then the platform filter is off, meaning there are no\nscheduling restrictions.\n" + "title": "Window", + "description": "Windows is the time window used to evaluate the restart policy\n(default value is 0, which is unbounded).\n", + "default": 0 } }, "type": "object", - "title": "Placement" + "title": "RestartPolicy1", + "description": "Specification for the restart policy which applies to containers\ncreated as part of this service." }, - "Platform": { + "RollbackConfig": { "properties": { - "Architecture": { + "Parallelism": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Architecture", - "description": "Architecture represents the hardware architecture (for example,\n`x86_64`).\n" + "title": "Parallelism", + "description": "Maximum number of tasks to be rolled back in one iteration (0 means\nunlimited parallelism).\n" }, - "OS": { + "Delay": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Os", - "description": "OS represents the Operating System (for example, `linux` or `windows`).\n" - } - }, - "type": "object", - "title": "Platform", - "description": "Platform represents the platform (Arch/OS)." - }, - "PluginPrivilege": { - "properties": { - "Name": { + "title": "Delay", + "description": "Amount of time between rollback iterations, in nanoseconds.\n" + }, + "FailureAction": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/FailureAction1" }, { "type": "null" } ], - "title": "Name" + "description": "Action to take if an rolled back task fails to run, or stops\nrunning during the rollback.\n" }, - "Description": { + "Monitor": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Description" + "title": "Monitor", + "description": "Amount of time to monitor each rolled back task for failures, in\nnanoseconds.\n" }, - "Value": { + "MaxFailureRatio": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "type": "number" }, { "type": "null" } ], - "title": "Value" + "title": "Maxfailureratio", + "description": "The fraction of tasks that may fail during a rollback before the\nfailure action is invoked, specified as a floating point number\nbetween 0 and 1.\n", + "default": 0 + }, + "Order": { + "anyOf": [ + { + "$ref": "#/components/schemas/Order1" + }, + { + "type": "null" + } + ], + "description": "The order of operations when rolling back a task. Either the old\ntask is shut down before the new task is started, or the new task\nis started before the old task is shut down.\n" } }, "type": "object", - "title": "PluginPrivilege", - "description": "Describes a permission the user has to accept upon installing\nthe plugin." + "title": "RollbackConfig", + "description": "Specification for the rollback strategy of the service." }, - "PluginSpec": { + "SeLinuxContext": { "properties": { - "Name": { + "Disable": { "anyOf": [ { - "type": "string" + "type": "boolean" }, { "type": "null" } ], - "title": "Name", - "description": "The name or 'alias' to use for the plugin." + "title": "Disable", + "description": "Disable SELinux" }, - "Remote": { + "User": { "anyOf": [ { "type": "string" @@ -2346,447 +2496,408 @@ "type": "null" } ], - "title": "Remote", - "description": "The plugin image reference to use." + "title": "User", + "description": "SELinux user label" }, - "Disabled": { + "Role": { "anyOf": [ { - "type": "boolean" + "type": "string" }, { "type": "null" } ], - "title": "Disabled", - "description": "Disable the plugin once scheduled." + "title": "Role", + "description": "SELinux role label" }, - "PluginPrivilege": { + "Type": { "anyOf": [ { - "items": { - "$ref": "#/components/schemas/PluginPrivilege" - }, - "type": "array" + "type": "string" }, { "type": "null" } ], - "title": "Pluginprivilege" - } - }, - "type": "object", - "title": "PluginSpec", - "description": "Plugin spec for the service. *(Experimental release only.)*\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`." - }, - "Preference": { - "properties": { - "Spread": { + "title": "Type", + "description": "SELinux type label" + }, + "Level": { "anyOf": [ { - "$ref": "#/components/schemas/Spread" + "type": "string" }, { "type": "null" } - ] + ], + "title": "Level", + "description": "SELinux level label" } }, "type": "object", - "title": "Preference" + "title": "SeLinuxContext", + "description": "SELinux labels of the container" }, - "Privileges": { + "Secret": { "properties": { - "CredentialSpec": { + "File": { "anyOf": [ { - "$ref": "#/components/schemas/CredentialSpec" + "$ref": "#/components/schemas/File" }, { "type": "null" } ], - "description": "CredentialSpec for managed service account (Windows only)" + "description": "File represents a specific target that is backed by a file.\n" }, - "SELinuxContext": { + "SecretID": { "anyOf": [ { - "$ref": "#/components/schemas/SeLinuxContext" + "type": "string" }, { "type": "null" } ], - "description": "SELinux labels of the container" - } - }, - "type": "object", - "title": "Privileges", - "description": "Security options for the container" - }, - "Propagation": { - "type": "string", - "enum": [ - "private", - "rprivate", - "shared", - "rshared", - "slave", - "rslave" - ], - "title": "Propagation", - "description": "A propagation mode with the value `[r]private`, `[r]shared`, or `[r]slave`." - }, - "PublishMode": { - "type": "string", - "enum": [ - "ingress", - "host" - ], - "title": "PublishMode", - "description": "The mode in which port is published.\n\n


\n\n- \"ingress\" makes the target port accessible on every node,\n regardless of whether there is a task for the service running on\n that node or not.\n- \"host\" bypasses the routing mesh and publish the port directly on\n the swarm node where that service is running." - }, - "Replicated": { - "properties": { - "Replicas": { + "title": "Secretid", + "description": "SecretID represents the ID of the specific secret that we're\nreferencing.\n" + }, + "SecretName": { "anyOf": [ { - "type": "integer" + "type": "string" }, { "type": "null" } ], - "title": "Replicas" + "title": "Secretname", + "description": "SecretName is the name of the secret that this references,\nbut this is just provided for lookup/display purposes. The\nsecret in the reference will be identified by its ID.\n" } }, "type": "object", - "title": "Replicated" + "title": "Secret" }, - "ReplicatedJob": { + "SelectBox": { "properties": { - "MaxConcurrent": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Maxconcurrent", - "description": "The maximum number of replicas to run simultaneously.\n", - "default": 1 - }, - "TotalCompletions": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Totalcompletions", - "description": "The total number of replicas desired to reach the Completed\nstate. If unset, will default to the value of `MaxConcurrent`\n" + "structure": { + "items": { + "$ref": "#/components/schemas/Structure" + }, + "type": "array", + "minItems": 1, + "title": "Structure" } }, + "additionalProperties": false, "type": "object", - "title": "ReplicatedJob", - "description": "The mode used for services with a finite number of tasks that run\nto a completed state." + "required": [ + "structure" + ], + "title": "SelectBox" }, - "ResourceObject": { + "ServiceAccessRightsGet": { "properties": { - "NanoCPUs": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Nanocpus" + "service_key": { + "type": "string", + "pattern": "^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "title": "Service Key" }, - "MemoryBytes": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Memorybytes" + "service_version": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", + "title": "Service Version" }, - "GenericResources": { - "anyOf": [ - { - "$ref": "#/components/schemas/GenericResources" + "gids_with_access_rights": { + "additionalProperties": { + "additionalProperties": { + "type": "boolean" }, - { - "type": "null" - } - ] + "type": "object" + }, + "type": "object", + "title": "Gids With Access Rights" } }, "type": "object", - "title": "ResourceObject", - "description": "An object describing the resources which can be advertised by a node and\nrequested by a task." + "required": [ + "service_key", + "service_version", + "gids_with_access_rights" + ], + "title": "ServiceAccessRightsGet" }, - "ResourceValue": { + "ServiceBuildDetails": { "properties": { - "limit": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "number" - }, - { - "type": "string" - } - ], - "title": "Limit" + "build_date": { + "type": "string", + "title": "Build Date" }, - "reservation": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "number" - }, - { - "type": "string" - } - ], - "title": "Reservation" + "vcs_ref": { + "type": "string", + "title": "Vcs Ref" + }, + "vcs_url": { + "type": "string", + "title": "Vcs Url" } }, "type": "object", "required": [ - "limit", - "reservation" + "build_date", + "vcs_ref", + "vcs_url" ], - "title": "ResourceValue" + "title": "ServiceBuildDetails" }, - "Resources1": { + "ServiceExtras": { "properties": { - "Limits": { + "node_requirements": { + "$ref": "#/components/schemas/NodeRequirements" + }, + "service_build_details": { "anyOf": [ { - "$ref": "#/components/schemas/Limit" + "$ref": "#/components/schemas/ServiceBuildDetails" }, { "type": "null" } - ], - "description": "Define resources limits." + ] }, - "Reservations": { + "container_spec": { "anyOf": [ { - "$ref": "#/components/schemas/ResourceObject" + "$ref": "#/components/schemas/models_library__service_settings_labels__ContainerSpec" }, { "type": "null" } - ], - "description": "Define resources reservation." + ] } }, "type": "object", - "title": "Resources1", - "description": "Resource requirements which apply to each individual container created\nas part of the service." + "required": [ + "node_requirements" + ], + "title": "ServiceExtras" }, - "RestartPolicy1": { + "ServiceGet": { "properties": { - "Condition": { + "name": { + "type": "string", + "title": "Name", + "description": "Display name: short, human readable name for the node" + }, + "thumbnail": { "anyOf": [ { - "$ref": "#/components/schemas/Condition" + "type": "string", + "maxLength": 2083, + "minLength": 1, + "format": "uri" }, { "type": "null" } ], - "description": "Condition for restart." + "title": "Thumbnail", + "description": "url to the thumbnail" }, - "Delay": { + "description": { + "type": "string", + "title": "Description", + "description": "human readable description of the purpose of the node" + }, + "description_ui": { + "type": "boolean", + "title": "Description Ui", + "description": "A flag to enable the `description` to be presented as a single web page (=true) or in another structured format (default=false).", + "default": false + }, + "version_display": { "anyOf": [ { - "type": "integer" + "type": "string" }, { "type": "null" } ], - "title": "Delay", - "description": "Delay between restart attempts." + "title": "Version Display", + "description": "A user-friendly or marketing name for the release. This can be used to reference the release in a more readable and recognizable format, such as 'Matterhorn Release,' 'Spring Update,' or 'Holiday Edition.' This name is not used for version comparison but is useful for communication and documentation purposes." }, - "MaxAttempts": { + "deprecated": { "anyOf": [ { - "type": "integer" + "type": "string", + "format": "date-time" }, { "type": "null" } ], - "title": "Maxattempts", - "description": "Maximum attempts to restart a given container before giving up\n(default value is 0, which is ignored).\n", - "default": 0 + "title": "Deprecated", + "description": "Owner can set the date to retire the service. Three possibilities:If None, the service is marked as `published`;If now=deprecated, the service is retired" }, - "Window": { + "classifiers": { "anyOf": [ { - "type": "integer" + "items": { + "type": "string" + }, + "type": "array" }, { "type": "null" } ], - "title": "Window", - "description": "Windows is the time window used to evaluate the restart policy\n(default value is 0, which is unbounded).\n", - "default": 0 - } - }, - "type": "object", - "title": "RestartPolicy1", - "description": "Specification for the restart policy which applies to containers\ncreated as part of this service." - }, - "RollbackConfig": { - "properties": { - "Parallelism": { + "title": "Classifiers" + }, + "quality": { + "type": "object", + "title": "Quality", + "default": {} + }, + "accessRights": { "anyOf": [ { - "type": "integer" + "additionalProperties": { + "$ref": "#/components/schemas/ServiceGroupAccessRights" + }, + "type": "object" }, { "type": "null" } ], - "title": "Parallelism", - "description": "Maximum number of tasks to be rolled back in one iteration (0 means\nunlimited parallelism).\n" + "title": "Accessrights", + "description": "service access rights per group id" }, - "Delay": { + "key": { + "type": "string", + "pattern": "^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", + "title": "Key", + "description": "distinctive name for the node based on the docker registry path" + }, + "version": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", + "title": "Version", + "description": "service version number" + }, + "release_date": { "anyOf": [ { - "type": "integer" + "type": "string", + "format": "date-time" }, { "type": "null" } ], - "title": "Delay", - "description": "Amount of time between rollback iterations, in nanoseconds.\n" + "title": "Release Date", + "description": "A timestamp when the specific version of the service was released. This field helps in tracking the timeline of releases and understanding the sequence of updates. A timestamp string should be formatted as YYYY-MM-DD[T]HH:MM[:SS[.ffffff]][Z or [\u00b1]HH[:]MM]" }, - "FailureAction": { + "integration-version": { "anyOf": [ { - "$ref": "#/components/schemas/FailureAction1" + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" }, { "type": "null" } ], - "description": "Action to take if an rolled back task fails to run, or stops\nrunning during the rollback.\n" + "title": "Integration-Version", + "description": "This version is used to maintain backward compatibility when there are changes in the way a service is integrated into the framework" }, - "Monitor": { + "type": { + "$ref": "#/components/schemas/ServiceType", + "description": "service type" + }, + "badges": { "anyOf": [ { - "type": "integer" - }, - { - "type": "null" - } - ], - "title": "Monitor", - "description": "Amount of time to monitor each rolled back task for failures, in\nnanoseconds.\n" - }, - "MaxFailureRatio": { - "anyOf": [ - { - "type": "number" + "items": { + "$ref": "#/components/schemas/Badge" + }, + "type": "array" }, { "type": "null" } ], - "title": "Maxfailureratio", - "description": "The fraction of tasks that may fail during a rollback before the\nfailure action is invoked, specified as a floating point number\nbetween 0 and 1.\n", - "default": 0 + "title": "Badges", + "deprecated": true }, - "Order": { - "anyOf": [ - { - "$ref": "#/components/schemas/Order1" - }, - { - "type": "null" - } - ], - "description": "The order of operations when rolling back a task. Either the old\ntask is shut down before the new task is started, or the new task\nis started before the old task is shut down.\n" - } - }, - "type": "object", - "title": "RollbackConfig", - "description": "Specification for the rollback strategy of the service." - }, - "SeLinuxContext": { - "properties": { - "Disable": { + "authors": { + "items": { + "$ref": "#/components/schemas/Author" + }, + "type": "array", + "minItems": 1, + "title": "Authors" + }, + "contact": { + "type": "string", + "format": "email", + "title": "Contact", + "description": "email to correspond to the authors about the node" + }, + "inputs": { "anyOf": [ { - "type": "boolean" + "type": "object" }, { "type": "null" } ], - "title": "Disable", - "description": "Disable SELinux" + "title": "Inputs", + "description": "definition of the inputs of this node" }, - "User": { + "outputs": { "anyOf": [ { - "type": "string" + "type": "object" }, { "type": "null" } ], - "title": "User", - "description": "SELinux user label" + "title": "Outputs", + "description": "definition of the outputs of this node" }, - "Role": { + "boot-options": { "anyOf": [ { - "type": "string" + "type": "object" }, { "type": "null" } ], - "title": "Role", - "description": "SELinux role label" + "title": "Boot-Options", + "description": "Service defined boot options. These get injected in the service as env variables." }, - "Type": { + "min-visible-inputs": { "anyOf": [ { - "type": "string" + "type": "integer", + "minimum": 0 }, { "type": "null" } ], - "title": "Type", - "description": "SELinux type label" + "title": "Min-Visible-Inputs", + "description": "The number of 'data type inputs' displayed by default in the UI. When None all 'data type inputs' are displayed." }, - "Level": { + "progress_regexp": { "anyOf": [ { "type": "string" @@ -2795,28 +2906,10 @@ "type": "null" } ], - "title": "Level", - "description": "SELinux level label" - } - }, - "type": "object", - "title": "SeLinuxContext", - "description": "SELinux labels of the container" - }, - "Secret": { - "properties": { - "File": { - "anyOf": [ - { - "$ref": "#/components/schemas/File" - }, - { - "type": "null" - } - ], - "description": "File represents a specific target that is backed by a file.\n" + "title": "Progress Regexp", + "description": "regexp pattern for detecting computational service's progress" }, - "SecretID": { + "image_digest": { "anyOf": [ { "type": "string" @@ -2825,300 +2918,136 @@ "type": "null" } ], - "title": "Secretid", - "description": "SecretID represents the ID of the specific secret that we're\nreferencing.\n" + "title": "Image Digest", + "description": "Image manifest digest. Note that this is NOT injected as an image label" }, - "SecretName": { + "owner": { "anyOf": [ { - "type": "string" + "type": "string", + "format": "email" }, { "type": "null" } ], - "title": "Secretname", - "description": "SecretName is the name of the secret that this references,\nbut this is just provided for lookup/display purposes. The\nsecret in the reference will be identified by its ID.\n" - } - }, - "type": "object", - "title": "Secret" - }, - "SelectBox": { - "properties": { - "structure": { - "items": { - "$ref": "#/components/schemas/Structure" - }, - "type": "array", - "minItems": 1, - "title": "Structure" + "title": "Owner", + "description": "None when the owner email cannot be found in the database" } }, - "additionalProperties": false, "type": "object", "required": [ - "structure" + "name", + "description", + "classifiers", + "key", + "version", + "type", + "authors", + "contact", + "inputs", + "outputs", + "owner" ], - "title": "SelectBox" + "title": "ServiceGet" }, - "ServiceAccessRightsGet": { + "ServiceGroupAccessRights": { "properties": { - "service_key": { - "type": "string", - "pattern": "^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", - "title": "Service Key" - }, - "service_version": { - "type": "string", - "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", - "title": "Service Version" + "execute_access": { + "type": "boolean", + "title": "Execute Access", + "description": "defines whether the group can execute the service", + "default": false }, - "gids_with_access_rights": { - "additionalProperties": { - "additionalProperties": { - "type": "boolean" - }, - "type": "object" - }, - "type": "object", - "title": "Gids With Access Rights" + "write_access": { + "type": "boolean", + "title": "Write Access", + "description": "defines whether the group can modify the service", + "default": false } }, "type": "object", - "required": [ - "service_key", - "service_version", - "gids_with_access_rights" - ], - "title": "ServiceAccessRightsGet" + "title": "ServiceGroupAccessRights" }, - "ServiceGet": { + "ServiceInput": { "properties": { - "name": { - "type": "string", - "title": "Name", - "description": "Display name: short, human readable name for the node" - }, - "thumbnail": { + "displayOrder": { "anyOf": [ { - "type": "string", - "maxLength": 2083, - "minLength": 1, - "format": "uri" + "type": "number" }, { "type": "null" } ], - "title": "Thumbnail", - "description": "url to the thumbnail" + "title": "Displayorder", + "description": "DEPRECATED: new display order is taken from the item position. This will be removed.", + "deprecated": true + }, + "label": { + "type": "string", + "title": "Label", + "description": "short name for the property" }, "description": { "type": "string", "title": "Description", - "description": "human readable description of the purpose of the node" + "description": "description of the property" }, - "description_ui": { - "type": "boolean", - "title": "Description Ui", - "description": "A flag to enable the `description` to be presented as a single web page (=true) or in another structured format (default=false).", - "default": false + "type": { + "type": "string", + "pattern": "^(number|integer|boolean|string|ref_contentSchema|data:([^/\\s,]+/[^/\\s,]+|\\[[^/\\s,]+/[^/\\s,]+(,[^/\\s]+/[^/,\\s]+)*\\]))$", + "title": "Type", + "description": "data type expected on this input glob matching for data type is allowed" }, - "version_display": { + "contentSchema": { "anyOf": [ { - "type": "string" + "type": "object" }, { "type": "null" } ], - "title": "Version Display", - "description": "A user-friendly or marketing name for the release. This can be used to reference the release in a more readable and recognizable format, such as 'Matterhorn Release,' 'Spring Update,' or 'Holiday Edition.' This name is not used for version comparison but is useful for communication and documentation purposes." + "title": "Contentschema", + "description": "jsonschema of this input/output. Required when type='ref_contentSchema'" }, - "deprecated": { + "fileToKeyMap": { "anyOf": [ { - "type": "string", - "format": "date-time" + "type": "object" }, { "type": "null" } ], - "title": "Deprecated", - "description": "Owner can set the date to retire the service. Three possibilities:If None, the service is marked as `published`;If now=deprecated, the service is retired" + "title": "Filetokeymap", + "description": "Place the data associated with the named keys in files" }, - "classifiers": { + "unit": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "type": "string" }, { "type": "null" } ], - "title": "Classifiers" + "title": "Unit", + "description": "Units, when it refers to a physical quantity", + "deprecated": true }, - "quality": { - "type": "object", - "title": "Quality", - "default": {} - }, - "accessRights": { - "anyOf": [ - { - "additionalProperties": { - "$ref": "#/components/schemas/ServiceGroupAccessRights" - }, - "type": "object" - }, - { - "type": "null" - } - ], - "title": "Accessrights", - "description": "service access rights per group id" - }, - "key": { - "type": "string", - "pattern": "^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", - "title": "Key", - "description": "distinctive name for the node based on the docker registry path" - }, - "version": { - "type": "string", - "pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$", - "title": "Version", - "description": "service version number" - }, - "release_date": { - "anyOf": [ - { - "type": "string", - "format": "date-time" - }, - { - "type": "null" - } - ], - "title": "Release Date", - "description": "A timestamp when the specific version of the service was released. This field helps in tracking the timeline of releases and understanding the sequence of updates. A timestamp string should be formatted as YYYY-MM-DD[T]HH:MM[:SS[.ffffff]][Z or [\u00b1]HH[:]MM]" - }, - "integration-version": { - "anyOf": [ - { - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" - }, - { - "type": "null" - } - ], - "title": "Integration-Version", - "description": "This version is used to maintain backward compatibility when there are changes in the way a service is integrated into the framework" - }, - "type": { - "$ref": "#/components/schemas/ServiceType", - "description": "service type" - }, - "badges": { - "anyOf": [ - { - "items": { - "$ref": "#/components/schemas/Badge" - }, - "type": "array" - }, - { - "type": "null" - } - ], - "title": "Badges", - "deprecated": true - }, - "authors": { - "items": { - "$ref": "#/components/schemas/Author" - }, - "type": "array", - "minItems": 1, - "title": "Authors" - }, - "contact": { - "type": "string", - "format": "email", - "title": "Contact", - "description": "email to correspond to the authors about the node" - }, - "inputs": { - "anyOf": [ - { - "type": "object" - }, - { - "type": "null" - } - ], - "title": "Inputs", - "description": "definition of the inputs of this node" - }, - "outputs": { - "anyOf": [ - { - "type": "object" - }, - { - "type": "null" - } - ], - "title": "Outputs", - "description": "definition of the outputs of this node" - }, - "boot-options": { + "defaultValue": { "anyOf": [ { - "type": "object" + "type": "boolean" }, { - "type": "null" - } - ], - "title": "Boot-Options", - "description": "Service defined boot options. These get injected in the service as env variables." - }, - "min-visible-inputs": { - "anyOf": [ - { - "type": "integer", - "minimum": 0 + "type": "integer" }, { - "type": "null" - } - ], - "title": "Min-Visible-Inputs", - "description": "The number of 'data type inputs' displayed by default in the UI. When None all 'data type inputs' are displayed." - }, - "progress_regexp": { - "anyOf": [ - { - "type": "string" + "type": "number" }, - { - "type": "null" - } - ], - "title": "Progress Regexp", - "description": "regexp pattern for detecting computational service's progress" - }, - "image_digest": { - "anyOf": [ { "type": "string" }, @@ -3126,58 +3055,32 @@ "type": "null" } ], - "title": "Image Digest", - "description": "Image manifest digest. Note that this is NOT injected as an image label" + "title": "Defaultvalue", + "deprecated": true }, - "owner": { + "widget": { "anyOf": [ { - "type": "string", - "format": "email" + "$ref": "#/components/schemas/Widget" }, { "type": "null" } ], - "title": "Owner", - "description": "None when the owner email cannot be found in the database" + "description": "custom widget to use instead of the default one determined from the data-type" } }, + "additionalProperties": false, "type": "object", "required": [ - "name", + "label", "description", - "classifiers", - "key", - "version", - "type", - "authors", - "contact", - "inputs", - "outputs", - "owner" + "type" ], - "title": "ServiceGet" - }, - "ServiceGroupAccessRights": { - "properties": { - "execute_access": { - "type": "boolean", - "title": "Execute Access", - "description": "defines whether the group can execute the service", - "default": false - }, - "write_access": { - "type": "boolean", - "title": "Write Access", - "description": "defines whether the group can modify the service", - "default": false - } - }, - "type": "object", - "title": "ServiceGroupAccessRights" + "title": "ServiceInput", + "description": "Metadata on a service input port" }, - "ServiceInput": { + "ServiceOutput": { "properties": { "displayOrder": { "anyOf": [ @@ -3245,27 +3148,6 @@ "description": "Units, when it refers to a physical quantity", "deprecated": true }, - "defaultValue": { - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "integer" - }, - { - "type": "number" - }, - { - "type": "string" - }, - { - "type": "null" - } - ], - "title": "Defaultvalue", - "deprecated": true - }, "widget": { "anyOf": [ { @@ -3275,7 +3157,8 @@ "type": "null" } ], - "description": "custom widget to use instead of the default one determined from the data-type" + "description": "custom widget to use instead of the default one determined from the data-type", + "deprecated": true } }, "additionalProperties": false, @@ -3285,116 +3168,249 @@ "description", "type" ], - "title": "ServiceInput", - "description": "Metadata on a service input port" + "title": "ServiceOutput" }, - "ServiceOutput": { + "ServicePortGet": { "properties": { - "displayOrder": { + "key": { + "type": "string", + "pattern": "^[^_\\W0-9]\\w*$", + "title": "Key name", + "description": "port identifier name" + }, + "kind": { + "type": "string", + "enum": [ + "input", + "output" + ], + "title": "Kind" + }, + "content_media_type": { "anyOf": [ { - "type": "number" + "type": "string" }, { "type": "null" } ], - "title": "Displayorder", - "description": "DEPRECATED: new display order is taken from the item position. This will be removed.", - "deprecated": true - }, - "label": { - "type": "string", - "title": "Label", - "description": "short name for the property" + "title": "Content Media Type" }, - "description": { - "type": "string", - "title": "Description", - "description": "description of the property" + "content_schema": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Content Schema", + "description": "jsonschema for the port's value. SEE https://json-schema.org/understanding-json-schema/" + } + }, + "type": "object", + "required": [ + "key", + "kind" + ], + "title": "ServicePortGet", + "example": { + "content_schema": { + "maximum": 5, + "minimum": 0, + "title": "Sleep interval", + "type": "integer", + "x_unit": "second" }, - "type": { - "type": "string", - "pattern": "^(number|integer|boolean|string|ref_contentSchema|data:([^/\\s,]+/[^/\\s,]+|\\[[^/\\s,]+/[^/\\s,]+(,[^/\\s]+/[^/,\\s]+)*\\]))$", - "title": "Type", - "description": "data type expected on this input glob matching for data type is allowed" + "key": "input_1", + "kind": "input" + } + }, + "ServiceSpec": { + "properties": { + "Name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "title": "Name", + "description": "Name of the service." }, - "contentSchema": { + "Labels": { "anyOf": [ { + "additionalProperties": { + "type": "string" + }, "type": "object" }, { "type": "null" } ], - "title": "Contentschema", - "description": "jsonschema of this input/output. Required when type='ref_contentSchema'" + "title": "Labels", + "description": "User-defined key/value metadata." }, - "fileToKeyMap": { + "TaskTemplate": { "anyOf": [ { - "type": "object" + "$ref": "#/components/schemas/TaskSpec" + }, + { + "type": "null" + } + ] + }, + "Mode": { + "anyOf": [ + { + "$ref": "#/components/schemas/Mode" }, { "type": "null" } ], - "title": "Filetokeymap", - "description": "Place the data associated with the named keys in files" + "description": "Scheduling mode for the service." }, - "unit": { + "UpdateConfig": { "anyOf": [ { - "type": "string" + "$ref": "#/components/schemas/UpdateConfig" }, { "type": "null" } ], - "title": "Unit", - "description": "Units, when it refers to a physical quantity", - "deprecated": true + "description": "Specification for the update strategy of the service." }, - "widget": { + "RollbackConfig": { "anyOf": [ { - "$ref": "#/components/schemas/Widget" + "$ref": "#/components/schemas/RollbackConfig" }, { "type": "null" } ], - "description": "custom widget to use instead of the default one determined from the data-type", - "deprecated": true + "description": "Specification for the rollback strategy of the service." + }, + "Networks": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/NetworkAttachmentConfig" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Networks", + "description": "Specifies which networks the service should attach to." + }, + "EndpointSpec": { + "anyOf": [ + { + "$ref": "#/components/schemas/EndpointSpec" + }, + { + "type": "null" + } + ] } }, - "additionalProperties": false, "type": "object", - "required": [ - "label", - "description", - "type" + "title": "ServiceSpec", + "description": "User modifiable configuration for a service." + }, + "ServiceSpecificationsGet": { + "properties": { + "sidecar": { + "anyOf": [ + { + "$ref": "#/components/schemas/ServiceSpec" + }, + { + "type": "null" + } + ], + "description": "schedule-time specifications for the service sidecar (follows Docker Service creation API, see https://docs.docker.com/engine/api/v1.25/#operation/ServiceCreate)" + }, + "service": { + "anyOf": [ + { + "$ref": "#/components/schemas/ServiceSpec" + }, + { + "type": "null" + } + ], + "description": "schedule-time specifications specifications for the service (follows Docker Service creation API (specifically only the Resources part), see https://docs.docker.com/engine/api/v1.41/#tag/Service/operation/ServiceCreate" + } + }, + "type": "object", + "title": "ServiceSpecificationsGet" + }, + "ServiceType": { + "type": "string", + "enum": [ + "computational", + "dynamic", + "frontend", + "backend" ], - "title": "ServiceOutput" + "title": "ServiceType" }, - "ServicePortGet": { + "ServiceUpdate": { "properties": { - "key": { - "type": "string", - "pattern": "^[^_\\W0-9]\\w*$", - "title": "Key name", - "description": "port identifier name" + "accessRights": { + "anyOf": [ + { + "additionalProperties": { + "$ref": "#/components/schemas/ServiceGroupAccessRights" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Accessrights", + "description": "service access rights per group id" }, - "kind": { - "type": "string", - "enum": [ - "input", - "output" + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } ], - "title": "Kind" + "title": "Name" }, - "content_media_type": { + "thumbnail": { + "anyOf": [ + { + "type": "string", + "maxLength": 2083, + "minLength": 1, + "format": "uri" + }, + { + "type": "null" + } + ], + "title": "Thumbnail" + }, + "description": { "anyOf": [ { "type": "string" @@ -3403,42 +3419,145 @@ "type": "null" } ], - "title": "Content Media Type" + "title": "Description" }, - "content_schema": { + "description_ui": { + "type": "boolean", + "title": "Description Ui", + "default": false + }, + "version_display": { "anyOf": [ { - "type": "object" + "type": "string" }, { "type": "null" } ], - "title": "Content Schema", - "description": "jsonschema for the port's value. SEE https://json-schema.org/understanding-json-schema/" + "title": "Version Display" + }, + "deprecated": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ], + "title": "Deprecated", + "description": "Owner can set the date to retire the service. Three possibilities:If None, the service is marked as `published`;If now=deprecated, the service is retired" + }, + "classifiers": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Classifiers" + }, + "quality": { + "type": "object", + "title": "Quality", + "default": {} } }, "type": "object", "required": [ - "key", - "kind" + "name", + "thumbnail", + "description", + "classifiers" ], - "title": "ServicePortGet", + "title": "ServiceUpdate", "example": { - "content_schema": { - "maximum": 5, - "minimum": 0, - "title": "Sleep interval", - "type": "integer", - "x_unit": "second" + "accessRights": { + "1": { + "execute_access": false, + "write_access": false + }, + "2": { + "execute_access": true, + "write_access": true + }, + "44": { + "execute_access": false, + "write_access": false + } }, - "key": "input_1", - "kind": "input" + "classifiers": [ + "RRID:SCR_018997", + "RRID:SCR_019001" + ], + "description": "An interesting service that does something", + "name": "My Human Readable Service Name", + "quality": { + "annotations": { + "certificationLink": "", + "certificationStatus": "Uncertified", + "documentation": "", + "limitations": "", + "purpose": "", + "standards": "", + "vandv": "" + }, + "enabled": true, + "tsr": { + "r01": { + "level": 3, + "references": "" + }, + "r02": { + "level": 2, + "references": "" + }, + "r03": { + "level": 0, + "references": "" + }, + "r04": { + "level": 0, + "references": "" + }, + "r05": { + "level": 2, + "references": "" + }, + "r06": { + "level": 0, + "references": "" + }, + "r07": { + "level": 0, + "references": "" + }, + "r08": { + "level": 1, + "references": "" + }, + "r09": { + "level": 0, + "references": "" + }, + "r10": { + "level": 0, + "references": "" + } + } + } } }, - "ServiceSpec": { + "Spread": { "properties": { - "Name": { + "SpreadDescriptor": { "anyOf": [ { "type": "string" @@ -3447,66 +3566,132 @@ "type": "null" } ], - "title": "Name", - "description": "Name of the service." + "title": "Spreaddescriptor", + "description": "label descriptor, such as `engine.labels.az`.\n" + } + }, + "type": "object", + "title": "Spread" + }, + "Structure": { + "properties": { + "key": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean" + }, + { + "type": "number" + } + ], + "title": "Key" }, - "Labels": { + "label": { + "type": "string", + "title": "Label" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "key", + "label" + ], + "title": "Structure" + }, + "TaskSpec": { + "properties": { + "PluginSpec": { "anyOf": [ { - "additionalProperties": { - "type": "string" - }, - "type": "object" + "$ref": "#/components/schemas/PluginSpec" }, { "type": "null" } ], - "title": "Labels", - "description": "User-defined key/value metadata." + "description": "Plugin spec for the service. *(Experimental release only.)*\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`.\n" }, - "TaskTemplate": { + "ContainerSpec": { + "anyOf": [ + { + "$ref": "#/components/schemas/models_library__generated_models__docker_rest_api__ContainerSpec" + }, + { + "type": "null" + } + ], + "description": "Container spec for the service.\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`.\n" + }, + "NetworkAttachmentSpec": { + "anyOf": [ + { + "$ref": "#/components/schemas/NetworkAttachmentSpec" + }, + { + "type": "null" + } + ], + "description": "Read-only spec type for non-swarm containers attached to swarm overlay\nnetworks.\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`.\n" + }, + "Resources": { + "anyOf": [ + { + "$ref": "#/components/schemas/Resources1" + }, + { + "type": "null" + } + ], + "description": "Resource requirements which apply to each individual container created\nas part of the service.\n" + }, + "RestartPolicy": { "anyOf": [ { - "$ref": "#/components/schemas/TaskSpec" + "$ref": "#/components/schemas/RestartPolicy1" }, { "type": "null" } - ] + ], + "description": "Specification for the restart policy which applies to containers\ncreated as part of this service.\n" }, - "Mode": { + "Placement": { "anyOf": [ { - "$ref": "#/components/schemas/Mode" + "$ref": "#/components/schemas/Placement" }, { "type": "null" } - ], - "description": "Scheduling mode for the service." + ] }, - "UpdateConfig": { + "ForceUpdate": { "anyOf": [ { - "$ref": "#/components/schemas/UpdateConfig" + "type": "integer" }, { "type": "null" } ], - "description": "Specification for the update strategy of the service." + "title": "Forceupdate", + "description": "A counter that triggers an update even if no relevant parameters have\nbeen changed.\n" }, - "RollbackConfig": { + "Runtime": { "anyOf": [ { - "$ref": "#/components/schemas/RollbackConfig" + "type": "string" }, { "type": "null" } ], - "description": "Specification for the rollback strategy of the service." + "title": "Runtime", + "description": "Runtime is the type of runtime specified for the task executor.\n" }, "Networks": { "anyOf": [ @@ -3523,373 +3708,422 @@ "title": "Networks", "description": "Specifies which networks the service should attach to." }, - "EndpointSpec": { + "LogDriver": { "anyOf": [ { - "$ref": "#/components/schemas/EndpointSpec" + "$ref": "#/components/schemas/LogDriver1" }, { "type": "null" } - ] + ], + "description": "Specifies the log driver to use for tasks created from this spec. If\nnot present, the default one for the swarm will be used, finally\nfalling back to the engine default if not specified.\n" } }, "type": "object", - "title": "ServiceSpec", - "description": "User modifiable configuration for a service." + "title": "TaskSpec", + "description": "User modifiable task configuration." }, - "ServiceSpecificationsGet": { + "TextArea": { "properties": { - "sidecar": { + "minHeight": { + "type": "integer", + "exclusiveMinimum": true, + "title": "Minheight", + "description": "minimum Height of the textarea", + "minimum": 0 + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "minHeight" + ], + "title": "TextArea" + }, + "TmpfsOptions": { + "properties": { + "SizeBytes": { "anyOf": [ { - "$ref": "#/components/schemas/ServiceSpec" + "type": "integer" }, { "type": "null" } ], - "description": "schedule-time specifications for the service sidecar (follows Docker Service creation API, see https://docs.docker.com/engine/api/v1.25/#operation/ServiceCreate)" + "title": "Sizebytes", + "description": "The size for the tmpfs mount in bytes." }, - "service": { + "Mode": { "anyOf": [ { - "$ref": "#/components/schemas/ServiceSpec" + "type": "integer" }, { "type": "null" } ], - "description": "schedule-time specifications specifications for the service (follows Docker Service creation API (specifically only the Resources part), see https://docs.docker.com/engine/api/v1.41/#tag/Service/operation/ServiceCreate" + "title": "Mode", + "description": "The permission mode for the tmpfs mount in an integer." } }, "type": "object", - "title": "ServiceSpecificationsGet" + "title": "TmpfsOptions", + "description": "Optional configuration for the `tmpfs` type." }, - "ServiceType": { + "Type": { "type": "string", "enum": [ - "computational", - "dynamic", - "frontend", - "backend" + "tcp", + "udp", + "sctp" ], - "title": "ServiceType" + "title": "Type" }, - "ServiceUpdate": { + "Type2": { + "type": "string", + "enum": [ + "bind", + "volume", + "tmpfs", + "npipe" + ], + "title": "Type2", + "description": "The mount type. Available types:\n\n- `bind` Mounts a file or directory from the host into the container. Must exist prior to creating the container.\n- `volume` Creates a volume with the given name and options (or uses a pre-existing volume with the same name and options). These are **not** removed when the container is removed.\n- `tmpfs` Create a tmpfs with the given options. The mount source cannot be specified for tmpfs.\n- `npipe` Mounts a named pipe from the host into the container. Must exist prior to creating the container." + }, + "Ulimit": { "properties": { - "accessRights": { + "Name": { "anyOf": [ { - "additionalProperties": { - "$ref": "#/components/schemas/ServiceGroupAccessRights" - }, - "type": "object" + "type": "string" }, { "type": "null" } ], - "title": "Accessrights", - "description": "service access rights per group id" + "title": "Name", + "description": "Name of ulimit" }, - "name": { + "Soft": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Name" + "title": "Soft", + "description": "Soft limit" }, - "thumbnail": { + "Hard": { "anyOf": [ { - "type": "string", - "maxLength": 2083, - "minLength": 1, - "format": "uri" + "type": "integer" }, { "type": "null" } ], - "title": "Thumbnail" - }, - "description": { + "title": "Hard", + "description": "Hard limit" + } + }, + "type": "object", + "title": "Ulimit" + }, + "UpdateConfig": { + "properties": { + "Parallelism": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Description" - }, - "description_ui": { - "type": "boolean", - "title": "Description Ui", - "default": false + "title": "Parallelism", + "description": "Maximum number of tasks to be updated in one iteration (0 means\nunlimited parallelism).\n" }, - "version_display": { + "Delay": { "anyOf": [ { - "type": "string" + "type": "integer" }, { "type": "null" } ], - "title": "Version Display" + "title": "Delay", + "description": "Amount of time between updates, in nanoseconds." }, - "deprecated": { + "FailureAction": { "anyOf": [ { - "type": "string", - "format": "date-time" + "$ref": "#/components/schemas/FailureAction" }, { "type": "null" } ], - "title": "Deprecated", - "description": "Owner can set the date to retire the service. Three possibilities:If None, the service is marked as `published`;If now=deprecated, the service is retired" + "description": "Action to take if an updated task fails to run, or stops running\nduring the update.\n" }, - "classifiers": { + "Monitor": { "anyOf": [ { - "items": { - "type": "string" - }, - "type": "array" + "type": "integer" }, { "type": "null" } ], - "title": "Classifiers" - }, - "quality": { - "type": "object", - "title": "Quality", - "default": {} - } - }, - "type": "object", - "required": [ - "name", - "thumbnail", - "description", - "classifiers" - ], - "title": "ServiceUpdate", - "example": { - "accessRights": { - "1": { - "execute_access": false, - "write_access": false - }, - "2": { - "execute_access": true, - "write_access": true - }, - "44": { - "execute_access": false, - "write_access": false - } + "title": "Monitor", + "description": "Amount of time to monitor each updated task for failures, in\nnanoseconds.\n" }, - "classifiers": [ - "RRID:SCR_018997", - "RRID:SCR_019001" - ], - "description": "An interesting service that does something", - "name": "My Human Readable Service Name", - "quality": { - "annotations": { - "certificationLink": "", - "certificationStatus": "Uncertified", - "documentation": "", - "limitations": "", - "purpose": "", - "standards": "", - "vandv": "" - }, - "enabled": true, - "tsr": { - "r01": { - "level": 3, - "references": "" - }, - "r02": { - "level": 2, - "references": "" - }, - "r03": { - "level": 0, - "references": "" - }, - "r04": { - "level": 0, - "references": "" - }, - "r05": { - "level": 2, - "references": "" - }, - "r06": { - "level": 0, - "references": "" - }, - "r07": { - "level": 0, - "references": "" - }, - "r08": { - "level": 1, - "references": "" + "MaxFailureRatio": { + "anyOf": [ + { + "type": "number" }, - "r09": { - "level": 0, - "references": "" + { + "type": "null" + } + ], + "title": "Maxfailureratio", + "description": "The fraction of tasks that may fail during an update before the\nfailure action is invoked, specified as a floating point number\nbetween 0 and 1.\n", + "default": 0 + }, + "Order": { + "anyOf": [ + { + "$ref": "#/components/schemas/Order" }, - "r10": { - "level": 0, - "references": "" + { + "type": "null" } - } + ], + "description": "The order of operations when rolling out an updated task. Either\nthe old task is shut down before the new task is started, or the\nnew task is started before the old task is shut down.\n" } - } + }, + "type": "object", + "title": "UpdateConfig", + "description": "Specification for the update strategy of the service." }, - "Spread": { + "ValidationError": { "properties": { - "SpreadDescriptor": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + }, + "VolumeOptions": { + "properties": { + "NoCopy": { "anyOf": [ { - "type": "string" + "type": "boolean" }, { "type": "null" } ], - "title": "Spreaddescriptor", - "description": "label descriptor, such as `engine.labels.az`.\n" + "title": "Nocopy", + "description": "Populate volume with data from the target.", + "default": false + }, + "Labels": { + "anyOf": [ + { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Labels", + "description": "User-defined key/value metadata." + }, + "DriverConfig": { + "anyOf": [ + { + "$ref": "#/components/schemas/DriverConfig" + }, + { + "type": "null" + } + ], + "description": "Map of driver specific options" } }, "type": "object", - "title": "Spread" + "title": "VolumeOptions", + "description": "Optional configuration for the `volume` type." }, - "Structure": { + "Widget": { "properties": { - "key": { + "type": { + "$ref": "#/components/schemas/WidgetType", + "description": "type of the property" + }, + "details": { "anyOf": [ { - "type": "string" - }, - { - "type": "boolean" + "$ref": "#/components/schemas/TextArea" }, { - "type": "number" + "$ref": "#/components/schemas/SelectBox" } ], - "title": "Key" - }, - "label": { - "type": "string", - "title": "Label" + "title": "Details" } }, "additionalProperties": false, "type": "object", "required": [ - "key", - "label" + "type", + "details" ], - "title": "Structure" + "title": "Widget" }, - "TaskSpec": { + "WidgetType": { + "type": "string", + "enum": [ + "TextArea", + "SelectBox" + ], + "title": "WidgetType" + }, + "models_library__generated_models__docker_rest_api__ContainerSpec": { "properties": { - "PluginSpec": { + "Image": { "anyOf": [ { - "$ref": "#/components/schemas/PluginSpec" + "type": "string" }, { "type": "null" } ], - "description": "Plugin spec for the service. *(Experimental release only.)*\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`.\n" + "title": "Image", + "description": "The image name to use for the container" }, - "ContainerSpec": { + "Labels": { "anyOf": [ { - "$ref": "#/components/schemas/ContainerSpec" + "additionalProperties": { + "type": "string" + }, + "type": "object" }, { "type": "null" } ], - "description": "Container spec for the service.\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`.\n" + "title": "Labels", + "description": "User-defined key/value data." }, - "NetworkAttachmentSpec": { + "Command": { "anyOf": [ { - "$ref": "#/components/schemas/NetworkAttachmentSpec" + "items": { + "type": "string" + }, + "type": "array" }, { "type": "null" } ], - "description": "Read-only spec type for non-swarm containers attached to swarm overlay\nnetworks.\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`.\n" + "title": "Command", + "description": "The command to be run in the image." }, - "Resources": { + "Args": { "anyOf": [ { - "$ref": "#/components/schemas/Resources1" + "items": { + "type": "string" + }, + "type": "array" }, { "type": "null" } ], - "description": "Resource requirements which apply to each individual container created\nas part of the service.\n" + "title": "Args", + "description": "Arguments to the command." }, - "RestartPolicy": { + "Hostname": { "anyOf": [ { - "$ref": "#/components/schemas/RestartPolicy1" + "type": "string" }, { "type": "null" } ], - "description": "Specification for the restart policy which applies to containers\ncreated as part of this service.\n" + "title": "Hostname", + "description": "The hostname to use for the container, as a valid\n[RFC 1123](https://tools.ietf.org/html/rfc1123) hostname.\n" }, - "Placement": { + "Env": { "anyOf": [ { - "$ref": "#/components/schemas/Placement" + "items": { + "type": "string" + }, + "type": "array" }, { "type": "null" } - ] + ], + "title": "Env", + "description": "A list of environment variables in the form `VAR=value`.\n" }, - "ForceUpdate": { + "Dir": { "anyOf": [ { - "type": "integer" + "type": "string" }, { "type": "null" } ], - "title": "Forceupdate", - "description": "A counter that triggers an update even if no relevant parameters have\nbeen changed.\n" + "title": "Dir", + "description": "The working directory for commands to run in." }, - "Runtime": { + "User": { "anyOf": [ { "type": "string" @@ -3898,14 +4132,14 @@ "type": "null" } ], - "title": "Runtime", - "description": "Runtime is the type of runtime specified for the task executor.\n" + "title": "User", + "description": "The user inside the container." }, - "Networks": { + "Groups": { "anyOf": [ { "items": { - "$ref": "#/components/schemas/NetworkAttachmentConfig" + "type": "string" }, "type": "array" }, @@ -3913,120 +4147,84 @@ "type": "null" } ], - "title": "Networks", - "description": "Specifies which networks the service should attach to." + "title": "Groups", + "description": "A list of additional groups that the container process will run as.\n" }, - "LogDriver": { + "Privileges": { "anyOf": [ { - "$ref": "#/components/schemas/LogDriver1" + "$ref": "#/components/schemas/Privileges" }, { "type": "null" } ], - "description": "Specifies the log driver to use for tasks created from this spec. If\nnot present, the default one for the swarm will be used, finally\nfalling back to the engine default if not specified.\n" - } - }, - "type": "object", - "title": "TaskSpec", - "description": "User modifiable task configuration." - }, - "TextArea": { - "properties": { - "minHeight": { - "type": "integer", - "exclusiveMinimum": true, - "title": "Minheight", - "description": "minimum Height of the textarea", - "minimum": 0 - } - }, - "additionalProperties": false, - "type": "object", - "required": [ - "minHeight" - ], - "title": "TextArea" - }, - "TmpfsOptions": { - "properties": { - "SizeBytes": { + "description": "Security options for the container" + }, + "TTY": { + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "null" + } + ], + "title": "Tty", + "description": "Whether a pseudo-TTY should be allocated." + }, + "OpenStdin": { "anyOf": [ { - "type": "integer" + "type": "boolean" }, { "type": "null" } ], - "title": "Sizebytes", - "description": "The size for the tmpfs mount in bytes." + "title": "Openstdin", + "description": "Open `stdin`" }, - "Mode": { + "ReadOnly": { "anyOf": [ { - "type": "integer" + "type": "boolean" }, { "type": "null" } ], - "title": "Mode", - "description": "The permission mode for the tmpfs mount in an integer." - } - }, - "type": "object", - "title": "TmpfsOptions", - "description": "Optional configuration for the `tmpfs` type." - }, - "Type": { - "type": "string", - "enum": [ - "tcp", - "udp", - "sctp" - ], - "title": "Type" - }, - "Type2": { - "type": "string", - "enum": [ - "bind", - "volume", - "tmpfs", - "npipe" - ], - "title": "Type2", - "description": "The mount type. Available types:\n\n- `bind` Mounts a file or directory from the host into the container. Must exist prior to creating the container.\n- `volume` Creates a volume with the given name and options (or uses a pre-existing volume with the same name and options). These are **not** removed when the container is removed.\n- `tmpfs` Create a tmpfs with the given options. The mount source cannot be specified for tmpfs.\n- `npipe` Mounts a named pipe from the host into the container. Must exist prior to creating the container." - }, - "Ulimit": { - "properties": { - "Name": { + "title": "Readonly", + "description": "Mount the container's root filesystem as read only." + }, + "Mounts": { "anyOf": [ { - "type": "string" + "items": { + "$ref": "#/components/schemas/Mount" + }, + "type": "array" }, { "type": "null" } ], - "title": "Name", - "description": "Name of ulimit" + "title": "Mounts", + "description": "Specification for mounts to be added to containers created as part\nof the service.\n" }, - "Soft": { + "StopSignal": { "anyOf": [ { - "type": "integer" + "type": "string" }, { "type": "null" } ], - "title": "Soft", - "description": "Soft limit" + "title": "Stopsignal", + "description": "Signal to stop the container." }, - "Hard": { + "StopGracePeriod": { "anyOf": [ { "type": "integer" @@ -4035,127 +4233,87 @@ "type": "null" } ], - "title": "Hard", - "description": "Hard limit" - } - }, - "type": "object", - "title": "Ulimit" - }, - "UpdateConfig": { - "properties": { - "Parallelism": { + "title": "Stopgraceperiod", + "description": "Amount of time to wait for the container to terminate before\nforcefully killing it.\n" + }, + "HealthCheck": { "anyOf": [ { - "type": "integer" + "$ref": "#/components/schemas/HealthConfig" }, { "type": "null" } - ], - "title": "Parallelism", - "description": "Maximum number of tasks to be updated in one iteration (0 means\nunlimited parallelism).\n" + ] }, - "Delay": { + "Hosts": { "anyOf": [ { - "type": "integer" + "items": { + "type": "string" + }, + "type": "array" }, { "type": "null" } ], - "title": "Delay", - "description": "Amount of time between updates, in nanoseconds." + "title": "Hosts", + "description": "A list of hostname/IP mappings to add to the container's `hosts`\nfile. The format of extra hosts is specified in the\n[hosts(5)](http://man7.org/linux/man-pages/man5/hosts.5.html)\nman page:\n\n IP_address canonical_hostname [aliases...]\n" }, - "FailureAction": { + "DNSConfig": { "anyOf": [ { - "$ref": "#/components/schemas/FailureAction" + "$ref": "#/components/schemas/DnsConfig" }, { "type": "null" } ], - "description": "Action to take if an updated task fails to run, or stops running\nduring the update.\n" + "description": "Specification for DNS related configurations in resolver configuration\nfile (`resolv.conf`).\n" }, - "Monitor": { + "Secrets": { "anyOf": [ { - "type": "integer" + "items": { + "$ref": "#/components/schemas/Secret" + }, + "type": "array" }, { "type": "null" } ], - "title": "Monitor", - "description": "Amount of time to monitor each updated task for failures, in\nnanoseconds.\n" + "title": "Secrets", + "description": "Secrets contains references to zero or more secrets that will be\nexposed to the service.\n" }, - "MaxFailureRatio": { + "Configs": { "anyOf": [ { - "type": "number" + "items": { + "$ref": "#/components/schemas/Config1" + }, + "type": "array" }, { "type": "null" } ], - "title": "Maxfailureratio", - "description": "The fraction of tasks that may fail during an update before the\nfailure action is invoked, specified as a floating point number\nbetween 0 and 1.\n", - "default": 0 + "title": "Configs", + "description": "Configs contains references to zero or more configs that will be\nexposed to the service.\n" }, - "Order": { + "Isolation": { "anyOf": [ { - "$ref": "#/components/schemas/Order" + "$ref": "#/components/schemas/Isolation1" }, { "type": "null" } ], - "description": "The order of operations when rolling out an updated task. Either\nthe old task is shut down before the new task is started, or the\nnew task is started before the old task is shut down.\n" - } - }, - "type": "object", - "title": "UpdateConfig", - "description": "Specification for the update strategy of the service." - }, - "ValidationError": { - "properties": { - "loc": { - "items": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "integer" - } - ] - }, - "type": "array", - "title": "Location" - }, - "msg": { - "type": "string", - "title": "Message" + "description": "Isolation technology of the containers running the service.\n(Windows only)\n" }, - "type": { - "type": "string", - "title": "Error Type" - } - }, - "type": "object", - "required": [ - "loc", - "msg", - "type" - ], - "title": "ValidationError" - }, - "VolumeOptions": { - "properties": { - "NoCopy": { + "Init": { "anyOf": [ { "type": "boolean" @@ -4164,11 +4322,10 @@ "type": "null" } ], - "title": "Nocopy", - "description": "Populate volume with data from the target.", - "default": false + "title": "Init", + "description": "Run an init inside the container that forwards signals and reaps\nprocesses. This field is omitted if empty, and the default (as\nconfigured on the daemon) is used.\n" }, - "Labels": { + "Sysctls": { "anyOf": [ { "additionalProperties": { @@ -4180,58 +4337,79 @@ "type": "null" } ], - "title": "Labels", - "description": "User-defined key/value metadata." + "title": "Sysctls", + "description": "Set kernel namedspaced parameters (sysctls) in the container.\nThe Sysctls option on services accepts the same sysctls as the\nare supported on containers. Note that while the same sysctls are\nsupported, no guarantees or checks are made about their\nsuitability for a clustered environment, and it's up to the user\nto determine whether a given sysctl will work properly in a\nService.\n" }, - "DriverConfig": { + "CapabilityAdd": { "anyOf": [ { - "$ref": "#/components/schemas/DriverConfig" + "items": { + "type": "string" + }, + "type": "array" }, { "type": "null" } ], - "description": "Map of driver specific options" - } - }, - "type": "object", - "title": "VolumeOptions", - "description": "Optional configuration for the `volume` type." - }, - "Widget": { - "properties": { - "type": { - "$ref": "#/components/schemas/WidgetType", - "description": "type of the property" + "title": "Capabilityadd", + "description": "A list of kernel capabilities to add to the default set\nfor the container.\n" }, - "details": { + "CapabilityDrop": { "anyOf": [ { - "$ref": "#/components/schemas/TextArea" + "items": { + "type": "string" + }, + "type": "array" }, { - "$ref": "#/components/schemas/SelectBox" + "type": "null" } ], - "title": "Details" + "title": "Capabilitydrop", + "description": "A list of kernel capabilities to drop from the default set\nfor the container.\n" + }, + "Ulimits": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/Ulimit" + }, + "type": "array" + }, + { + "type": "null" + } + ], + "title": "Ulimits", + "description": "A list of resource limits to set in the container. For example: `{\"Name\": \"nofile\", \"Soft\": 1024, \"Hard\": 2048}`\"\n" + } + }, + "type": "object", + "title": "ContainerSpec", + "description": "Container spec for the service.\n\n


\n\n> **Note**: ContainerSpec, NetworkAttachmentSpec, and PluginSpec are\n> mutually exclusive. PluginSpec is only used when the Runtime field\n> is set to `plugin`. NetworkAttachmentSpec is used when the Runtime\n> field is set to `attachment`." + }, + "models_library__service_settings_labels__ContainerSpec": { + "properties": { + "Command": { + "items": { + "type": "string" + }, + "type": "array", + "maxItems": 2, + "minItems": 1, + "title": "Command", + "description": "Used to override the container's command" } }, "additionalProperties": false, "type": "object", "required": [ - "type", - "details" - ], - "title": "Widget" - }, - "WidgetType": { - "type": "string", - "enum": [ - "TextArea", - "SelectBox" + "Command" ], - "title": "WidgetType" + "title": "ContainerSpec", + "description": "Implements entries that can be overriden for https://docs.docker.com/engine/api/v1.41/#operation/ServiceCreate\nrequest body: TaskTemplate -> ContainerSpec" } } } diff --git a/services/catalog/src/simcore_service_catalog/api/rest/_services_extras.py b/services/catalog/src/simcore_service_catalog/api/rest/_services_extras.py new file mode 100644 index 000000000000..8f1658f57bec --- /dev/null +++ b/services/catalog/src/simcore_service_catalog/api/rest/_services_extras.py @@ -0,0 +1,19 @@ +from typing import Annotated + +from fastapi import APIRouter, Depends +from models_library.api_schemas_directorv2.services import ServiceExtras +from models_library.services import ServiceKey, ServiceVersion + +from ...services.director import DirectorApi +from ..dependencies.director import get_director_api + +router = APIRouter() + + +@router.get("/{service_key:path}/{service_version}/extras") +async def get_service_extras( + service_key: ServiceKey, + service_version: ServiceVersion, + director_client: Annotated[DirectorApi, Depends(get_director_api)], +) -> ServiceExtras: + return await director_client.get_service_extras(service_key, service_version) diff --git a/services/catalog/src/simcore_service_catalog/api/rest/routes.py b/services/catalog/src/simcore_service_catalog/api/rest/routes.py index 25f0d8e92e56..91e9329d0202 100644 --- a/services/catalog/src/simcore_service_catalog/api/rest/routes.py +++ b/services/catalog/src/simcore_service_catalog/api/rest/routes.py @@ -7,6 +7,7 @@ _meta, _services, _services_access_rights, + _services_extras, _services_labels, _services_ports, _services_resources, @@ -44,6 +45,11 @@ tags=_SERVICE_TAGS, prefix=_SERVICE_PREFIX, ) +v0_router.include_router( + _services_extras.router, + tags=_SERVICE_TAGS, + prefix=_SERVICE_PREFIX, +) v0_router.include_router( _services_specifications.router, tags=_SERVICE_TAGS, diff --git a/services/catalog/src/simcore_service_catalog/core/application.py b/services/catalog/src/simcore_service_catalog/core/application.py index 6ed95110c39a..a736891c7369 100644 --- a/services/catalog/src/simcore_service_catalog/core/application.py +++ b/services/catalog/src/simcore_service_catalog/core/application.py @@ -50,9 +50,7 @@ def create_app(settings: ApplicationSettings | None = None) -> FastAPI: setup_tracing(app, settings.CATALOG_TRACING, APP_NAME) # STARTUP-EVENT - app.add_event_handler( - "startup", create_on_startup(app, tracing_settings=settings.CATALOG_TRACING) - ) + app.add_event_handler("startup", create_on_startup(app)) # PLUGIN SETUP setup_function_services(app) diff --git a/services/catalog/src/simcore_service_catalog/core/events.py b/services/catalog/src/simcore_service_catalog/core/events.py index dde295a2e565..f22adbba4ece 100644 --- a/services/catalog/src/simcore_service_catalog/core/events.py +++ b/services/catalog/src/simcore_service_catalog/core/events.py @@ -5,7 +5,6 @@ from fastapi import FastAPI from servicelib.fastapi.db_asyncpg_engine import close_db_connection, connect_to_db from servicelib.logging_utils import log_context -from settings_library.tracing import TracingSettings from .._meta import APP_FINISHED_BANNER_MSG, APP_STARTED_BANNER_MSG from ..db.events import setup_default_product @@ -27,9 +26,7 @@ def _flush_finished_banner() -> None: print(APP_FINISHED_BANNER_MSG, flush=True) # noqa: T201 -def create_on_startup( - app: FastAPI, tracing_settings: TracingSettings | None -) -> EventCallable: +def create_on_startup(app: FastAPI) -> EventCallable: async def _() -> None: _flush_started_banner() @@ -40,7 +37,7 @@ async def _() -> None: if app.state.settings.CATALOG_DIRECTOR: # setup connection to director - await setup_director(app, tracing_settings=tracing_settings) + await setup_director(app) # FIXME: check director service is in place and ready. Hand-shake?? # SEE https://github.com/ITISFoundation/osparc-simcore/issues/1728 diff --git a/services/catalog/src/simcore_service_catalog/core/settings.py b/services/catalog/src/simcore_service_catalog/core/settings.py index 9c36ff5ba43e..8f521cf6221c 100644 --- a/services/catalog/src/simcore_service_catalog/core/settings.py +++ b/services/catalog/src/simcore_service_catalog/core/settings.py @@ -7,7 +7,14 @@ ) from models_library.basic_types import LogLevel from models_library.services_resources import ResourcesDict, ResourceValue -from pydantic import AliasChoices, ByteSize, Field, PositiveInt, TypeAdapter +from pydantic import ( + AliasChoices, + ByteSize, + Field, + NonNegativeInt, + PositiveInt, + TypeAdapter, +) from servicelib.logging_utils_filtering import LoggerName, MessageSubstring from settings_library.application import BaseApplicationSettings from settings_library.base import BaseCustomSettings @@ -103,3 +110,6 @@ class ApplicationSettings(BaseApplicationSettings, MixinLoggingSettings): json_schema_extra={"auto_default_from_env": True}, description="settings for opentelemetry tracing", ) + + DIRECTOR_DEFAULT_MAX_MEMORY: NonNegativeInt = Field(default=0) + DIRECTOR_DEFAULT_MAX_NANO_CPUS: NonNegativeInt = Field(default=0) diff --git a/services/catalog/src/simcore_service_catalog/services/access_rights.py b/services/catalog/src/simcore_service_catalog/services/access_rights.py index 0c83baf2a014..e504fe185841 100644 --- a/services/catalog/src/simcore_service_catalog/services/access_rights.py +++ b/services/catalog/src/simcore_service_catalog/services/access_rights.py @@ -6,8 +6,7 @@ import operator from collections.abc import Callable from datetime import UTC, datetime -from typing import Any, cast -from urllib.parse import quote_plus +from typing import cast import arrow from fastapi import FastAPI @@ -36,15 +35,11 @@ async def _is_old_service(app: FastAPI, service: ServiceMetaDataPublished) -> bo # NOTE: https://github.com/ITISFoundation/osparc-simcore/pull/6003#discussion_r1658200909 # get service build date client = get_director_api(app) - data = cast( - dict[str, Any], - await client.get( - f"/service_extras/{quote_plus(service.key)}/{service.version}" - ), - ) - if not data or "build_date" not in data: + + data = await client.get_service_extras(service.key, service.version) + if not data or data.service_build_details is None: return True - service_build_data = arrow.get(data["build_date"]).datetime + service_build_data = arrow.get(data.service_build_details.build_date).datetime return bool(service_build_data < _LEGACY_SERVICES_DATE) diff --git a/services/catalog/src/simcore_service_catalog/services/director.py b/services/catalog/src/simcore_service_catalog/services/director.py index 8897f2c0a399..525840621840 100644 --- a/services/catalog/src/simcore_service_catalog/services/director.py +++ b/services/catalog/src/simcore_service_catalog/services/director.py @@ -1,37 +1,64 @@ import asyncio import functools +import json import logging import urllib.parse from collections.abc import Awaitable, Callable from contextlib import suppress -from typing import Any +from pprint import pformat +from typing import Any, Final import httpx from common_library.json_serialization import json_dumps from fastapi import FastAPI, HTTPException +from models_library.api_schemas_directorv2.services import ServiceExtras from models_library.services_metadata_published import ServiceMetaDataPublished from models_library.services_types import ServiceKey, ServiceVersion +from pydantic import NonNegativeInt, TypeAdapter from servicelib.fastapi.tracing import setup_httpx_client_tracing from servicelib.logging_utils import log_context -from settings_library.tracing import TracingSettings from starlette import status from tenacity.asyncio import AsyncRetrying from tenacity.before_sleep import before_sleep_log from tenacity.stop import stop_after_delay from tenacity.wait import wait_random +from ..core.settings import ApplicationSettings from ..exceptions.errors import DirectorUnresponsiveError _logger = logging.getLogger(__name__) -MINUTE = 60 +_MINUTE: Final[NonNegativeInt] = 60 + + +_SERVICE_RUNTIME_SETTINGS: Final[str] = "simcore.service.settings" +_ORG_LABELS_TO_SCHEMA_LABELS: Final[dict[str, str]] = { + "org.label-schema.build-date": "build_date", + "org.label-schema.vcs-ref": "vcs_ref", + "org.label-schema.vcs-url": "vcs_url", +} + +_CONTAINER_SPEC_ENTRY_NAME = "ContainerSpec".lower() +_RESOURCES_ENTRY_NAME = "Resources".lower() + + +def _validate_kind(entry_to_validate: dict[str, Any], kind_name: str): + for element in ( + entry_to_validate.get("value", {}) + .get("Reservations", {}) + .get("GenericResources", []) + ): + if element.get("DiscreteResourceSpec", {}).get("Kind") == kind_name: + return True + return False + _director_startup_retry_policy: dict[str, Any] = { # Random service startup order in swarm. # wait_random prevents saturating other services while startup # "wait": wait_random(2, 5), - "stop": stop_after_delay(2 * MINUTE), + "stop": stop_after_delay(2 * _MINUTE), "before_sleep": before_sleep_log(_logger, logging.WARNING), "reraise": True, } @@ -108,16 +135,22 @@ class DirectorApi: SEE services/catalog/src/simcore_service_catalog/api/dependencies/director.py """ - def __init__( - self, base_url: str, app: FastAPI, tracing_settings: TracingSettings | None - ): + def __init__(self, base_url: str, app: FastAPI): + settings: ApplicationSettings = app.state.settings + + assert settings.CATALOG_CLIENT_REQUEST # nosec self.client = httpx.AsyncClient( base_url=base_url, - timeout=app.state.settings.CATALOG_CLIENT_REQUEST.HTTP_CLIENT_REQUEST_TOTAL_TIMEOUT, + timeout=settings.CATALOG_CLIENT_REQUEST.HTTP_CLIENT_REQUEST_TOTAL_TIMEOUT, ) - if tracing_settings: + if settings.CATALOG_TRACING: setup_httpx_client_tracing(self.client) - self.vtag = app.state.settings.CATALOG_DIRECTOR.DIRECTOR_VTAG + + assert settings.CATALOG_DIRECTOR # nosec + self.vtag = settings.CATALOG_DIRECTOR.DIRECTOR_VTAG + + self.default_max_memory = settings.DIRECTOR_DEFAULT_MAX_MEMORY + self.default_max_nano_cpus = settings.DIRECTOR_DEFAULT_MAX_NANO_CPUS async def close(self): await self.client.aclose() @@ -167,26 +200,103 @@ async def get_service_labels( assert isinstance(response, dict) # nosec return response + async def get_service_extras( + self, + service_key: ServiceKey, + service_version: ServiceVersion, + ) -> ServiceExtras: + # check physical node requirements + # all nodes require "CPU" + result: dict[str, Any] = { + "node_requirements": { + "CPU": self.default_max_nano_cpus / 1.0e09, + "RAM": self.default_max_memory, + } + } + + labels = await self.get_service_labels(service_key, service_version) + _logger.debug("Compiling service extras from labels %s", pformat(labels)) + + if _SERVICE_RUNTIME_SETTINGS in labels: + service_settings: list[dict[str, Any]] = json.loads( + labels[_SERVICE_RUNTIME_SETTINGS] + ) + for entry in service_settings: + entry_name = entry.get("name", "").lower() + entry_value = entry.get("value") + invalid_with_msg = None + + if entry_name == _RESOURCES_ENTRY_NAME: + if entry_value and isinstance(entry_value, dict): + res_limit = entry_value.get("Limits", {}) + res_reservation = entry_value.get("Reservations", {}) + # CPU + result["node_requirements"]["CPU"] = ( + float(res_limit.get("NanoCPUs", 0)) + or float(res_reservation.get("NanoCPUs", 0)) + or self.default_max_nano_cpus + ) / 1.0e09 + # RAM + result["node_requirements"]["RAM"] = ( + res_limit.get("MemoryBytes", 0) + or res_reservation.get("MemoryBytes", 0) + or self.default_max_memory + ) + else: + invalid_with_msg = f"invalid type for resource [{entry_value}]" + + # discrete resources (custom made ones) --- + # check if the service requires GPU support + if not invalid_with_msg and _validate_kind(entry, "VRAM"): + + result["node_requirements"]["GPU"] = 1 + if not invalid_with_msg and _validate_kind(entry, "MPI"): + result["node_requirements"]["MPI"] = 1 + + elif entry_name == _CONTAINER_SPEC_ENTRY_NAME: + # NOTE: some minor validation + # expects {'name': 'ContainerSpec', 'type': 'ContainerSpec', 'value': {'Command': [...]}} + if ( + entry_value + and isinstance(entry_value, dict) + and "Command" in entry_value + ): + result["container_spec"] = entry_value + else: + invalid_with_msg = f"invalid container_spec [{entry_value}]" + + if invalid_with_msg: + _logger.warning( + "%s entry [%s] encoded in settings labels of service image %s:%s", + invalid_with_msg, + entry, + service_key, + service_version, + ) + + # get org labels + result.update( + { + sl: labels[dl] + for dl, sl in _ORG_LABELS_TO_SCHEMA_LABELS.items() + if dl in labels + } + ) + + _logger.debug("Following service extras were compiled: %s", pformat(result)) + + return TypeAdapter(ServiceExtras).validate_python(result) -async def setup_director( - app: FastAPI, tracing_settings: TracingSettings | None -) -> None: + +async def setup_director(app: FastAPI) -> None: if settings := app.state.settings.CATALOG_DIRECTOR: with log_context( _logger, logging.DEBUG, "Setup director at %s", f"{settings.base_url=}" ): async for attempt in AsyncRetrying(**_director_startup_retry_policy): - client = DirectorApi( - base_url=settings.base_url, - app=app, - tracing_settings=tracing_settings, - ) + client = DirectorApi(base_url=settings.base_url, app=app) with attempt: - client = DirectorApi( - base_url=settings.base_url, - app=app, - tracing_settings=tracing_settings, - ) + client = DirectorApi(base_url=settings.base_url, app=app) if not await client.is_responsive(): with suppress(Exception): await client.close() diff --git a/services/catalog/tests/unit/conftest.py b/services/catalog/tests/unit/conftest.py index 59ac3b929dce..4b0000cbac49 100644 --- a/services/catalog/tests/unit/conftest.py +++ b/services/catalog/tests/unit/conftest.py @@ -21,8 +21,9 @@ from faker import Faker from fastapi import FastAPI, status from fastapi.testclient import TestClient +from models_library.api_schemas_directorv2.services import ServiceExtras from packaging.version import Version -from pydantic import EmailStr +from pydantic import EmailStr, TypeAdapter from pytest_mock import MockerFixture, MockType from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict from pytest_simcore.helpers.typing_env import EnvVarsDict @@ -413,21 +414,29 @@ def _(service_key: str, service_version: str) -> dict: "maintainer": "johnsmith", "org.label-schema.build-date": "2023-04-17T08:04:15Z", "org.label-schema.schema-version": "1.0", - "org.label-schema.vcs-ref": "", - "org.label-schema.vcs-url": "", + "org.label-schema.vcs-ref": "4d79449a2e79f8a3b3b2e1dd0290af9f3d1a8792", + "org.label-schema.vcs-url": "https://github.com/ITISFoundation/jupyter-math.git", "simcore.service.restart-policy": "no-restart", - "simcore.service.settings": '[{"name": "Resources", "type": "Resources", "value": {"Limits": {"NanoCPUs": 4000000000, "MemoryBytes": 2147483648}, "Reservations": {"NanoCPUs": 4000000000, "MemoryBytes": 2147483648}}}]', + "simcore.service.settings": '[{"name": "Resources", "type": "Resources", "value": {"Limits": {"NanoCPUs": 1000000000, "MemoryBytes": 4194304}, "Reservations": {"NanoCPUs": 4000000000, "MemoryBytes": 2147483648}}}]', } return _ +@pytest.fixture +def mock_service_extras() -> ServiceExtras: + return TypeAdapter(ServiceExtras).validate_python( + ServiceExtras.model_json_schema()["examples"][0] + ) + + @pytest.fixture def mocked_director_service_api( mocked_director_service_api_base: respx.MockRouter, director_service_openapi_specs: dict[str, Any], expected_director_list_services: list[dict[str, Any]], get_mocked_service_labels: Callable[[str, str], dict], + mock_service_extras: ServiceExtras, ) -> respx.MockRouter: """ STANDARD fixture to mock director service API @@ -504,34 +513,4 @@ def _get_service_labels(request, service_key, service_version): }, ) - # GET EXTRAS - assert openapi["paths"].get("/service_extras/{service_key}/{service_version}") - - @respx_mock.get( - path__regex=r"^/service_extras/(?P[/\w-]+)/(?P[0-9\.]+)$", - name="get_service_extras", - ) - def _get_service_extras(request, service_key, service_version): - if _search(service_key, service_version): - return httpx.Response( - status.HTTP_200_OK, - json={ - "data": { - "node_requirements": {"CPU": 4, "RAM": 2147483648}, - "build_date": "2023-04-17T08:04:15Z", - "vcs_ref": "", - "vcs_url": "", - } - }, - ) - return httpx.Response( - status.HTTP_404_NOT_FOUND, - json={ - "data": { - "status": status.HTTP_404_NOT_FOUND, - "message": f"The service {service_key}:{service_version} does not exist", - } - }, - ) - return respx_mock diff --git a/services/catalog/tests/unit/test_services_director.py b/services/catalog/tests/unit/test_services_director.py index 7458ad6f7add..eb36988b5198 100644 --- a/services/catalog/tests/unit/test_services_director.py +++ b/services/catalog/tests/unit/test_services_director.py @@ -77,12 +77,6 @@ async def test_director_client_low_level_api( assert service_labels - service_extras = await director_api.get( - f"/service_extras/{urllib.parse.quote_plus(key)}/{version}" - ) - - assert service_extras - service = await director_api.get( f"/services/{urllib.parse.quote_plus(key)}/{version}" ) diff --git a/services/catalog/tests/unit/test_utils_service_extras.py b/services/catalog/tests/unit/test_utils_service_extras.py new file mode 100644 index 000000000000..e5f69ab23bb5 --- /dev/null +++ b/services/catalog/tests/unit/test_utils_service_extras.py @@ -0,0 +1,35 @@ +# pylint: disable=redefined-outer-name +# pylint: disable=unused-argument + +from unittest.mock import AsyncMock + +import pytest +from fastapi import FastAPI, status +from httpx import AsyncClient +from models_library.api_schemas_directorv2.services import ServiceExtras +from pydantic import TypeAdapter +from respx import MockRouter + + +@pytest.fixture +def mock_engine(app: FastAPI) -> None: + app.state.engine = AsyncMock() + + +async def test_get_service_extras( + postgres_setup_disabled: None, + mocked_director_service_api: MockRouter, + rabbitmq_and_rpc_setup_disabled: None, + background_tasks_setup_disabled: None, + mock_engine: None, + mock_service_extras: ServiceExtras, + aclient: AsyncClient, +): + service_key = "simcore/services/comp/ans-model" + service_version = "3.0.0" + result = await aclient.get(f"/v0/services/{service_key}/{service_version}/extras") + assert result.status_code == status.HTTP_200_OK, result.text + + assert ( + TypeAdapter(ServiceExtras).validate_python(result.json()) == mock_service_extras + ) diff --git a/services/catalog/tests/unit/test_utils_service_labels.py b/services/catalog/tests/unit/test_utils_service_labels.py index 950a30cbd891..14bbd06a2860 100644 --- a/services/catalog/tests/unit/test_utils_service_labels.py +++ b/services/catalog/tests/unit/test_utils_service_labels.py @@ -5,7 +5,7 @@ from unittest.mock import AsyncMock import pytest -from fastapi import FastAPI +from fastapi import FastAPI, status from httpx import AsyncClient from respx import MockRouter @@ -27,5 +27,5 @@ async def test_get_service_labels( service_key = "simcore/services/comp/ans-model" service_version = "3.0.0" result = await aclient.get(f"/v0/services/{service_key}/{service_version}/labels") - assert result.status_code == 200, result.text + assert result.status_code == status.HTTP_200_OK, result.text assert result.json() == get_mocked_service_labels(service_key, service_version) diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py index 707d7a8cc1eb..faca265cd8d1 100644 --- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py +++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py @@ -67,7 +67,6 @@ from ...modules.db.repositories.projects import ProjectsRepository from ...modules.db.repositories.projects_metadata import ProjectsMetadataRepository from ...modules.db.repositories.users import UsersRepository -from ...modules.director_v0 import DirectorV0Client from ...modules.resource_usage_tracker_client import ResourceUsageTrackerClient from ...utils import computations as utils from ...utils.dags import ( @@ -81,7 +80,6 @@ ) from ..dependencies.catalog import get_catalog_client from ..dependencies.database import get_repository -from ..dependencies.director_v0 import get_director_v0_client from ..dependencies.rabbitmq import rabbitmq_rpc_client from ..dependencies.rut_client import get_rut_client from .computations_tasks import analyze_pipeline @@ -288,7 +286,6 @@ async def create_computation( # noqa: PLR0913 # pylint: disable=too-many-positi projects_metadata_repo: Annotated[ ProjectsMetadataRepository, Depends(get_repository(ProjectsMetadataRepository)) ], - director_client: Annotated[DirectorV0Client, Depends(get_director_v0_client)], catalog_client: Annotated[CatalogClient, Depends(get_catalog_client)], rut_client: Annotated[ResourceUsageTrackerClient, Depends(get_rut_client)], rpc_client: Annotated[RabbitMQRPCClient, Depends(rabbitmq_rpc_client)], @@ -334,7 +331,6 @@ async def create_computation( # noqa: PLR0913 # pylint: disable=too-many-positi comp_tasks = await comp_tasks_repo.upsert_tasks_from_project( project=project, catalog_client=catalog_client, - director_client=director_client, published_nodes=min_computation_nodes if computation.start_pipeline else [], user_id=computation.user_id, product_name=computation.product_name, diff --git a/services/director-v2/src/simcore_service_director_v2/modules/catalog.py b/services/director-v2/src/simcore_service_director_v2/modules/catalog.py index eb9eead1e9e1..ab94d681a113 100644 --- a/services/director-v2/src/simcore_service_director_v2/modules/catalog.py +++ b/services/director-v2/src/simcore_service_director_v2/modules/catalog.py @@ -5,6 +5,7 @@ import httpx from fastapi import FastAPI, HTTPException, status +from models_library.api_schemas_directorv2.services import ServiceExtras from models_library.service_settings_labels import SimcoreServiceLabels from models_library.services import ServiceKey, ServiceVersion from models_library.services_resources import ServiceResourcesDict @@ -120,6 +121,17 @@ async def get_service_labels( return SimcoreServiceLabels.model_validate(resp.json()) raise HTTPException(status_code=resp.status_code, detail=resp.content) + async def get_service_extras( + self, service_key: ServiceKey, service_version: ServiceVersion + ) -> ServiceExtras: + resp = await self.request( + "GET", + f"/services/{urllib.parse.quote_plus(service_key)}/{service_version}/extras", + ) + if resp.status_code == status.HTTP_200_OK: + return ServiceExtras.model_validate(resp.json()) + raise HTTPException(status_code=resp.status_code, detail=resp.content) + async def get_service_specifications( self, user_id: UserID, service_key: ServiceKey, service_version: ServiceVersion ) -> dict[str, Any]: diff --git a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks/_core.py b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks/_core.py index aa72e996d7fc..5068e144944a 100644 --- a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks/_core.py +++ b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks/_core.py @@ -24,7 +24,6 @@ from .....utils.computations import to_node_class from .....utils.db import RUNNING_STATE_TO_DB from ....catalog import CatalogClient -from ....director_v0 import DirectorV0Client from ...tables import NodeClass, StateType, comp_tasks from .._base import BaseRepository from . import _utils @@ -91,7 +90,6 @@ async def upsert_tasks_from_project( *, project: ProjectAtDB, catalog_client: CatalogClient, - director_client: DirectorV0Client, published_nodes: list[NodeID], user_id: UserID, product_name: str, @@ -106,7 +104,6 @@ async def upsert_tasks_from_project( ] = await _utils.generate_tasks_list_from_project( project=project, catalog_client=catalog_client, - director_client=director_client, published_nodes=published_nodes, user_id=user_id, product_name=product_name, diff --git a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks/_utils.py b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks/_utils.py index 1a59ecd0e184..b9568c0d15d3 100644 --- a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks/_utils.py +++ b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks/_utils.py @@ -59,7 +59,6 @@ from .....utils.computations import to_node_class from ....catalog import CatalogClient from ....comp_scheduler._utils import COMPLETED_STATES -from ....director_v0 import DirectorV0Client from ...tables import NodeClass _logger = logging.getLogger(__name__) @@ -131,7 +130,6 @@ def _compute_node_envs(node_labels: SimcoreServiceLabels) -> ContainerEnvsDict: async def _get_node_infos( catalog_client: CatalogClient, - director_client: DirectorV0Client, user_id: UserID, product_name: str, node: ServiceKeyVersion, @@ -149,7 +147,7 @@ async def _get_node_infos( ServiceMetaDataPublished, ServiceExtras, SimcoreServiceLabels ] = await asyncio.gather( _get_service_details(catalog_client, user_id, product_name, node), - director_client.get_service_extras(node.key, node.version), + catalog_client.get_service_extras(node.key, node.version), catalog_client.get_service_labels(node.key, node.version), ) return result @@ -334,7 +332,6 @@ async def generate_tasks_list_from_project( *, project: ProjectAtDB, catalog_client: CatalogClient, - director_client: DirectorV0Client, published_nodes: list[NodeID], user_id: UserID, product_name: str, @@ -355,7 +352,6 @@ async def generate_tasks_list_from_project( key_version_to_node_infos = { key_version: await _get_node_infos( catalog_client, - director_client, user_id, product_name, key_version, diff --git a/services/director-v2/src/simcore_service_director_v2/modules/director_v0.py b/services/director-v2/src/simcore_service_director_v2/modules/director_v0.py index 8e1dc3f08c2c..8b8d10468125 100644 --- a/services/director-v2/src/simcore_service_director_v2/modules/director_v0.py +++ b/services/director-v2/src/simcore_service_director_v2/modules/director_v0.py @@ -1,7 +1,6 @@ """Module that takes care of communications with director v0 service""" import logging -import urllib.parse from dataclasses import dataclass from typing import Any, cast @@ -11,10 +10,8 @@ from models_library.api_schemas_directorv2.dynamic_services_service import ( RunningDynamicServiceDetails, ) -from models_library.api_schemas_directorv2.services import ServiceExtras from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID -from models_library.services import ServiceKey, ServiceVersion from models_library.users import UserID from servicelib.fastapi.tracing import setup_httpx_client_tracing from servicelib.logging_utils import log_decorator @@ -81,18 +78,6 @@ def instance(cls, app: FastAPI) -> "DirectorV0Client": async def _request(self, method: str, tail_path: str, **kwargs) -> httpx.Response: return await self.client.request(method, tail_path, **kwargs) - @log_decorator(logger=logger) - async def get_service_extras( - self, service_key: ServiceKey, service_version: ServiceVersion - ) -> ServiceExtras: - resp = await self._request( - "GET", - f"/service_extras/{urllib.parse.quote_plus(service_key)}/{service_version}", - ) - if resp.status_code == status.HTTP_200_OK: - return ServiceExtras.model_validate(unenvelope_or_raise_error(resp)) - raise HTTPException(status_code=resp.status_code, detail=resp.content) - @log_decorator(logger=logger) async def get_running_service_details( self, service_uuid: NodeID diff --git a/services/director-v2/tests/integration/02/test_dynamic_sidecar_nodeports_integration.py b/services/director-v2/tests/integration/02/test_dynamic_sidecar_nodeports_integration.py index b1c99b772b9c..8c587a2a08e3 100644 --- a/services/director-v2/tests/integration/02/test_dynamic_sidecar_nodeports_integration.py +++ b/services/director-v2/tests/integration/02/test_dynamic_sidecar_nodeports_integration.py @@ -68,7 +68,6 @@ from simcore_postgres_database.models.comp_pipeline import comp_pipeline from simcore_postgres_database.models.comp_tasks import comp_tasks from simcore_postgres_database.models.projects_networks import projects_networks -from simcore_postgres_database.models.services import services_access_rights from simcore_sdk import node_ports_v2 from simcore_sdk.node_data import data_manager from simcore_sdk.node_ports_common.file_io_utils import LogRedirectCB @@ -173,22 +172,6 @@ async def minimal_configuration( # pylint: disable=no-value-for-parameter conn.execute(comp_tasks.delete()) conn.execute(comp_pipeline.delete()) - # NOTE: ensure access to services to everyone [catalog access needed] - for service in ( - dy_static_file_server_dynamic_sidecar_service, - dy_static_file_server_dynamic_sidecar_compose_spec_service, - ): - service_image = service["image"] - conn.execute( - services_access_rights.insert().values( - key=service_image["name"], - version=service_image["tag"], - gid=1, - execute_access=1, - write_access=0, - product_name=osparc_product_name, - ) - ) yield diff --git a/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_api_route_computations.py b/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_api_route_computations.py index 2178ce7bfcb1..028b33ad4841 100644 --- a/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_api_route_computations.py +++ b/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_api_route_computations.py @@ -134,7 +134,6 @@ def fake_service_labels() -> dict[str, Any]: def mocked_director_service_fcts( minimal_app: FastAPI, fake_service_details: ServiceMetaDataPublished, - fake_service_extras: ServiceExtras, ) -> Iterator[respx.MockRouter]: # pylint: disable=not-context-manager with respx.mock( @@ -151,15 +150,6 @@ def mocked_director_service_fcts( json={"data": [fake_service_details.model_dump(mode="json", by_alias=True)]} ) - respx_mock.get( - re.compile( - r"/service_extras/(simcore)%2F(services)%2F(comp|dynamic|frontend)%2F.+/(.+)" - ), - name="get_service_extras", - ).respond( - json={"data": fake_service_extras.model_dump(mode="json", by_alias=True)} - ) - yield respx_mock @@ -169,6 +159,7 @@ def mocked_catalog_service_fcts( fake_service_details: ServiceMetaDataPublished, fake_service_resources: ServiceResourcesDict, fake_service_labels: dict[str, Any], + fake_service_extras: ServiceExtras, ) -> Iterator[respx.MockRouter]: def _mocked_service_resources(request) -> httpx.Response: return httpx.Response( @@ -223,6 +214,12 @@ def _mocked_services_details( ), name="get_service_labels", ).respond(json=fake_service_labels) + respx_mock.get( + re.compile( + r"/services/simcore%2Fservices%2F(comp|dynamic|frontend)%2F[^/]+/\d+.\d+.\d+/extras" + ), + name="get_service_extras", + ).respond(json=fake_service_extras.model_dump(mode="json", by_alias=True)) respx_mock.get( re.compile( r"services/(?Psimcore%2Fservices%2F(comp|dynamic|frontend)%2F[^/]+)/(?P[^\.]+.[^\.]+.[^/\?]+).*" diff --git a/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_worker.py b/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_worker.py index 608204a3c73c..860d397dcc19 100644 --- a/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_worker.py +++ b/services/director-v2/tests/unit/with_dbs/comp_scheduler/test_worker.py @@ -12,12 +12,12 @@ from unittest import mock import pytest -from tenacity import retry, stop_after_delay, wait_fixed from _helpers import PublishedProject from fastapi import FastAPI from pytest_mock import MockerFixture from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict from pytest_simcore.helpers.typing_env import EnvVarsDict +from settings_library.rabbit import RabbitSettings from simcore_service_director_v2.models.comp_runs import RunMetadataDict from simcore_service_director_v2.modules.comp_scheduler._manager import run_new_pipeline from simcore_service_director_v2.modules.comp_scheduler._models import ( @@ -26,7 +26,7 @@ from simcore_service_director_v2.modules.comp_scheduler._worker import ( _get_scheduler_worker, ) -from settings_library.rabbit import RabbitSettings +from tenacity import retry, stop_after_delay, wait_fixed pytest_simcore_core_services_selection = ["postgres", "rabbit", "redis"] pytest_simcore_ops_services_selection = ["adminer"] @@ -95,7 +95,6 @@ def with_scheduling_concurrency( ) -@pytest.mark.testit @pytest.mark.parametrize("scheduling_concurrency", [1, 50, 100]) @pytest.mark.parametrize( "queue_name", [SchedulePipelineRabbitMessage.get_channel_name()] diff --git a/services/director/src/simcore_service_director/api/rest/_service_extras.py b/services/director/src/simcore_service_director/api/rest/_service_extras.py deleted file mode 100644 index ab61e8ac1ad2..000000000000 --- a/services/director/src/simcore_service_director/api/rest/_service_extras.py +++ /dev/null @@ -1,40 +0,0 @@ -import logging -from typing import Annotated, Any - -from fastapi import APIRouter, Depends, FastAPI, HTTPException, status -from models_library.generics import Envelope -from models_library.services_types import ServiceKey, ServiceVersion -from servicelib.fastapi.dependencies import get_app - -from ... import registry_proxy -from ...core.errors import RegistryConnectionError, ServiceNotAvailableError - -router = APIRouter() - -_logger = logging.getLogger(__name__) - - -@router.get("/service_extras/{service_key:path}/{service_version}") -async def list_service_extras( - the_app: Annotated[FastAPI, Depends(get_app)], - service_key: ServiceKey, - service_version: ServiceVersion, -) -> Envelope[dict[str, Any]]: - _logger.debug( - "Client does service_extras_by_key_version_get request with service_key %s, service_version %s", - service_key, - service_version, - ) - try: - service_extras = await registry_proxy.get_service_extras( - the_app, service_key, service_version - ) - return Envelope[dict[str, Any]](data=service_extras) - except ServiceNotAvailableError as err: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail=f"{err}" - ) from err - except RegistryConnectionError as err: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, detail=f"{err}" - ) from err diff --git a/services/director/src/simcore_service_director/api/rest/routes.py b/services/director/src/simcore_service_director/api/rest/routes.py index 3d789ba02efe..907b3d2bc749 100644 --- a/services/director/src/simcore_service_director/api/rest/routes.py +++ b/services/director/src/simcore_service_director/api/rest/routes.py @@ -6,7 +6,7 @@ http_exception_as_json_response, ) -from . import _health, _running_interactive_services, _service_extras, _services +from . import _health, _running_interactive_services, _services _V0_VTAG: Final[str] = "v0" @@ -22,7 +22,6 @@ def setup_api_routes(app: FastAPI): # include the rest under /vX api_router = APIRouter(prefix=f"/{_V0_VTAG}") api_router.include_router(_services.router, tags=["services"]) - api_router.include_router(_service_extras.router, tags=["services"]) api_router.include_router(_running_interactive_services.router, tags=["services"]) app.include_router(api_router) diff --git a/services/director/src/simcore_service_director/constants.py b/services/director/src/simcore_service_director/constants.py index bb11b71cec92..3c94c501d614 100644 --- a/services/director/src/simcore_service_director/constants.py +++ b/services/director/src/simcore_service_director/constants.py @@ -4,12 +4,6 @@ SERVICE_REVERSE_PROXY_SETTINGS: Final[str] = "simcore.service.reverse-proxy-settings" SERVICE_RUNTIME_BOOTSETTINGS: Final[str] = "simcore.service.bootsettings" -ORG_LABELS_TO_SCHEMA_LABELS: Final[dict[str, str]] = { - "org.label-schema.build-date": "build_date", - "org.label-schema.vcs-ref": "vcs_ref", - "org.label-schema.vcs-url": "vcs_url", -} - CPU_RESOURCE_LIMIT_KEY: Final[str] = "SIMCORE_NANO_CPUS_LIMIT" MEM_RESOURCE_LIMIT_KEY: Final[str] = "SIMCORE_MEMORY_BYTES_LIMIT" diff --git a/services/director/src/simcore_service_director/registry_proxy.py b/services/director/src/simcore_service_director/registry_proxy.py index b0df79d6c4d7..619e68a0f449 100644 --- a/services/director/src/simcore_service_director/registry_proxy.py +++ b/services/director/src/simcore_service_director/registry_proxy.py @@ -4,7 +4,6 @@ import logging import re from collections.abc import AsyncGenerator, Mapping -from pprint import pformat from typing import Any, Final, cast import httpx @@ -22,11 +21,7 @@ from yarl import URL from .client_session import get_client_session -from .constants import ( - DIRECTOR_SIMCORE_SERVICES_PREFIX, - ORG_LABELS_TO_SCHEMA_LABELS, - SERVICE_RUNTIME_SETTINGS, -) +from .constants import DIRECTOR_SIMCORE_SERVICES_PREFIX from .core.errors import ( DirectorRuntimeError, RegistryConnectionError, @@ -531,104 +526,3 @@ def get_service_last_names(image_key: str) -> str: "retrieved service last name from repo %s : %s", image_key, service_last_name ) return service_last_name - - -CONTAINER_SPEC_ENTRY_NAME = "ContainerSpec".lower() -RESOURCES_ENTRY_NAME = "Resources".lower() - - -def _validate_kind(entry_to_validate: dict[str, Any], kind_name: str): - for element in ( - entry_to_validate.get("value", {}) - .get("Reservations", {}) - .get("GenericResources", []) - ): - if element.get("DiscreteResourceSpec", {}).get("Kind") == kind_name: - return True - return False - - -async def get_service_extras( - app: FastAPI, image_key: str, image_tag: str -) -> dict[str, Any]: - # check physical node requirements - # all nodes require "CPU" - app_settings = get_application_settings(app) - result: dict[str, Any] = { - "node_requirements": { - "CPU": app_settings.DIRECTOR_DEFAULT_MAX_NANO_CPUS / 1.0e09, - "RAM": app_settings.DIRECTOR_DEFAULT_MAX_MEMORY, - } - } - - labels, _ = await get_image_labels(app, image_key, image_tag) - _logger.debug("Compiling service extras from labels %s", pformat(labels)) - - if SERVICE_RUNTIME_SETTINGS in labels: - service_settings: list[dict[str, Any]] = json.loads( - labels[SERVICE_RUNTIME_SETTINGS] - ) - for entry in service_settings: - entry_name = entry.get("name", "").lower() - entry_value = entry.get("value") - invalid_with_msg = None - - if entry_name == RESOURCES_ENTRY_NAME: - if entry_value and isinstance(entry_value, dict): - res_limit = entry_value.get("Limits", {}) - res_reservation = entry_value.get("Reservations", {}) - # CPU - result["node_requirements"]["CPU"] = ( - float(res_limit.get("NanoCPUs", 0)) - or float(res_reservation.get("NanoCPUs", 0)) - or app_settings.DIRECTOR_DEFAULT_MAX_NANO_CPUS - ) / 1.0e09 - # RAM - result["node_requirements"]["RAM"] = ( - res_limit.get("MemoryBytes", 0) - or res_reservation.get("MemoryBytes", 0) - or app_settings.DIRECTOR_DEFAULT_MAX_MEMORY - ) - else: - invalid_with_msg = f"invalid type for resource [{entry_value}]" - - # discrete resources (custom made ones) --- - # check if the service requires GPU support - if not invalid_with_msg and _validate_kind(entry, "VRAM"): - result["node_requirements"]["GPU"] = 1 - if not invalid_with_msg and _validate_kind(entry, "MPI"): - result["node_requirements"]["MPI"] = 1 - - elif entry_name == CONTAINER_SPEC_ENTRY_NAME: - # NOTE: some minor validation - # expects {'name': 'ContainerSpec', 'type': 'ContainerSpec', 'value': {'Command': [...]}} - if ( - entry_value - and isinstance(entry_value, dict) - and "Command" in entry_value - ): - result["container_spec"] = entry_value - else: - invalid_with_msg = f"invalid container_spec [{entry_value}]" - - if invalid_with_msg: - _logger.warning( - "%s entry [%s] encoded in settings labels of service image %s:%s", - invalid_with_msg, - entry, - image_key, - image_tag, - ) - - # get org labels - result.update( - { - sl: labels[dl] - for dl, sl in ORG_LABELS_TO_SCHEMA_LABELS.items() - if dl in labels - } - ) - - _logger.debug("Following service extras were compiled: %s", pformat(result)) - - return result diff --git a/services/director/tests/unit/api/test_rest_service_extras.py b/services/director/tests/unit/api/test_rest_service_extras.py deleted file mode 100644 index 8b8bba037c34..000000000000 --- a/services/director/tests/unit/api/test_rest_service_extras.py +++ /dev/null @@ -1,64 +0,0 @@ -# pylint: disable=redefined-outer-name -# pylint: disable=unused-argument -# pylint: disable=unused-variable -# pylint: disable=too-many-arguments - -from urllib.parse import quote - -import httpx -from fastapi import status -from fixtures.fake_services import ServiceInRegistryInfoDict -from pytest_simcore.helpers.typing_env import EnvVarsDict - - -def _assert_response_and_unwrap_envelope(got: httpx.Response): - assert got.headers["content-type"] == "application/json" - assert got.encoding == "utf-8" - - body = got.json() - assert isinstance(body, dict) - assert "data" in body or "error" in body - return body.get("data"), body.get("error") - - -async def test_get_services_extras_by_key_and_version_with_empty_registry( - configure_registry_access: EnvVarsDict, - client: httpx.AsyncClient, - api_version_prefix: str, -): - resp = await client.get( - f"/{api_version_prefix}/service_extras/whatever/someversion" - ) - assert resp.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY, f"Got f{resp.text}" - resp = await client.get( - f"/{api_version_prefix}/service_extras/simcore/services/dynamic/something/someversion" - ) - assert resp.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY, f"Got f{resp.text}" - resp = await client.get( - f"/{api_version_prefix}/service_extras/simcore/services/dynamic/something/1.5.2" - ) - assert resp.status_code == status.HTTP_404_NOT_FOUND, f"Got f{resp.text}" - - -async def test_get_services_extras_by_key_and_version( - configure_registry_access: EnvVarsDict, - client: httpx.AsyncClient, - created_services: list[ServiceInRegistryInfoDict], - api_version_prefix: str, -): - assert len(created_services) == 5 - - for created_service in created_services: - service_description = created_service["service_description"] - # note that it is very important to remove the safe="/" from quote!!!! - key, version = ( - quote(service_description[key], safe="") for key in ("key", "version") - ) - url = f"/{api_version_prefix}/service_extras/{key}/{version}" - resp = await client.get(url) - - assert resp.status_code == status.HTTP_200_OK, f"Got {resp.text=}" - - service_extras, error = _assert_response_and_unwrap_envelope(resp) - assert not error - assert created_service["service_extras"] == service_extras diff --git a/services/director/tests/unit/test_registry_proxy.py b/services/director/tests/unit/test_registry_proxy.py index 538e4220f0d4..c15ccd7df7f2 100644 --- a/services/director/tests/unit/test_registry_proxy.py +++ b/services/director/tests/unit/test_registry_proxy.py @@ -299,23 +299,3 @@ def run_async_test() -> None: asyncio.get_event_loop().run_until_complete(_list_services()) benchmark.pedantic(run_async_test, rounds=5) - - -async def test_generate_service_extras( - configure_registry_access: EnvVarsDict, - app: FastAPI, - push_services, -): - images = await push_services( - number_of_computational_services=1, number_of_interactive_services=1 - ) - - for image in images: - service_description = image["service_description"] - service_extras = image["service_extras"] - - extras = await registry_proxy.get_service_extras( - app, service_description["key"], service_description["version"] - ) - - assert extras == service_extras diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 0f589739ad38..1fae14e81d34 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -156,6 +156,8 @@ services: CATALOG_PROFILING: ${CATALOG_PROFILING} CATALOG_SERVICES_DEFAULT_RESOURCES: ${CATALOG_SERVICES_DEFAULT_RESOURCES} CATALOG_SERVICES_DEFAULT_SPECIFICATIONS: ${CATALOG_SERVICES_DEFAULT_SPECIFICATIONS} + DIRECTOR_DEFAULT_MAX_MEMORY: ${DIRECTOR_DEFAULT_MAX_MEMORY} + DIRECTOR_DEFAULT_MAX_NANO_CPUS: ${DIRECTOR_DEFAULT_MAX_NANO_CPUS} DIRECTOR_HOST: ${DIRECTOR_HOST:-director} DIRECTOR_PORT: ${DIRECTOR_PORT:-8080} LOG_FORMAT_LOCAL_DEV_ENABLED: ${LOG_FORMAT_LOCAL_DEV_ENABLED}