WIP: 103268-job-task-progress #104185
@ -1,8 +0,0 @@
|
||||
{
|
||||
"project_id": "Flamenco",
|
||||
"conduit_uri": "https://developer.blender.org/",
|
||||
"phabricator.uri": "https://developer.blender.org/",
|
||||
"git.default-relative-commit": "origin/main",
|
||||
"arc.land.update.default": "rebase",
|
||||
"arc.land.onto.default": "main"
|
||||
}
|
@ -4,6 +4,13 @@ This file contains the history of changes to Flamenco. Only changes that might
|
||||
be interesting for users are listed here, such as new features and fixes for
|
||||
bugs in actually-released versions.
|
||||
|
||||
## 3.3 - in development
|
||||
|
||||
- Improve speed of queueing up >100 simultaneous job deletions.
|
||||
- Improve logging of job deletion.
|
||||
- Add Worker Cluster support. Workers can be members of any number of clusters. Workers will only work on jobs that are assigned to that cluster. Jobs that do not have a cluster will be available to all workers, regardless of their cluster assignment. As a result, clusterless workers will only work on clusterless jobs.
|
||||
|
||||
|
||||
## 3.2 - released 2023-02-21
|
||||
|
||||
- When rendering EXR files, use Blender's preview JPEG files to generate the preview video ([43bc22f10fae](https://developer.blender.org/rF43bc22f10fae0fcaed6a4a3b3ace1be617193e21)).
|
||||
|
8
Makefile
8
Makefile
@ -2,13 +2,13 @@ PKG := git.blender.org/flamenco
|
||||
|
||||
# To update the version number in all the relevant places, update the VERSION
|
||||
# variable below and run `make update-version`.
|
||||
VERSION := 3.2
|
||||
RELEASE_CYCLE := release
|
||||
VERSION := 3.3-alpha0
|
||||
RELEASE_CYCLE := alpha
|
||||
|
||||
# _GIT_DESCRIPTION_OR_TAG is either something like '16-123abc' (when we're 16
|
||||
# commits since the last tag) or it's something like `v3.0-beta2` (when exactly
|
||||
# on a tagged version).
|
||||
_GIT_DESCRIPTION_OR_TAG := $(subst v${VERSION}-,,$(shell git describe --dirty --always))
|
||||
_GIT_DESCRIPTION_OR_TAG := $(subst v${VERSION}-,,$(shell git describe --tag --dirty --always))
|
||||
# In the above cases, GITHASH is either `16-123abc` (in the same case above) or
|
||||
# `123abc` (when the tag matches the current commit exactly) or `dirty` (when
|
||||
# the tag matches the current commit exactly, and there are subsequent
|
||||
@ -17,7 +17,7 @@ _GIT_DESCRIPTION_OR_TAG := $(subst v${VERSION}-,,$(shell git describe --dirty --
|
||||
# ${GITHASH}.
|
||||
GITHASH := $(subst v${VERSION},$(shell git rev-parse --short HEAD),${_GIT_DESCRIPTION_OR_TAG})
|
||||
|
||||
LDFLAGS := -X ${PKG}/internal/appinfo.ApplicationVersion=${VERSION} \
|
||||
LDFLAGS := ${LDFLAGS} -X ${PKG}/internal/appinfo.ApplicationVersion=${VERSION} \
|
||||
-X ${PKG}/internal/appinfo.ApplicationGitHash=${GITHASH} \
|
||||
-X ${PKG}/internal/appinfo.ReleaseCycle=${RELEASE_CYCLE}
|
||||
BUILD_FLAGS = -ldflags="${LDFLAGS}"
|
||||
|
@ -5,21 +5,21 @@
|
||||
bl_info = {
|
||||
"name": "Flamenco 3",
|
||||
"author": "Sybren A. Stüvel",
|
||||
"version": (3, 2),
|
||||
"version": (3, 3),
|
||||
"blender": (3, 1, 0),
|
||||
"description": "Flamenco client for Blender.",
|
||||
"location": "Output Properties > Flamenco",
|
||||
"doc_url": "https://flamenco.blender.org/",
|
||||
"category": "System",
|
||||
"support": "COMMUNITY",
|
||||
"warning": "",
|
||||
"warning": "This is version 3.3-alpha0 of the add-on, which is not a stable release",
|
||||
}
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
__is_first_load = "operators" not in locals()
|
||||
if __is_first_load:
|
||||
from . import operators, gui, job_types, comms, preferences
|
||||
from . import operators, gui, job_types, comms, preferences, worker_clusters
|
||||
else:
|
||||
import importlib
|
||||
|
||||
@ -28,6 +28,7 @@ else:
|
||||
job_types = importlib.reload(job_types)
|
||||
comms = importlib.reload(comms)
|
||||
preferences = importlib.reload(preferences)
|
||||
worker_clusters = importlib.reload(worker_clusters)
|
||||
|
||||
import bpy
|
||||
|
||||
@ -145,6 +146,7 @@ def register() -> None:
|
||||
)
|
||||
|
||||
preferences.register()
|
||||
worker_clusters.register()
|
||||
operators.register()
|
||||
gui.register()
|
||||
job_types.register()
|
||||
@ -162,4 +164,5 @@ def unregister() -> None:
|
||||
job_types.unregister()
|
||||
gui.unregister()
|
||||
operators.unregister()
|
||||
worker_clusters.unregister()
|
||||
preferences.unregister()
|
||||
|
@ -43,6 +43,11 @@ class FLAMENCO_PT_job_submission(bpy.types.Panel):
|
||||
col.prop(context.scene, "flamenco_job_name", text="Job Name")
|
||||
col.prop(context.scene, "flamenco_job_priority", text="Priority")
|
||||
|
||||
# Worker cluster:
|
||||
row = col.row(align=True)
|
||||
row.prop(context.scene, "flamenco_worker_cluster", text="Cluster")
|
||||
row.operator("flamenco.fetch_worker_clusters", text="", icon="FILE_REFRESH")
|
||||
|
||||
layout.separator()
|
||||
|
||||
col = layout.column()
|
||||
|
@ -53,6 +53,11 @@ def job_for_scene(scene: bpy.types.Scene) -> Optional[_SubmittedJob]:
|
||||
submitter_platform=platform.system().lower(),
|
||||
type_etag=propgroup.job_type.etag,
|
||||
)
|
||||
|
||||
worker_cluster: str = getattr(scene, "flamenco_worker_cluster", "")
|
||||
if worker_cluster and worker_cluster != "-":
|
||||
job.worker_cluster = worker_cluster
|
||||
|
||||
return job
|
||||
|
||||
|
||||
|
2
addon/flamenco/manager/__init__.py
generated
2
addon/flamenco/manager/__init__.py
generated
@ -10,7 +10,7 @@
|
||||
"""
|
||||
|
||||
|
||||
__version__ = "3.2"
|
||||
__version__ = "3.3-alpha0"
|
||||
|
||||
# import ApiClient
|
||||
from flamenco.manager.api_client import ApiClient
|
||||
|
770
addon/flamenco/manager/api/worker_mgt_api.py
generated
770
addon/flamenco/manager/api/worker_mgt_api.py
generated
@ -23,6 +23,9 @@ from flamenco.manager.model_utils import ( # noqa: F401
|
||||
)
|
||||
from flamenco.manager.model.error import Error
|
||||
from flamenco.manager.model.worker import Worker
|
||||
from flamenco.manager.model.worker_cluster import WorkerCluster
|
||||
from flamenco.manager.model.worker_cluster_change_request import WorkerClusterChangeRequest
|
||||
from flamenco.manager.model.worker_cluster_list import WorkerClusterList
|
||||
from flamenco.manager.model.worker_list import WorkerList
|
||||
from flamenco.manager.model.worker_sleep_schedule import WorkerSleepSchedule
|
||||
from flamenco.manager.model.worker_status_change_request import WorkerStatusChangeRequest
|
||||
@ -39,6 +42,56 @@ class WorkerMgtApi(object):
|
||||
if api_client is None:
|
||||
api_client = ApiClient()
|
||||
self.api_client = api_client
|
||||
self.create_worker_cluster_endpoint = _Endpoint(
|
||||
settings={
|
||||
'response_type': (WorkerCluster,),
|
||||
'auth': [],
|
||||
'endpoint_path': '/api/v3/worker-mgt/clusters',
|
||||
'operation_id': 'create_worker_cluster',
|
||||
'http_method': 'POST',
|
||||
'servers': None,
|
||||
},
|
||||
params_map={
|
||||
'all': [
|
||||
'worker_cluster',
|
||||
],
|
||||
'required': [
|
||||
'worker_cluster',
|
||||
],
|
||||
'nullable': [
|
||||
],
|
||||
'enum': [
|
||||
],
|
||||
'validation': [
|
||||
]
|
||||
},
|
||||
root_map={
|
||||
'validations': {
|
||||
},
|
||||
'allowed_values': {
|
||||
},
|
||||
'openapi_types': {
|
||||
'worker_cluster':
|
||||
(WorkerCluster,),
|
||||
},
|
||||
'attribute_map': {
|
||||
},
|
||||
'location_map': {
|
||||
'worker_cluster': 'body',
|
||||
},
|
||||
'collection_format_map': {
|
||||
}
|
||||
},
|
||||
headers_map={
|
||||
'accept': [
|
||||
'application/json'
|
||||
],
|
||||
'content_type': [
|
||||
'application/json'
|
||||
]
|
||||
},
|
||||
api_client=api_client
|
||||
)
|
||||
self.delete_worker_endpoint = _Endpoint(
|
||||
settings={
|
||||
'response_type': None,
|
||||
@ -88,6 +141,55 @@ class WorkerMgtApi(object):
|
||||
},
|
||||
api_client=api_client
|
||||
)
|
||||
self.delete_worker_cluster_endpoint = _Endpoint(
|
||||
settings={
|
||||
'response_type': None,
|
||||
'auth': [],
|
||||
'endpoint_path': '/api/v3/worker-mgt/cluster/{cluster_id}',
|
||||
'operation_id': 'delete_worker_cluster',
|
||||
'http_method': 'DELETE',
|
||||
'servers': None,
|
||||
},
|
||||
params_map={
|
||||
'all': [
|
||||
'cluster_id',
|
||||
],
|
||||
'required': [
|
||||
'cluster_id',
|
||||
],
|
||||
'nullable': [
|
||||
],
|
||||
'enum': [
|
||||
],
|
||||
'validation': [
|
||||
]
|
||||
},
|
||||
root_map={
|
||||
'validations': {
|
||||
},
|
||||
'allowed_values': {
|
||||
},
|
||||
'openapi_types': {
|
||||
'cluster_id':
|
||||
(str,),
|
||||
},
|
||||
'attribute_map': {
|
||||
'cluster_id': 'cluster_id',
|
||||
},
|
||||
'location_map': {
|
||||
'cluster_id': 'path',
|
||||
},
|
||||
'collection_format_map': {
|
||||
}
|
||||
},
|
||||
headers_map={
|
||||
'accept': [
|
||||
'application/json'
|
||||
],
|
||||
'content_type': [],
|
||||
},
|
||||
api_client=api_client
|
||||
)
|
||||
self.fetch_worker_endpoint = _Endpoint(
|
||||
settings={
|
||||
'response_type': (Worker,),
|
||||
@ -137,6 +239,97 @@ class WorkerMgtApi(object):
|
||||
},
|
||||
api_client=api_client
|
||||
)
|
||||
self.fetch_worker_cluster_endpoint = _Endpoint(
|
||||
settings={
|
||||
'response_type': (WorkerCluster,),
|
||||
'auth': [],
|
||||
'endpoint_path': '/api/v3/worker-mgt/cluster/{cluster_id}',
|
||||
'operation_id': 'fetch_worker_cluster',
|
||||
'http_method': 'GET',
|
||||
'servers': None,
|
||||
},
|
||||
params_map={
|
||||
'all': [
|
||||
'cluster_id',
|
||||
],
|
||||
'required': [
|
||||
'cluster_id',
|
||||
],
|
||||
'nullable': [
|
||||
],
|
||||
'enum': [
|
||||
],
|
||||
'validation': [
|
||||
]
|
||||
},
|
||||
root_map={
|
||||
'validations': {
|
||||
},
|
||||
'allowed_values': {
|
||||
},
|
||||
'openapi_types': {
|
||||
'cluster_id':
|
||||
(str,),
|
||||
},
|
||||
'attribute_map': {
|
||||
'cluster_id': 'cluster_id',
|
||||
},
|
||||
'location_map': {
|
||||
'cluster_id': 'path',
|
||||
},
|
||||
'collection_format_map': {
|
||||
}
|
||||
},
|
||||
headers_map={
|
||||
'accept': [
|
||||
'application/json'
|
||||
],
|
||||
'content_type': [],
|
||||
},
|
||||
api_client=api_client
|
||||
)
|
||||
self.fetch_worker_clusters_endpoint = _Endpoint(
|
||||
settings={
|
||||
'response_type': (WorkerClusterList,),
|
||||
'auth': [],
|
||||
'endpoint_path': '/api/v3/worker-mgt/clusters',
|
||||
'operation_id': 'fetch_worker_clusters',
|
||||
'http_method': 'GET',
|
||||
'servers': None,
|
||||
},
|
||||
params_map={
|
||||
'all': [
|
||||
],
|
||||
'required': [],
|
||||
'nullable': [
|
||||
],
|
||||
'enum': [
|
||||
],
|
||||
'validation': [
|
||||
]
|
||||
},
|
||||
root_map={
|
||||
'validations': {
|
||||
},
|
||||
'allowed_values': {
|
||||
},
|
||||
'openapi_types': {
|
||||
},
|
||||
'attribute_map': {
|
||||
},
|
||||
'location_map': {
|
||||
},
|
||||
'collection_format_map': {
|
||||
}
|
||||
},
|
||||
headers_map={
|
||||
'accept': [
|
||||
'application/json'
|
||||
],
|
||||
'content_type': [],
|
||||
},
|
||||
api_client=api_client
|
||||
)
|
||||
self.fetch_worker_sleep_schedule_endpoint = _Endpoint(
|
||||
settings={
|
||||
'response_type': (WorkerSleepSchedule,),
|
||||
@ -284,6 +477,62 @@ class WorkerMgtApi(object):
|
||||
},
|
||||
api_client=api_client
|
||||
)
|
||||
self.set_worker_clusters_endpoint = _Endpoint(
|
||||
settings={
|
||||
'response_type': None,
|
||||
'auth': [],
|
||||
'endpoint_path': '/api/v3/worker-mgt/workers/{worker_id}/setclusters',
|
||||
'operation_id': 'set_worker_clusters',
|
||||
'http_method': 'POST',
|
||||
'servers': None,
|
||||
},
|
||||
params_map={
|
||||
'all': [
|
||||
'worker_id',
|
||||
'worker_cluster_change_request',
|
||||
],
|
||||
'required': [
|
||||
'worker_id',
|
||||
'worker_cluster_change_request',
|
||||
],
|
||||
'nullable': [
|
||||
],
|
||||
'enum': [
|
||||
],
|
||||
'validation': [
|
||||
]
|
||||
},
|
||||
root_map={
|
||||
'validations': {
|
||||
},
|
||||
'allowed_values': {
|
||||
},
|
||||
'openapi_types': {
|
||||
'worker_id':
|
||||
(str,),
|
||||
'worker_cluster_change_request':
|
||||
(WorkerClusterChangeRequest,),
|
||||
},
|
||||
'attribute_map': {
|
||||
'worker_id': 'worker_id',
|
||||
},
|
||||
'location_map': {
|
||||
'worker_id': 'path',
|
||||
'worker_cluster_change_request': 'body',
|
||||
},
|
||||
'collection_format_map': {
|
||||
}
|
||||
},
|
||||
headers_map={
|
||||
'accept': [
|
||||
'application/json'
|
||||
],
|
||||
'content_type': [
|
||||
'application/json'
|
||||
]
|
||||
},
|
||||
api_client=api_client
|
||||
)
|
||||
self.set_worker_sleep_schedule_endpoint = _Endpoint(
|
||||
settings={
|
||||
'response_type': None,
|
||||
@ -340,6 +589,139 @@ class WorkerMgtApi(object):
|
||||
},
|
||||
api_client=api_client
|
||||
)
|
||||
self.update_worker_cluster_endpoint = _Endpoint(
|
||||
settings={
|
||||
'response_type': None,
|
||||
'auth': [],
|
||||
'endpoint_path': '/api/v3/worker-mgt/cluster/{cluster_id}',
|
||||
'operation_id': 'update_worker_cluster',
|
||||
'http_method': 'PUT',
|
||||
'servers': None,
|
||||
},
|
||||
params_map={
|
||||
'all': [
|
||||
'cluster_id',
|
||||
'worker_cluster',
|
||||
],
|
||||
'required': [
|
||||
'cluster_id',
|
||||
'worker_cluster',
|
||||
],
|
||||
'nullable': [
|
||||
],
|
||||
'enum': [
|
||||
],
|
||||
'validation': [
|
||||
]
|
||||
},
|
||||
root_map={
|
||||
'validations': {
|
||||
},
|
||||
'allowed_values': {
|
||||
},
|
||||
'openapi_types': {
|
||||
'cluster_id':
|
||||
(str,),
|
||||
'worker_cluster':
|
||||
(WorkerCluster,),
|
||||
},
|
||||
'attribute_map': {
|
||||
'cluster_id': 'cluster_id',
|
||||
},
|
||||
'location_map': {
|
||||
'cluster_id': 'path',
|
||||
'worker_cluster': 'body',
|
||||
},
|
||||
'collection_format_map': {
|
||||
}
|
||||
},
|
||||
headers_map={
|
||||
'accept': [
|
||||
'application/json'
|
||||
],
|
||||
'content_type': [
|
||||
'application/json'
|
||||
]
|
||||
},
|
||||
api_client=api_client
|
||||
)
|
||||
|
||||
def create_worker_cluster(
|
||||
self,
|
||||
worker_cluster,
|
||||
**kwargs
|
||||
):
|
||||
"""Create a new worker cluster. # noqa: E501
|
||||
|
||||
This method makes a synchronous HTTP request by default. To make an
|
||||
asynchronous HTTP request, please pass async_req=True
|
||||
|
||||
>>> thread = api.create_worker_cluster(worker_cluster, async_req=True)
|
||||
>>> result = thread.get()
|
||||
|
||||
Args:
|
||||
worker_cluster (WorkerCluster): The worker cluster.
|
||||
|
||||
Keyword Args:
|
||||
_return_http_data_only (bool): response data without head status
|
||||
code and headers. Default is True.
|
||||
_preload_content (bool): if False, the urllib3.HTTPResponse object
|
||||
will be returned without reading/decoding response data.
|
||||
Default is True.
|
||||
_request_timeout (int/float/tuple): timeout setting for this request. If
|
||||
one number provided, it will be total request timeout. It can also
|
||||
be a pair (tuple) of (connection, read) timeouts.
|
||||
Default is None.
|
||||
_check_input_type (bool): specifies if type checking
|
||||
should be done one the data sent to the server.
|
||||
Default is True.
|
||||
_check_return_type (bool): specifies if type checking
|
||||
should be done one the data received from the server.
|
||||
Default is True.
|
||||
_spec_property_naming (bool): True if the variable names in the input data
|
||||
are serialized names, as specified in the OpenAPI document.
|
||||
False if the variable names in the input data
|
||||
are pythonic names, e.g. snake case (default)
|
||||
_content_type (str/None): force body content-type.
|
||||
Default is None and content-type will be predicted by allowed
|
||||
content-types and body.
|
||||
_host_index (int/None): specifies the index of the server
|
||||
that we want to use.
|
||||
Default is read from the configuration.
|
||||
async_req (bool): execute request asynchronously
|
||||
|
||||
Returns:
|
||||
WorkerCluster
|
||||
If the method is called asynchronously, returns the request
|
||||
thread.
|
||||
"""
|
||||
kwargs['async_req'] = kwargs.get(
|
||||
'async_req', False
|
||||
)
|
||||
kwargs['_return_http_data_only'] = kwargs.get(
|
||||
'_return_http_data_only', True
|
||||
)
|
||||
kwargs['_preload_content'] = kwargs.get(
|
||||
'_preload_content', True
|
||||
)
|
||||
kwargs['_request_timeout'] = kwargs.get(
|
||||
'_request_timeout', None
|
||||
)
|
||||
kwargs['_check_input_type'] = kwargs.get(
|
||||
'_check_input_type', True
|
||||
)
|
||||
kwargs['_check_return_type'] = kwargs.get(
|
||||
'_check_return_type', True
|
||||
)
|
||||
kwargs['_spec_property_naming'] = kwargs.get(
|
||||
'_spec_property_naming', False
|
||||
)
|
||||
kwargs['_content_type'] = kwargs.get(
|
||||
'_content_type')
|
||||
kwargs['_host_index'] = kwargs.get('_host_index')
|
||||
kwargs['worker_cluster'] = \
|
||||
worker_cluster
|
||||
return self.create_worker_cluster_endpoint.call_with_http_info(**kwargs)
|
||||
|
||||
def delete_worker(
|
||||
self,
|
||||
@ -418,6 +800,83 @@ class WorkerMgtApi(object):
|
||||
worker_id
|
||||
return self.delete_worker_endpoint.call_with_http_info(**kwargs)
|
||||
|
||||
def delete_worker_cluster(
|
||||
self,
|
||||
cluster_id,
|
||||
**kwargs
|
||||
):
|
||||
"""Remove this worker cluster. This unassigns all workers from the cluster and removes it. # noqa: E501
|
||||
|
||||
This method makes a synchronous HTTP request by default. To make an
|
||||
asynchronous HTTP request, please pass async_req=True
|
||||
|
||||
>>> thread = api.delete_worker_cluster(cluster_id, async_req=True)
|
||||
>>> result = thread.get()
|
||||
|
||||
Args:
|
||||
cluster_id (str):
|
||||
|
||||
Keyword Args:
|
||||
_return_http_data_only (bool): response data without head status
|
||||
code and headers. Default is True.
|
||||
_preload_content (bool): if False, the urllib3.HTTPResponse object
|
||||
will be returned without reading/decoding response data.
|
||||
Default is True.
|
||||
_request_timeout (int/float/tuple): timeout setting for this request. If
|
||||
one number provided, it will be total request timeout. It can also
|
||||
be a pair (tuple) of (connection, read) timeouts.
|
||||
Default is None.
|
||||
_check_input_type (bool): specifies if type checking
|
||||
should be done one the data sent to the server.
|
||||
Default is True.
|
||||
_check_return_type (bool): specifies if type checking
|
||||
should be done one the data received from the server.
|
||||
Default is True.
|
||||
_spec_property_naming (bool): True if the variable names in the input data
|
||||
are serialized names, as specified in the OpenAPI document.
|
||||
False if the variable names in the input data
|
||||
are pythonic names, e.g. snake case (default)
|
||||
_content_type (str/None): force body content-type.
|
||||
Default is None and content-type will be predicted by allowed
|
||||
content-types and body.
|
||||
_host_index (int/None): specifies the index of the server
|
||||
that we want to use.
|
||||
Default is read from the configuration.
|
||||
async_req (bool): execute request asynchronously
|
||||
|
||||
Returns:
|
||||
None
|
||||
If the method is called asynchronously, returns the request
|
||||
thread.
|
||||
"""
|
||||
kwargs['async_req'] = kwargs.get(
|
||||
'async_req', False
|
||||
)
|
||||
kwargs['_return_http_data_only'] = kwargs.get(
|
||||
'_return_http_data_only', True
|
||||
)
|
||||
kwargs['_preload_content'] = kwargs.get(
|
||||
'_preload_content', True
|
||||
)
|
||||
kwargs['_request_timeout'] = kwargs.get(
|
||||
'_request_timeout', None
|
||||
)
|
||||
kwargs['_check_input_type'] = kwargs.get(
|
||||
'_check_input_type', True
|
||||
)
|
||||
kwargs['_check_return_type'] = kwargs.get(
|
||||
'_check_return_type', True
|
||||
)
|
||||
kwargs['_spec_property_naming'] = kwargs.get(
|
||||
'_spec_property_naming', False
|
||||
)
|
||||
kwargs['_content_type'] = kwargs.get(
|
||||
'_content_type')
|
||||
kwargs['_host_index'] = kwargs.get('_host_index')
|
||||
kwargs['cluster_id'] = \
|
||||
cluster_id
|
||||
return self.delete_worker_cluster_endpoint.call_with_http_info(**kwargs)
|
||||
|
||||
def fetch_worker(
|
||||
self,
|
||||
worker_id,
|
||||
@ -495,6 +954,155 @@ class WorkerMgtApi(object):
|
||||
worker_id
|
||||
return self.fetch_worker_endpoint.call_with_http_info(**kwargs)
|
||||
|
||||
def fetch_worker_cluster(
|
||||
self,
|
||||
cluster_id,
|
||||
**kwargs
|
||||
):
|
||||
"""Get a single worker cluster. # noqa: E501
|
||||
|
||||
This method makes a synchronous HTTP request by default. To make an
|
||||
asynchronous HTTP request, please pass async_req=True
|
||||
|
||||
>>> thread = api.fetch_worker_cluster(cluster_id, async_req=True)
|
||||
>>> result = thread.get()
|
||||
|
||||
Args:
|
||||
cluster_id (str):
|
||||
|
||||
Keyword Args:
|
||||
_return_http_data_only (bool): response data without head status
|
||||
code and headers. Default is True.
|
||||
_preload_content (bool): if False, the urllib3.HTTPResponse object
|
||||
will be returned without reading/decoding response data.
|
||||
Default is True.
|
||||
_request_timeout (int/float/tuple): timeout setting for this request. If
|
||||
one number provided, it will be total request timeout. It can also
|
||||
be a pair (tuple) of (connection, read) timeouts.
|
||||
Default is None.
|
||||
_check_input_type (bool): specifies if type checking
|
||||
should be done one the data sent to the server.
|
||||
Default is True.
|
||||
_check_return_type (bool): specifies if type checking
|
||||
should be done one the data received from the server.
|
||||
Default is True.
|
||||
_spec_property_naming (bool): True if the variable names in the input data
|
||||
are serialized names, as specified in the OpenAPI document.
|
||||
False if the variable names in the input data
|
||||
are pythonic names, e.g. snake case (default)
|
||||
_content_type (str/None): force body content-type.
|
||||
Default is None and content-type will be predicted by allowed
|
||||
content-types and body.
|
||||
_host_index (int/None): specifies the index of the server
|
||||
that we want to use.
|
||||
Default is read from the configuration.
|
||||
async_req (bool): execute request asynchronously
|
||||
|
||||
Returns:
|
||||
WorkerCluster
|
||||
If the method is called asynchronously, returns the request
|
||||
thread.
|
||||
"""
|
||||
kwargs['async_req'] = kwargs.get(
|
||||
'async_req', False
|
||||
)
|
||||
kwargs['_return_http_data_only'] = kwargs.get(
|
||||
'_return_http_data_only', True
|
||||
)
|
||||
kwargs['_preload_content'] = kwargs.get(
|
||||
'_preload_content', True
|
||||
)
|
||||
kwargs['_request_timeout'] = kwargs.get(
|
||||
'_request_timeout', None
|
||||
)
|
||||
kwargs['_check_input_type'] = kwargs.get(
|
||||
'_check_input_type', True
|
||||
)
|
||||
kwargs['_check_return_type'] = kwargs.get(
|
||||
'_check_return_type', True
|
||||
)
|
||||
kwargs['_spec_property_naming'] = kwargs.get(
|
||||
'_spec_property_naming', False
|
||||
)
|
||||
kwargs['_content_type'] = kwargs.get(
|
||||
'_content_type')
|
||||
kwargs['_host_index'] = kwargs.get('_host_index')
|
||||
kwargs['cluster_id'] = \
|
||||
cluster_id
|
||||
return self.fetch_worker_cluster_endpoint.call_with_http_info(**kwargs)
|
||||
|
||||
def fetch_worker_clusters(
|
||||
self,
|
||||
**kwargs
|
||||
):
|
||||
"""Get list of worker clusters. # noqa: E501
|
||||
|
||||
This method makes a synchronous HTTP request by default. To make an
|
||||
asynchronous HTTP request, please pass async_req=True
|
||||
|
||||
>>> thread = api.fetch_worker_clusters(async_req=True)
|
||||
>>> result = thread.get()
|
||||
|
||||
|
||||
Keyword Args:
|
||||
_return_http_data_only (bool): response data without head status
|
||||
code and headers. Default is True.
|
||||
_preload_content (bool): if False, the urllib3.HTTPResponse object
|
||||
will be returned without reading/decoding response data.
|
||||
Default is True.
|
||||
_request_timeout (int/float/tuple): timeout setting for this request. If
|
||||
one number provided, it will be total request timeout. It can also
|
||||
be a pair (tuple) of (connection, read) timeouts.
|
||||
Default is None.
|
||||
_check_input_type (bool): specifies if type checking
|
||||
should be done one the data sent to the server.
|
||||
Default is True.
|
||||
_check_return_type (bool): specifies if type checking
|
||||
should be done one the data received from the server.
|
||||
Default is True.
|
||||
_spec_property_naming (bool): True if the variable names in the input data
|
||||
are serialized names, as specified in the OpenAPI document.
|
||||
False if the variable names in the input data
|
||||
are pythonic names, e.g. snake case (default)
|
||||
_content_type (str/None): force body content-type.
|
||||
Default is None and content-type will be predicted by allowed
|
||||
content-types and body.
|
||||
_host_index (int/None): specifies the index of the server
|
||||
that we want to use.
|
||||
Default is read from the configuration.
|
||||
async_req (bool): execute request asynchronously
|
||||
|
||||
Returns:
|
||||
WorkerClusterList
|
||||
If the method is called asynchronously, returns the request
|
||||
thread.
|
||||
"""
|
||||
kwargs['async_req'] = kwargs.get(
|
||||
'async_req', False
|
||||
)
|
||||
kwargs['_return_http_data_only'] = kwargs.get(
|
||||
'_return_http_data_only', True
|
||||
)
|
||||
kwargs['_preload_content'] = kwargs.get(
|
||||
'_preload_content', True
|
||||
)
|
||||
kwargs['_request_timeout'] = kwargs.get(
|
||||
'_request_timeout', None
|
||||
)
|
||||
kwargs['_check_input_type'] = kwargs.get(
|
||||
'_check_input_type', True
|
||||
)
|
||||
kwargs['_check_return_type'] = kwargs.get(
|
||||
'_check_return_type', True
|
||||
)
|
||||
kwargs['_spec_property_naming'] = kwargs.get(
|
||||
'_spec_property_naming', False
|
||||
)
|
||||
kwargs['_content_type'] = kwargs.get(
|
||||
'_content_type')
|
||||
kwargs['_host_index'] = kwargs.get('_host_index')
|
||||
return self.fetch_worker_clusters_endpoint.call_with_http_info(**kwargs)
|
||||
|
||||
def fetch_worker_sleep_schedule(
|
||||
self,
|
||||
worker_id,
|
||||
@ -725,6 +1333,87 @@ class WorkerMgtApi(object):
|
||||
worker_status_change_request
|
||||
return self.request_worker_status_change_endpoint.call_with_http_info(**kwargs)
|
||||
|
||||
def set_worker_clusters(
|
||||
self,
|
||||
worker_id,
|
||||
worker_cluster_change_request,
|
||||
**kwargs
|
||||
):
|
||||
"""set_worker_clusters # noqa: E501
|
||||
|
||||
This method makes a synchronous HTTP request by default. To make an
|
||||
asynchronous HTTP request, please pass async_req=True
|
||||
|
||||
>>> thread = api.set_worker_clusters(worker_id, worker_cluster_change_request, async_req=True)
|
||||
>>> result = thread.get()
|
||||
|
||||
Args:
|
||||
worker_id (str):
|
||||
worker_cluster_change_request (WorkerClusterChangeRequest): The list of cluster IDs this worker should be a member of.
|
||||
|
||||
Keyword Args:
|
||||
_return_http_data_only (bool): response data without head status
|
||||
code and headers. Default is True.
|
||||
_preload_content (bool): if False, the urllib3.HTTPResponse object
|
||||
will be returned without reading/decoding response data.
|
||||
Default is True.
|
||||
_request_timeout (int/float/tuple): timeout setting for this request. If
|
||||
one number provided, it will be total request timeout. It can also
|
||||
be a pair (tuple) of (connection, read) timeouts.
|
||||
Default is None.
|
||||
_check_input_type (bool): specifies if type checking
|
||||
should be done one the data sent to the server.
|
||||
Default is True.
|
||||
_check_return_type (bool): specifies if type checking
|
||||
should be done one the data received from the server.
|
||||
Default is True.
|
||||
_spec_property_naming (bool): True if the variable names in the input data
|
||||
are serialized names, as specified in the OpenAPI document.
|
||||
False if the variable names in the input data
|
||||
are pythonic names, e.g. snake case (default)
|
||||
_content_type (str/None): force body content-type.
|
||||
Default is None and content-type will be predicted by allowed
|
||||
content-types and body.
|
||||
_host_index (int/None): specifies the index of the server
|
||||
that we want to use.
|
||||
Default is read from the configuration.
|
||||
async_req (bool): execute request asynchronously
|
||||
|
||||
Returns:
|
||||
None
|
||||
If the method is called asynchronously, returns the request
|
||||
thread.
|
||||
"""
|
||||
kwargs['async_req'] = kwargs.get(
|
||||
'async_req', False
|
||||
)
|
||||
kwargs['_return_http_data_only'] = kwargs.get(
|
||||
'_return_http_data_only', True
|
||||
)
|
||||
kwargs['_preload_content'] = kwargs.get(
|
||||
'_preload_content', True
|
||||
)
|
||||
kwargs['_request_timeout'] = kwargs.get(
|
||||
'_request_timeout', None
|
||||
)
|
||||
kwargs['_check_input_type'] = kwargs.get(
|
||||
'_check_input_type', True
|
||||
)
|
||||
kwargs['_check_return_type'] = kwargs.get(
|
||||
'_check_return_type', True
|
||||
)
|
||||
kwargs['_spec_property_naming'] = kwargs.get(
|
||||
'_spec_property_naming', False
|
||||
)
|
||||
kwargs['_content_type'] = kwargs.get(
|
||||
'_content_type')
|
||||
kwargs['_host_index'] = kwargs.get('_host_index')
|
||||
kwargs['worker_id'] = \
|
||||
worker_id
|
||||
kwargs['worker_cluster_change_request'] = \
|
||||
worker_cluster_change_request
|
||||
return self.set_worker_clusters_endpoint.call_with_http_info(**kwargs)
|
||||
|
||||
def set_worker_sleep_schedule(
|
||||
self,
|
||||
worker_id,
|
||||
@ -806,3 +1495,84 @@ class WorkerMgtApi(object):
|
||||
worker_sleep_schedule
|
||||
return self.set_worker_sleep_schedule_endpoint.call_with_http_info(**kwargs)
|
||||
|
||||
def update_worker_cluster(
|
||||
self,
|
||||
cluster_id,
|
||||
worker_cluster,
|
||||
**kwargs
|
||||
):
|
||||
"""Update an existing worker cluster. # noqa: E501
|
||||
|
||||
This method makes a synchronous HTTP request by default. To make an
|
||||
asynchronous HTTP request, please pass async_req=True
|
||||
|
||||
>>> thread = api.update_worker_cluster(cluster_id, worker_cluster, async_req=True)
|
||||
>>> result = thread.get()
|
||||
|
||||
Args:
|
||||
cluster_id (str):
|
||||
worker_cluster (WorkerCluster): The updated worker cluster.
|
||||
|
||||
Keyword Args:
|
||||
_return_http_data_only (bool): response data without head status
|
||||
code and headers. Default is True.
|
||||
_preload_content (bool): if False, the urllib3.HTTPResponse object
|
||||
will be returned without reading/decoding response data.
|
||||
Default is True.
|
||||
_request_timeout (int/float/tuple): timeout setting for this request. If
|
||||
one number provided, it will be total request timeout. It can also
|
||||
be a pair (tuple) of (connection, read) timeouts.
|
||||
Default is None.
|
||||
_check_input_type (bool): specifies if type checking
|
||||
should be done one the data sent to the server.
|
||||
Default is True.
|
||||
_check_return_type (bool): specifies if type checking
|
||||
should be done one the data received from the server.
|
||||
Default is True.
|
||||
_spec_property_naming (bool): True if the variable names in the input data
|
||||
are serialized names, as specified in the OpenAPI document.
|
||||
False if the variable names in the input data
|
||||
are pythonic names, e.g. snake case (default)
|
||||
_content_type (str/None): force body content-type.
|
||||
Default is None and content-type will be predicted by allowed
|
||||
content-types and body.
|
||||
_host_index (int/None): specifies the index of the server
|
||||
that we want to use.
|
||||
Default is read from the configuration.
|
||||
async_req (bool): execute request asynchronously
|
||||
|
||||
Returns:
|
||||
None
|
||||
If the method is called asynchronously, returns the request
|
||||
thread.
|
||||
"""
|
||||
kwargs['async_req'] = kwargs.get(
|
||||
'async_req', False
|
||||
)
|
||||
kwargs['_return_http_data_only'] = kwargs.get(
|
||||
'_return_http_data_only', True
|
||||
)
|
||||
kwargs['_preload_content'] = kwargs.get(
|
||||
'_preload_content', True
|
||||
)
|
||||
kwargs['_request_timeout'] = kwargs.get(
|
||||
'_request_timeout', None
|
||||
)
|
||||
kwargs['_check_input_type'] = kwargs.get(
|
||||
'_check_input_type', True
|
||||
)
|
||||
kwargs['_check_return_type'] = kwargs.get(
|
||||
'_check_return_type', True
|
||||
)
|
||||
kwargs['_spec_property_naming'] = kwargs.get(
|
||||
'_spec_property_naming', False
|
||||
)
|
||||
kwargs['_content_type'] = kwargs.get(
|
||||
'_content_type')
|
||||
kwargs['_host_index'] = kwargs.get('_host_index')
|
||||
kwargs['cluster_id'] = \
|
||||
cluster_id
|
||||
kwargs['worker_cluster'] = \
|
||||
worker_cluster
|
||||
return self.update_worker_cluster_endpoint.call_with_http_info(**kwargs)
|
||||
|
||||
|
2
addon/flamenco/manager/api_client.py
generated
2
addon/flamenco/manager/api_client.py
generated
@ -76,7 +76,7 @@ class ApiClient(object):
|
||||
self.default_headers[header_name] = header_value
|
||||
self.cookie = cookie
|
||||
# Set default User-Agent.
|
||||
self.user_agent = 'Flamenco/3.2 (Blender add-on)'
|
||||
self.user_agent = 'Flamenco/3.3-alpha0 (Blender add-on)'
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
2
addon/flamenco/manager/configuration.py
generated
2
addon/flamenco/manager/configuration.py
generated
@ -404,7 +404,7 @@ conf = flamenco.manager.Configuration(
|
||||
"OS: {env}\n"\
|
||||
"Python Version: {pyversion}\n"\
|
||||
"Version of the API: 1.0.0\n"\
|
||||
"SDK Package Version: 3.2".\
|
||||
"SDK Package Version: 3.3-alpha0".\
|
||||
format(env=sys.platform, pyversion=sys.version)
|
||||
|
||||
def get_host_settings(self):
|
||||
|
1
addon/flamenco/manager/docs/Job.md
generated
1
addon/flamenco/manager/docs/Job.md
generated
@ -17,6 +17,7 @@ Name | Type | Description | Notes
|
||||
**settings** | [**JobSettings**](JobSettings.md) | | [optional]
|
||||
**metadata** | [**JobMetadata**](JobMetadata.md) | | [optional]
|
||||
**storage** | [**JobStorageInfo**](JobStorageInfo.md) | | [optional]
|
||||
**worker_cluster** | **str** | Worker Cluster that should execute this job. When a cluster ID is given, only Workers in that cluster will be scheduled to work on it. If empty or ommitted, all workers can work on this job. | [optional]
|
||||
**delete_requested_at** | **datetime** | If job deletion was requested, this is the timestamp at which that request was stored on Flamenco Manager. | [optional]
|
||||
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
|
||||
|
||||
|
2
addon/flamenco/manager/docs/JobsApi.md
generated
2
addon/flamenco/manager/docs/JobsApi.md
generated
@ -1225,6 +1225,7 @@ with flamenco.manager.ApiClient() as api_client:
|
||||
storage=JobStorageInfo(
|
||||
shaman_checkout_id="shaman_checkout_id_example",
|
||||
),
|
||||
worker_cluster="worker_cluster_example",
|
||||
) # SubmittedJob | Job to submit
|
||||
|
||||
# example passing only required values which don't have defaults set
|
||||
@ -1306,6 +1307,7 @@ with flamenco.manager.ApiClient() as api_client:
|
||||
storage=JobStorageInfo(
|
||||
shaman_checkout_id="shaman_checkout_id_example",
|
||||
),
|
||||
worker_cluster="worker_cluster_example",
|
||||
) # SubmittedJob | Job to check
|
||||
|
||||
# example passing only required values which don't have defaults set
|
||||
|
1
addon/flamenco/manager/docs/SubmittedJob.md
generated
1
addon/flamenco/manager/docs/SubmittedJob.md
generated
@ -13,6 +13,7 @@ Name | Type | Description | Notes
|
||||
**settings** | [**JobSettings**](JobSettings.md) | | [optional]
|
||||
**metadata** | [**JobMetadata**](JobMetadata.md) | | [optional]
|
||||
**storage** | [**JobStorageInfo**](JobStorageInfo.md) | | [optional]
|
||||
**worker_cluster** | **str** | Worker Cluster that should execute this job. When a cluster ID is given, only Workers in that cluster will be scheduled to work on it. If empty or ommitted, all workers can work on this job. | [optional]
|
||||
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
1
addon/flamenco/manager/docs/Worker.md
generated
1
addon/flamenco/manager/docs/Worker.md
generated
@ -15,6 +15,7 @@ Name | Type | Description | Notes
|
||||
**status_change** | [**WorkerStatusChangeRequest**](WorkerStatusChangeRequest.md) | | [optional]
|
||||
**last_seen** | **datetime** | Last time this worker was seen by the Manager. | [optional]
|
||||
**task** | [**WorkerTask**](WorkerTask.md) | | [optional]
|
||||
**clusters** | [**[WorkerCluster]**](WorkerCluster.md) | Clusters of which this Worker is a member. | [optional]
|
||||
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
1
addon/flamenco/manager/docs/WorkerAllOf.md
generated
1
addon/flamenco/manager/docs/WorkerAllOf.md
generated
@ -8,6 +8,7 @@ Name | Type | Description | Notes
|
||||
**platform** | **str** | Operating system of the Worker |
|
||||
**supported_task_types** | **[str]** | |
|
||||
**task** | [**WorkerTask**](WorkerTask.md) | | [optional]
|
||||
**clusters** | [**[WorkerCluster]**](WorkerCluster.md) | Clusters of which this Worker is a member. | [optional]
|
||||
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
15
addon/flamenco/manager/docs/WorkerCluster.md
generated
Normal file
15
addon/flamenco/manager/docs/WorkerCluster.md
generated
Normal file
@ -0,0 +1,15 @@
|
||||
# WorkerCluster
|
||||
|
||||
Cluster of workers. A job can optionally specify which cluster it should be limited to. Workers can be part of multiple clusters simultaneously.
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**name** | **str** | |
|
||||
**id** | **str** | UUID of the cluster. Can be ommitted when creating a new cluster, in which case a random UUID will be assigned. | [optional]
|
||||
**description** | **str** | | [optional]
|
||||
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
13
addon/flamenco/manager/docs/WorkerClusterChangeRequest.md
generated
Normal file
13
addon/flamenco/manager/docs/WorkerClusterChangeRequest.md
generated
Normal file
@ -0,0 +1,13 @@
|
||||
# WorkerClusterChangeRequest
|
||||
|
||||
Request to change which clusters this Worker is assigned to.
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**cluster_ids** | **[str]** | |
|
||||
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
12
addon/flamenco/manager/docs/WorkerClusterList.md
generated
Normal file
12
addon/flamenco/manager/docs/WorkerClusterList.md
generated
Normal file
@ -0,0 +1,12 @@
|
||||
# WorkerClusterList
|
||||
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**clusters** | [**[WorkerCluster]**](WorkerCluster.md) | | [optional]
|
||||
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
411
addon/flamenco/manager/docs/WorkerMgtApi.md
generated
411
addon/flamenco/manager/docs/WorkerMgtApi.md
generated
@ -4,14 +4,91 @@ All URIs are relative to *http://localhost*
|
||||
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**create_worker_cluster**](WorkerMgtApi.md#create_worker_cluster) | **POST** /api/v3/worker-mgt/clusters | Create a new worker cluster.
|
||||
[**delete_worker**](WorkerMgtApi.md#delete_worker) | **DELETE** /api/v3/worker-mgt/workers/{worker_id} | Remove the given worker. It is recommended to only call this function when the worker is in `offline` state. If the worker is still running, stop it first. Any task still assigned to the worker will be requeued.
|
||||
[**delete_worker_cluster**](WorkerMgtApi.md#delete_worker_cluster) | **DELETE** /api/v3/worker-mgt/cluster/{cluster_id} | Remove this worker cluster. This unassigns all workers from the cluster and removes it.
|
||||
[**fetch_worker**](WorkerMgtApi.md#fetch_worker) | **GET** /api/v3/worker-mgt/workers/{worker_id} | Fetch info about the worker.
|
||||
[**fetch_worker_cluster**](WorkerMgtApi.md#fetch_worker_cluster) | **GET** /api/v3/worker-mgt/cluster/{cluster_id} | Get a single worker cluster.
|
||||
[**fetch_worker_clusters**](WorkerMgtApi.md#fetch_worker_clusters) | **GET** /api/v3/worker-mgt/clusters | Get list of worker clusters.
|
||||
[**fetch_worker_sleep_schedule**](WorkerMgtApi.md#fetch_worker_sleep_schedule) | **GET** /api/v3/worker-mgt/workers/{worker_id}/sleep-schedule |
|
||||
[**fetch_workers**](WorkerMgtApi.md#fetch_workers) | **GET** /api/v3/worker-mgt/workers | Get list of workers.
|
||||
[**request_worker_status_change**](WorkerMgtApi.md#request_worker_status_change) | **POST** /api/v3/worker-mgt/workers/{worker_id}/setstatus |
|
||||
[**set_worker_clusters**](WorkerMgtApi.md#set_worker_clusters) | **POST** /api/v3/worker-mgt/workers/{worker_id}/setclusters |
|
||||
[**set_worker_sleep_schedule**](WorkerMgtApi.md#set_worker_sleep_schedule) | **POST** /api/v3/worker-mgt/workers/{worker_id}/sleep-schedule |
|
||||
[**update_worker_cluster**](WorkerMgtApi.md#update_worker_cluster) | **PUT** /api/v3/worker-mgt/cluster/{cluster_id} | Update an existing worker cluster.
|
||||
|
||||
|
||||
# **create_worker_cluster**
|
||||
> WorkerCluster create_worker_cluster(worker_cluster)
|
||||
|
||||
Create a new worker cluster.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import time
|
||||
import flamenco.manager
|
||||
from flamenco.manager.api import worker_mgt_api
|
||||
from flamenco.manager.model.error import Error
|
||||
from flamenco.manager.model.worker_cluster import WorkerCluster
|
||||
from pprint import pprint
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = flamenco.manager.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
with flamenco.manager.ApiClient() as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = worker_mgt_api.WorkerMgtApi(api_client)
|
||||
worker_cluster = WorkerCluster(
|
||||
id="id_example",
|
||||
name="name_example",
|
||||
description="description_example",
|
||||
) # WorkerCluster | The worker cluster.
|
||||
|
||||
# example passing only required values which don't have defaults set
|
||||
try:
|
||||
# Create a new worker cluster.
|
||||
api_response = api_instance.create_worker_cluster(worker_cluster)
|
||||
pprint(api_response)
|
||||
except flamenco.manager.ApiException as e:
|
||||
print("Exception when calling WorkerMgtApi->create_worker_cluster: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**worker_cluster** | [**WorkerCluster**](WorkerCluster.md)| The worker cluster. |
|
||||
|
||||
### Return type
|
||||
|
||||
[**WorkerCluster**](WorkerCluster.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | The cluster was created. The created cluster is returned, so that the caller can know its UUID. | - |
|
||||
**0** | Error message | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **delete_worker**
|
||||
> delete_worker(worker_id)
|
||||
|
||||
@ -77,6 +154,71 @@ No authorization required
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **delete_worker_cluster**
|
||||
> delete_worker_cluster(cluster_id)
|
||||
|
||||
Remove this worker cluster. This unassigns all workers from the cluster and removes it.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import time
|
||||
import flamenco.manager
|
||||
from flamenco.manager.api import worker_mgt_api
|
||||
from flamenco.manager.model.error import Error
|
||||
from pprint import pprint
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = flamenco.manager.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
with flamenco.manager.ApiClient() as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = worker_mgt_api.WorkerMgtApi(api_client)
|
||||
cluster_id = "cluster_id_example" # str |
|
||||
|
||||
# example passing only required values which don't have defaults set
|
||||
try:
|
||||
# Remove this worker cluster. This unassigns all workers from the cluster and removes it.
|
||||
api_instance.delete_worker_cluster(cluster_id)
|
||||
except flamenco.manager.ApiException as e:
|
||||
print("Exception when calling WorkerMgtApi->delete_worker_cluster: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**cluster_id** | **str**| |
|
||||
|
||||
### Return type
|
||||
|
||||
void (empty response body)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**204** | The cluster has been removed. | - |
|
||||
**0** | Unexpected error. | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **fetch_worker**
|
||||
> Worker fetch_worker(worker_id)
|
||||
|
||||
@ -142,6 +284,132 @@ No authorization required
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **fetch_worker_cluster**
|
||||
> WorkerCluster fetch_worker_cluster(cluster_id)
|
||||
|
||||
Get a single worker cluster.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import time
|
||||
import flamenco.manager
|
||||
from flamenco.manager.api import worker_mgt_api
|
||||
from flamenco.manager.model.worker_cluster import WorkerCluster
|
||||
from pprint import pprint
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = flamenco.manager.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
with flamenco.manager.ApiClient() as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = worker_mgt_api.WorkerMgtApi(api_client)
|
||||
cluster_id = "cluster_id_example" # str |
|
||||
|
||||
# example passing only required values which don't have defaults set
|
||||
try:
|
||||
# Get a single worker cluster.
|
||||
api_response = api_instance.fetch_worker_cluster(cluster_id)
|
||||
pprint(api_response)
|
||||
except flamenco.manager.ApiException as e:
|
||||
print("Exception when calling WorkerMgtApi->fetch_worker_cluster: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**cluster_id** | **str**| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**WorkerCluster**](WorkerCluster.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | The worker cluster. | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **fetch_worker_clusters**
|
||||
> WorkerClusterList fetch_worker_clusters()
|
||||
|
||||
Get list of worker clusters.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import time
|
||||
import flamenco.manager
|
||||
from flamenco.manager.api import worker_mgt_api
|
||||
from flamenco.manager.model.worker_cluster_list import WorkerClusterList
|
||||
from pprint import pprint
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = flamenco.manager.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
with flamenco.manager.ApiClient() as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = worker_mgt_api.WorkerMgtApi(api_client)
|
||||
|
||||
# example, this endpoint has no required or optional parameters
|
||||
try:
|
||||
# Get list of worker clusters.
|
||||
api_response = api_instance.fetch_worker_clusters()
|
||||
pprint(api_response)
|
||||
except flamenco.manager.ApiException as e:
|
||||
print("Exception when calling WorkerMgtApi->fetch_worker_clusters: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
### Parameters
|
||||
This endpoint does not need any parameter.
|
||||
|
||||
### Return type
|
||||
|
||||
[**WorkerClusterList**](WorkerClusterList.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**200** | Worker clusters. | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **fetch_worker_sleep_schedule**
|
||||
> WorkerSleepSchedule fetch_worker_sleep_schedule(worker_id)
|
||||
|
||||
@ -331,6 +599,77 @@ No authorization required
|
||||
- **Accept**: application/json
|
||||
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**204** | Status change was accepted. | - |
|
||||
**0** | Unexpected error. | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **set_worker_clusters**
|
||||
> set_worker_clusters(worker_id, worker_cluster_change_request)
|
||||
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import time
|
||||
import flamenco.manager
|
||||
from flamenco.manager.api import worker_mgt_api
|
||||
from flamenco.manager.model.error import Error
|
||||
from flamenco.manager.model.worker_cluster_change_request import WorkerClusterChangeRequest
|
||||
from pprint import pprint
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = flamenco.manager.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
with flamenco.manager.ApiClient() as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = worker_mgt_api.WorkerMgtApi(api_client)
|
||||
worker_id = "worker_id_example" # str |
|
||||
worker_cluster_change_request = WorkerClusterChangeRequest(
|
||||
cluster_ids=[
|
||||
"cluster_ids_example",
|
||||
],
|
||||
) # WorkerClusterChangeRequest | The list of cluster IDs this worker should be a member of.
|
||||
|
||||
# example passing only required values which don't have defaults set
|
||||
try:
|
||||
api_instance.set_worker_clusters(worker_id, worker_cluster_change_request)
|
||||
except flamenco.manager.ApiException as e:
|
||||
print("Exception when calling WorkerMgtApi->set_worker_clusters: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**worker_id** | **str**| |
|
||||
**worker_cluster_change_request** | [**WorkerClusterChangeRequest**](WorkerClusterChangeRequest.md)| The list of cluster IDs this worker should be a member of. |
|
||||
|
||||
### Return type
|
||||
|
||||
void (empty response body)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
@ -412,3 +751,75 @@ No authorization required
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **update_worker_cluster**
|
||||
> update_worker_cluster(cluster_id, worker_cluster)
|
||||
|
||||
Update an existing worker cluster.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
```python
|
||||
import time
|
||||
import flamenco.manager
|
||||
from flamenco.manager.api import worker_mgt_api
|
||||
from flamenco.manager.model.error import Error
|
||||
from flamenco.manager.model.worker_cluster import WorkerCluster
|
||||
from pprint import pprint
|
||||
# Defining the host is optional and defaults to http://localhost
|
||||
# See configuration.py for a list of all supported configuration parameters.
|
||||
configuration = flamenco.manager.Configuration(
|
||||
host = "http://localhost"
|
||||
)
|
||||
|
||||
|
||||
# Enter a context with an instance of the API client
|
||||
with flamenco.manager.ApiClient() as api_client:
|
||||
# Create an instance of the API class
|
||||
api_instance = worker_mgt_api.WorkerMgtApi(api_client)
|
||||
cluster_id = "cluster_id_example" # str |
|
||||
worker_cluster = WorkerCluster(
|
||||
id="id_example",
|
||||
name="name_example",
|
||||
description="description_example",
|
||||
) # WorkerCluster | The updated worker cluster.
|
||||
|
||||
# example passing only required values which don't have defaults set
|
||||
try:
|
||||
# Update an existing worker cluster.
|
||||
api_instance.update_worker_cluster(cluster_id, worker_cluster)
|
||||
except flamenco.manager.ApiException as e:
|
||||
print("Exception when calling WorkerMgtApi->update_worker_cluster: %s\n" % e)
|
||||
```
|
||||
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**cluster_id** | **str**| |
|
||||
**worker_cluster** | [**WorkerCluster**](WorkerCluster.md)| The updated worker cluster. |
|
||||
|
||||
### Return type
|
||||
|
||||
void (empty response body)
|
||||
|
||||
### Authorization
|
||||
|
||||
No authorization required
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: application/json
|
||||
- **Accept**: application/json
|
||||
|
||||
|
||||
### HTTP response details
|
||||
|
||||
| Status code | Description | Response headers |
|
||||
|-------------|-------------|------------------|
|
||||
**204** | The cluster update has been stored. | - |
|
||||
**0** | Error message | - |
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
|
4
addon/flamenco/manager/model/job.py
generated
4
addon/flamenco/manager/model/job.py
generated
@ -110,6 +110,7 @@ class Job(ModelComposed):
|
||||
'settings': (JobSettings,), # noqa: E501
|
||||
'metadata': (JobMetadata,), # noqa: E501
|
||||
'storage': (JobStorageInfo,), # noqa: E501
|
||||
'worker_cluster': (str,), # noqa: E501
|
||||
'delete_requested_at': (datetime,), # noqa: E501
|
||||
}
|
||||
|
||||
@ -132,6 +133,7 @@ class Job(ModelComposed):
|
||||
'settings': 'settings', # noqa: E501
|
||||
'metadata': 'metadata', # noqa: E501
|
||||
'storage': 'storage', # noqa: E501
|
||||
'worker_cluster': 'worker_cluster', # noqa: E501
|
||||
'delete_requested_at': 'delete_requested_at', # noqa: E501
|
||||
}
|
||||
|
||||
@ -187,6 +189,7 @@ class Job(ModelComposed):
|
||||
settings (JobSettings): [optional] # noqa: E501
|
||||
metadata (JobMetadata): [optional] # noqa: E501
|
||||
storage (JobStorageInfo): [optional] # noqa: E501
|
||||
worker_cluster (str): Worker Cluster that should execute this job. When a cluster ID is given, only Workers in that cluster will be scheduled to work on it. If empty or ommitted, all workers can work on this job. . [optional] # noqa: E501
|
||||
delete_requested_at (datetime): If job deletion was requested, this is the timestamp at which that request was stored on Flamenco Manager. . [optional] # noqa: E501
|
||||
"""
|
||||
|
||||
@ -301,6 +304,7 @@ class Job(ModelComposed):
|
||||
settings (JobSettings): [optional] # noqa: E501
|
||||
metadata (JobMetadata): [optional] # noqa: E501
|
||||
storage (JobStorageInfo): [optional] # noqa: E501
|
||||
worker_cluster (str): Worker Cluster that should execute this job. When a cluster ID is given, only Workers in that cluster will be scheduled to work on it. If empty or ommitted, all workers can work on this job. . [optional] # noqa: E501
|
||||
delete_requested_at (datetime): If job deletion was requested, this is the timestamp at which that request was stored on Flamenco Manager. . [optional] # noqa: E501
|
||||
"""
|
||||
|
||||
|
4
addon/flamenco/manager/model/submitted_job.py
generated
4
addon/flamenco/manager/model/submitted_job.py
generated
@ -99,6 +99,7 @@ class SubmittedJob(ModelNormal):
|
||||
'settings': (JobSettings,), # noqa: E501
|
||||
'metadata': (JobMetadata,), # noqa: E501
|
||||
'storage': (JobStorageInfo,), # noqa: E501
|
||||
'worker_cluster': (str,), # noqa: E501
|
||||
}
|
||||
|
||||
@cached_property
|
||||
@ -115,6 +116,7 @@ class SubmittedJob(ModelNormal):
|
||||
'settings': 'settings', # noqa: E501
|
||||
'metadata': 'metadata', # noqa: E501
|
||||
'storage': 'storage', # noqa: E501
|
||||
'worker_cluster': 'worker_cluster', # noqa: E501
|
||||
}
|
||||
|
||||
read_only_vars = {
|
||||
@ -168,6 +170,7 @@ class SubmittedJob(ModelNormal):
|
||||
settings (JobSettings): [optional] # noqa: E501
|
||||
metadata (JobMetadata): [optional] # noqa: E501
|
||||
storage (JobStorageInfo): [optional] # noqa: E501
|
||||
worker_cluster (str): Worker Cluster that should execute this job. When a cluster ID is given, only Workers in that cluster will be scheduled to work on it. If empty or ommitted, all workers can work on this job. . [optional] # noqa: E501
|
||||
"""
|
||||
|
||||
priority = kwargs.get('priority', 50)
|
||||
@ -264,6 +267,7 @@ class SubmittedJob(ModelNormal):
|
||||
settings (JobSettings): [optional] # noqa: E501
|
||||
metadata (JobMetadata): [optional] # noqa: E501
|
||||
storage (JobStorageInfo): [optional] # noqa: E501
|
||||
worker_cluster (str): Worker Cluster that should execute this job. When a cluster ID is given, only Workers in that cluster will be scheduled to work on it. If empty or ommitted, all workers can work on this job. . [optional] # noqa: E501
|
||||
"""
|
||||
|
||||
priority = kwargs.get('priority', 50)
|
||||
|
6
addon/flamenco/manager/model/worker.py
generated
6
addon/flamenco/manager/model/worker.py
generated
@ -31,11 +31,13 @@ from flamenco.manager.exceptions import ApiAttributeError
|
||||
|
||||
def lazy_import():
|
||||
from flamenco.manager.model.worker_all_of import WorkerAllOf
|
||||
from flamenco.manager.model.worker_cluster import WorkerCluster
|
||||
from flamenco.manager.model.worker_status import WorkerStatus
|
||||
from flamenco.manager.model.worker_status_change_request import WorkerStatusChangeRequest
|
||||
from flamenco.manager.model.worker_summary import WorkerSummary
|
||||
from flamenco.manager.model.worker_task import WorkerTask
|
||||
globals()['WorkerAllOf'] = WorkerAllOf
|
||||
globals()['WorkerCluster'] = WorkerCluster
|
||||
globals()['WorkerStatus'] = WorkerStatus
|
||||
globals()['WorkerStatusChangeRequest'] = WorkerStatusChangeRequest
|
||||
globals()['WorkerSummary'] = WorkerSummary
|
||||
@ -105,6 +107,7 @@ class Worker(ModelComposed):
|
||||
'status_change': (WorkerStatusChangeRequest,), # noqa: E501
|
||||
'last_seen': (datetime,), # noqa: E501
|
||||
'task': (WorkerTask,), # noqa: E501
|
||||
'clusters': ([WorkerCluster],), # noqa: E501
|
||||
}
|
||||
|
||||
@cached_property
|
||||
@ -123,6 +126,7 @@ class Worker(ModelComposed):
|
||||
'status_change': 'status_change', # noqa: E501
|
||||
'last_seen': 'last_seen', # noqa: E501
|
||||
'task': 'task', # noqa: E501
|
||||
'clusters': 'clusters', # noqa: E501
|
||||
}
|
||||
|
||||
read_only_vars = {
|
||||
@ -174,6 +178,7 @@ class Worker(ModelComposed):
|
||||
status_change (WorkerStatusChangeRequest): [optional] # noqa: E501
|
||||
last_seen (datetime): Last time this worker was seen by the Manager.. [optional] # noqa: E501
|
||||
task (WorkerTask): [optional] # noqa: E501
|
||||
clusters ([WorkerCluster]): Clusters of which this Worker is a member.. [optional] # noqa: E501
|
||||
"""
|
||||
|
||||
_check_type = kwargs.pop('_check_type', True)
|
||||
@ -283,6 +288,7 @@ class Worker(ModelComposed):
|
||||
status_change (WorkerStatusChangeRequest): [optional] # noqa: E501
|
||||
last_seen (datetime): Last time this worker was seen by the Manager.. [optional] # noqa: E501
|
||||
task (WorkerTask): [optional] # noqa: E501
|
||||
clusters ([WorkerCluster]): Clusters of which this Worker is a member.. [optional] # noqa: E501
|
||||
"""
|
||||
|
||||
_check_type = kwargs.pop('_check_type', True)
|
||||
|
6
addon/flamenco/manager/model/worker_all_of.py
generated
6
addon/flamenco/manager/model/worker_all_of.py
generated
@ -30,7 +30,9 @@ from flamenco.manager.exceptions import ApiAttributeError
|
||||
|
||||
|
||||
def lazy_import():
|
||||
from flamenco.manager.model.worker_cluster import WorkerCluster
|
||||
from flamenco.manager.model.worker_task import WorkerTask
|
||||
globals()['WorkerCluster'] = WorkerCluster
|
||||
globals()['WorkerTask'] = WorkerTask
|
||||
|
||||
|
||||
@ -91,6 +93,7 @@ class WorkerAllOf(ModelNormal):
|
||||
'platform': (str,), # noqa: E501
|
||||
'supported_task_types': ([str],), # noqa: E501
|
||||
'task': (WorkerTask,), # noqa: E501
|
||||
'clusters': ([WorkerCluster],), # noqa: E501
|
||||
}
|
||||
|
||||
@cached_property
|
||||
@ -103,6 +106,7 @@ class WorkerAllOf(ModelNormal):
|
||||
'platform': 'platform', # noqa: E501
|
||||
'supported_task_types': 'supported_task_types', # noqa: E501
|
||||
'task': 'task', # noqa: E501
|
||||
'clusters': 'clusters', # noqa: E501
|
||||
}
|
||||
|
||||
read_only_vars = {
|
||||
@ -152,6 +156,7 @@ class WorkerAllOf(ModelNormal):
|
||||
through its discriminator because we passed in
|
||||
_visited_composed_classes = (Animal,)
|
||||
task (WorkerTask): [optional] # noqa: E501
|
||||
clusters ([WorkerCluster]): Clusters of which this Worker is a member.. [optional] # noqa: E501
|
||||
"""
|
||||
|
||||
_check_type = kwargs.pop('_check_type', True)
|
||||
@ -242,6 +247,7 @@ class WorkerAllOf(ModelNormal):
|
||||
through its discriminator because we passed in
|
||||
_visited_composed_classes = (Animal,)
|
||||
task (WorkerTask): [optional] # noqa: E501
|
||||
clusters ([WorkerCluster]): Clusters of which this Worker is a member.. [optional] # noqa: E501
|
||||
"""
|
||||
|
||||
_check_type = kwargs.pop('_check_type', True)
|
||||
|
269
addon/flamenco/manager/model/worker_cluster.py
generated
Normal file
269
addon/flamenco/manager/model/worker_cluster.py
generated
Normal file
@ -0,0 +1,269 @@
|
||||
"""
|
||||
Flamenco manager
|
||||
|
||||
Render Farm manager API # noqa: E501
|
||||
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by: https://openapi-generator.tech
|
||||
"""
|
||||
|
||||
|
||||
import re # noqa: F401
|
||||
import sys # noqa: F401
|
||||
|
||||
from flamenco.manager.model_utils import ( # noqa: F401
|
||||
ApiTypeError,
|
||||
ModelComposed,
|
||||
ModelNormal,
|
||||
ModelSimple,
|
||||
cached_property,
|
||||
change_keys_js_to_python,
|
||||
convert_js_args_to_python_args,
|
||||
date,
|
||||
datetime,
|
||||
file_type,
|
||||
none_type,
|
||||
validate_get_composed_info,
|
||||
OpenApiModel
|
||||
)
|
||||
from flamenco.manager.exceptions import ApiAttributeError
|
||||
|
||||
|
||||
|
||||
class WorkerCluster(ModelNormal):
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator.
|
||||
Ref: https://openapi-generator.tech
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
Attributes:
|
||||
allowed_values (dict): The key is the tuple path to the attribute
|
||||
and the for var_name this is (var_name,). The value is a dict
|
||||
with a capitalized key describing the allowed value and an allowed
|
||||
value. These dicts store the allowed enum values.
|
||||
attribute_map (dict): The key is attribute name
|
||||
and the value is json key in definition.
|
||||
discriminator_value_class_map (dict): A dict to go from the discriminator
|
||||
variable value to the discriminator class name.
|
||||
validations (dict): The key is the tuple path to the attribute
|
||||
and the for var_name this is (var_name,). The value is a dict
|
||||
that stores validations for max_length, min_length, max_items,
|
||||
min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum,
|
||||
inclusive_minimum, and regex.
|
||||
additional_properties_type (tuple): A tuple of classes accepted
|
||||
as additional properties values.
|
||||
"""
|
||||
|
||||
allowed_values = {
|
||||
}
|
||||
|
||||
validations = {
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def additional_properties_type():
|
||||
"""
|
||||
This must be a method because a model may have properties that are
|
||||
of type self, this must run after the class is loaded
|
||||
"""
|
||||
return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501
|
||||
|
||||
_nullable = False
|
||||
|
||||
@cached_property
|
||||
def openapi_types():
|
||||
"""
|
||||
This must be a method because a model may have properties that are
|
||||
of type self, this must run after the class is loaded
|
||||
|
||||
Returns
|
||||
openapi_types (dict): The key is attribute name
|
||||
and the value is attribute type.
|
||||
"""
|
||||
return {
|
||||
'name': (str,), # noqa: E501
|
||||
'id': (str,), # noqa: E501
|
||||
'description': (str,), # noqa: E501
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def discriminator():
|
||||
return None
|
||||
|
||||
|
||||
attribute_map = {
|
||||
'name': 'name', # noqa: E501
|
||||
'id': 'id', # noqa: E501
|
||||
'description': 'description', # noqa: E501
|
||||
}
|
||||
|
||||
read_only_vars = {
|
||||
}
|
||||
|
||||
_composed_schemas = {}
|
||||
|
||||
@classmethod
|
||||
@convert_js_args_to_python_args
|
||||
def _from_openapi_data(cls, name, *args, **kwargs): # noqa: E501
|
||||
"""WorkerCluster - a model defined in OpenAPI
|
||||
|
||||
Args:
|
||||
name (str):
|
||||
|
||||
Keyword Args:
|
||||
_check_type (bool): if True, values for parameters in openapi_types
|
||||
will be type checked and a TypeError will be
|
||||
raised if the wrong type is input.
|
||||
Defaults to True
|
||||
_path_to_item (tuple/list): This is a list of keys or values to
|
||||
drill down to the model in received_data
|
||||
when deserializing a response
|
||||
_spec_property_naming (bool): True if the variable names in the input data
|
||||
are serialized names, as specified in the OpenAPI document.
|
||||
False if the variable names in the input data
|
||||
are pythonic names, e.g. snake case (default)
|
||||
_configuration (Configuration): the instance to use when
|
||||
deserializing a file_type parameter.
|
||||
If passed, type conversion is attempted
|
||||
If omitted no type conversion is done.
|
||||
_visited_composed_classes (tuple): This stores a tuple of
|
||||
classes that we have traveled through so that
|
||||
if we see that class again we will not use its
|
||||
discriminator again.
|
||||
When traveling through a discriminator, the
|
||||
composed schema that is
|
||||
is traveled through is added to this set.
|
||||
For example if Animal has a discriminator
|
||||
petType and we pass in "Dog", and the class Dog
|
||||
allOf includes Animal, we move through Animal
|
||||
once using the discriminator, and pick Dog.
|
||||
Then in Dog, we will make an instance of the
|
||||
Animal class but this time we won't travel
|
||||
through its discriminator because we passed in
|
||||
_visited_composed_classes = (Animal,)
|
||||
id (str): UUID of the cluster. Can be ommitted when creating a new cluster, in which case a random UUID will be assigned. . [optional] # noqa: E501
|
||||
description (str): [optional] # noqa: E501
|
||||
"""
|
||||
|
||||
_check_type = kwargs.pop('_check_type', True)
|
||||
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
|
||||
_path_to_item = kwargs.pop('_path_to_item', ())
|
||||
_configuration = kwargs.pop('_configuration', None)
|
||||
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
|
||||
|
||||
self = super(OpenApiModel, cls).__new__(cls)
|
||||
|
||||
if args:
|
||||
raise ApiTypeError(
|
||||
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
|
||||
args,
|
||||
self.__class__.__name__,
|
||||
),
|
||||
path_to_item=_path_to_item,
|
||||
valid_classes=(self.__class__,),
|
||||
)
|
||||
|
||||
self._data_store = {}
|
||||
self._check_type = _check_type
|
||||
self._spec_property_naming = _spec_property_naming
|
||||
self._path_to_item = _path_to_item
|
||||
self._configuration = _configuration
|
||||
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
|
||||
|
||||
self.name = name
|
||||
for var_name, var_value in kwargs.items():
|
||||
if var_name not in self.attribute_map and \
|
||||
self._configuration is not None and \
|
||||
self._configuration.discard_unknown_keys and \
|
||||
self.additional_properties_type is None:
|
||||
# discard variable.
|
||||
continue
|
||||
setattr(self, var_name, var_value)
|
||||
return self
|
||||
|
||||
required_properties = set([
|
||||
'_data_store',
|
||||
'_check_type',
|
||||
'_spec_property_naming',
|
||||
'_path_to_item',
|
||||
'_configuration',
|
||||
'_visited_composed_classes',
|
||||
])
|
||||
|
||||
@convert_js_args_to_python_args
|
||||
def __init__(self, name, *args, **kwargs): # noqa: E501
|
||||
"""WorkerCluster - a model defined in OpenAPI
|
||||
|
||||
Args:
|
||||
name (str):
|
||||
|
||||
Keyword Args:
|
||||
_check_type (bool): if True, values for parameters in openapi_types
|
||||
will be type checked and a TypeError will be
|
||||
raised if the wrong type is input.
|
||||
Defaults to True
|
||||
_path_to_item (tuple/list): This is a list of keys or values to
|
||||
drill down to the model in received_data
|
||||
when deserializing a response
|
||||
_spec_property_naming (bool): True if the variable names in the input data
|
||||
are serialized names, as specified in the OpenAPI document.
|
||||
False if the variable names in the input data
|
||||
are pythonic names, e.g. snake case (default)
|
||||
_configuration (Configuration): the instance to use when
|
||||
deserializing a file_type parameter.
|
||||
If passed, type conversion is attempted
|
||||
If omitted no type conversion is done.
|
||||
_visited_composed_classes (tuple): This stores a tuple of
|
||||
classes that we have traveled through so that
|
||||
if we see that class again we will not use its
|
||||
discriminator again.
|
||||
When traveling through a discriminator, the
|
||||
composed schema that is
|
||||
is traveled through is added to this set.
|
||||
For example if Animal has a discriminator
|
||||
petType and we pass in "Dog", and the class Dog
|
||||
allOf includes Animal, we move through Animal
|
||||
once using the discriminator, and pick Dog.
|
||||
Then in Dog, we will make an instance of the
|
||||
Animal class but this time we won't travel
|
||||
through its discriminator because we passed in
|
||||
_visited_composed_classes = (Animal,)
|
||||
id (str): UUID of the cluster. Can be ommitted when creating a new cluster, in which case a random UUID will be assigned. . [optional] # noqa: E501
|
||||
description (str): [optional] # noqa: E501
|
||||
"""
|
||||
|
||||
_check_type = kwargs.pop('_check_type', True)
|
||||
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
|
||||
_path_to_item = kwargs.pop('_path_to_item', ())
|
||||
_configuration = kwargs.pop('_configuration', None)
|
||||
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
|
||||
|
||||
if args:
|
||||
raise ApiTypeError(
|
||||
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
|
||||
args,
|
||||
self.__class__.__name__,
|
||||
),
|
||||
path_to_item=_path_to_item,
|
||||
valid_classes=(self.__class__,),
|
||||
)
|
||||
|
||||
self._data_store = {}
|
||||
self._check_type = _check_type
|
||||
self._spec_property_naming = _spec_property_naming
|
||||
self._path_to_item = _path_to_item
|
||||
self._configuration = _configuration
|
||||
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
|
||||
|
||||
self.name = name
|
||||
for var_name, var_value in kwargs.items():
|
||||
if var_name not in self.attribute_map and \
|
||||
self._configuration is not None and \
|
||||
self._configuration.discard_unknown_keys and \
|
||||
self.additional_properties_type is None:
|
||||
# discard variable.
|
||||
continue
|
||||
setattr(self, var_name, var_value)
|
||||
if var_name in self.read_only_vars:
|
||||
raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
|
||||
f"class with read only attributes.")
|
261
addon/flamenco/manager/model/worker_cluster_change_request.py
generated
Normal file
261
addon/flamenco/manager/model/worker_cluster_change_request.py
generated
Normal file
@ -0,0 +1,261 @@
|
||||
"""
|
||||
Flamenco manager
|
||||
|
||||
Render Farm manager API # noqa: E501
|
||||
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by: https://openapi-generator.tech
|
||||
"""
|
||||
|
||||
|
||||
import re # noqa: F401
|
||||
import sys # noqa: F401
|
||||
|
||||
from flamenco.manager.model_utils import ( # noqa: F401
|
||||
ApiTypeError,
|
||||
ModelComposed,
|
||||
ModelNormal,
|
||||
ModelSimple,
|
||||
cached_property,
|
||||
change_keys_js_to_python,
|
||||
convert_js_args_to_python_args,
|
||||
date,
|
||||
datetime,
|
||||
file_type,
|
||||
none_type,
|
||||
validate_get_composed_info,
|
||||
OpenApiModel
|
||||
)
|
||||
from flamenco.manager.exceptions import ApiAttributeError
|
||||
|
||||
|
||||
|
||||
class WorkerClusterChangeRequest(ModelNormal):
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator.
|
||||
Ref: https://openapi-generator.tech
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
Attributes:
|
||||
allowed_values (dict): The key is the tuple path to the attribute
|
||||
and the for var_name this is (var_name,). The value is a dict
|
||||
with a capitalized key describing the allowed value and an allowed
|
||||
value. These dicts store the allowed enum values.
|
||||
attribute_map (dict): The key is attribute name
|
||||
and the value is json key in definition.
|
||||
discriminator_value_class_map (dict): A dict to go from the discriminator
|
||||
variable value to the discriminator class name.
|
||||
validations (dict): The key is the tuple path to the attribute
|
||||
and the for var_name this is (var_name,). The value is a dict
|
||||
that stores validations for max_length, min_length, max_items,
|
||||
min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum,
|
||||
inclusive_minimum, and regex.
|
||||
additional_properties_type (tuple): A tuple of classes accepted
|
||||
as additional properties values.
|
||||
"""
|
||||
|
||||
allowed_values = {
|
||||
}
|
||||
|
||||
validations = {
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def additional_properties_type():
|
||||
"""
|
||||
This must be a method because a model may have properties that are
|
||||
of type self, this must run after the class is loaded
|
||||
"""
|
||||
return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501
|
||||
|
||||
_nullable = False
|
||||
|
||||
@cached_property
|
||||
def openapi_types():
|
||||
"""
|
||||
This must be a method because a model may have properties that are
|
||||
of type self, this must run after the class is loaded
|
||||
|
||||
Returns
|
||||
openapi_types (dict): The key is attribute name
|
||||
and the value is attribute type.
|
||||
"""
|
||||
return {
|
||||
'cluster_ids': ([str],), # noqa: E501
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def discriminator():
|
||||
return None
|
||||
|
||||
|
||||
attribute_map = {
|
||||
'cluster_ids': 'cluster_ids', # noqa: E501
|
||||
}
|
||||
|
||||
read_only_vars = {
|
||||
}
|
||||
|
||||
_composed_schemas = {}
|
||||
|
||||
@classmethod
|
||||
@convert_js_args_to_python_args
|
||||
def _from_openapi_data(cls, cluster_ids, *args, **kwargs): # noqa: E501
|
||||
"""WorkerClusterChangeRequest - a model defined in OpenAPI
|
||||
|
||||
Args:
|
||||
cluster_ids ([str]):
|
||||
|
||||
Keyword Args:
|
||||
_check_type (bool): if True, values for parameters in openapi_types
|
||||
will be type checked and a TypeError will be
|
||||
raised if the wrong type is input.
|
||||
Defaults to True
|
||||
_path_to_item (tuple/list): This is a list of keys or values to
|
||||
drill down to the model in received_data
|
||||
when deserializing a response
|
||||
_spec_property_naming (bool): True if the variable names in the input data
|
||||
are serialized names, as specified in the OpenAPI document.
|
||||
False if the variable names in the input data
|
||||
are pythonic names, e.g. snake case (default)
|
||||
_configuration (Configuration): the instance to use when
|
||||
deserializing a file_type parameter.
|
||||
If passed, type conversion is attempted
|
||||
If omitted no type conversion is done.
|
||||
_visited_composed_classes (tuple): This stores a tuple of
|
||||
classes that we have traveled through so that
|
||||
if we see that class again we will not use its
|
||||
discriminator again.
|
||||
When traveling through a discriminator, the
|
||||
composed schema that is
|
||||
is traveled through is added to this set.
|
||||
For example if Animal has a discriminator
|
||||
petType and we pass in "Dog", and the class Dog
|
||||
allOf includes Animal, we move through Animal
|
||||
once using the discriminator, and pick Dog.
|
||||
Then in Dog, we will make an instance of the
|
||||
Animal class but this time we won't travel
|
||||
through its discriminator because we passed in
|
||||
_visited_composed_classes = (Animal,)
|
||||
"""
|
||||
|
||||
_check_type = kwargs.pop('_check_type', True)
|
||||
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
|
||||
_path_to_item = kwargs.pop('_path_to_item', ())
|
||||
_configuration = kwargs.pop('_configuration', None)
|
||||
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
|
||||
|
||||
self = super(OpenApiModel, cls).__new__(cls)
|
||||
|
||||
if args:
|
||||
raise ApiTypeError(
|
||||
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
|
||||
args,
|
||||
self.__class__.__name__,
|
||||
),
|
||||
path_to_item=_path_to_item,
|
||||
valid_classes=(self.__class__,),
|
||||
)
|
||||
|
||||
self._data_store = {}
|
||||
self._check_type = _check_type
|
||||
self._spec_property_naming = _spec_property_naming
|
||||
self._path_to_item = _path_to_item
|
||||
self._configuration = _configuration
|
||||
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
|
||||
|
||||
self.cluster_ids = cluster_ids
|
||||
for var_name, var_value in kwargs.items():
|
||||
if var_name not in self.attribute_map and \
|
||||
self._configuration is not None and \
|
||||
self._configuration.discard_unknown_keys and \
|
||||
self.additional_properties_type is None:
|
||||
# discard variable.
|
||||
continue
|
||||
setattr(self, var_name, var_value)
|
||||
return self
|
||||
|
||||
required_properties = set([
|
||||
'_data_store',
|
||||
'_check_type',
|
||||
'_spec_property_naming',
|
||||
'_path_to_item',
|
||||
'_configuration',
|
||||
'_visited_composed_classes',
|
||||
])
|
||||
|
||||
@convert_js_args_to_python_args
|
||||
def __init__(self, cluster_ids, *args, **kwargs): # noqa: E501
|
||||
"""WorkerClusterChangeRequest - a model defined in OpenAPI
|
||||
|
||||
Args:
|
||||
cluster_ids ([str]):
|
||||
|
||||
Keyword Args:
|
||||
_check_type (bool): if True, values for parameters in openapi_types
|
||||
will be type checked and a TypeError will be
|
||||
raised if the wrong type is input.
|
||||
Defaults to True
|
||||
_path_to_item (tuple/list): This is a list of keys or values to
|
||||
drill down to the model in received_data
|
||||
when deserializing a response
|
||||
_spec_property_naming (bool): True if the variable names in the input data
|
||||
are serialized names, as specified in the OpenAPI document.
|
||||
False if the variable names in the input data
|
||||
are pythonic names, e.g. snake case (default)
|
||||
_configuration (Configuration): the instance to use when
|
||||
deserializing a file_type parameter.
|
||||
If passed, type conversion is attempted
|
||||
If omitted no type conversion is done.
|
||||
_visited_composed_classes (tuple): This stores a tuple of
|
||||
classes that we have traveled through so that
|
||||
if we see that class again we will not use its
|
||||
discriminator again.
|
||||
When traveling through a discriminator, the
|
||||
composed schema that is
|
||||
is traveled through is added to this set.
|
||||
For example if Animal has a discriminator
|
||||
petType and we pass in "Dog", and the class Dog
|
||||
allOf includes Animal, we move through Animal
|
||||
once using the discriminator, and pick Dog.
|
||||
Then in Dog, we will make an instance of the
|
||||
Animal class but this time we won't travel
|
||||
through its discriminator because we passed in
|
||||
_visited_composed_classes = (Animal,)
|
||||
"""
|
||||
|
||||
_check_type = kwargs.pop('_check_type', True)
|
||||
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
|
||||
_path_to_item = kwargs.pop('_path_to_item', ())
|
||||
_configuration = kwargs.pop('_configuration', None)
|
||||
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
|
||||
|
||||
if args:
|
||||
raise ApiTypeError(
|
||||
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
|
||||
args,
|
||||
self.__class__.__name__,
|
||||
),
|
||||
path_to_item=_path_to_item,
|
||||
valid_classes=(self.__class__,),
|
||||
)
|
||||
|
||||
self._data_store = {}
|
||||
self._check_type = _check_type
|
||||
self._spec_property_naming = _spec_property_naming
|
||||
self._path_to_item = _path_to_item
|
||||
self._configuration = _configuration
|
||||
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
|
||||
|
||||
self.cluster_ids = cluster_ids
|
||||
for var_name, var_value in kwargs.items():
|
||||
if var_name not in self.attribute_map and \
|
||||
self._configuration is not None and \
|
||||
self._configuration.discard_unknown_keys and \
|
||||
self.additional_properties_type is None:
|
||||
# discard variable.
|
||||
continue
|
||||
setattr(self, var_name, var_value)
|
||||
if var_name in self.read_only_vars:
|
||||
raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
|
||||
f"class with read only attributes.")
|
261
addon/flamenco/manager/model/worker_cluster_list.py
generated
Normal file
261
addon/flamenco/manager/model/worker_cluster_list.py
generated
Normal file
@ -0,0 +1,261 @@
|
||||
"""
|
||||
Flamenco manager
|
||||
|
||||
Render Farm manager API # noqa: E501
|
||||
|
||||
The version of the OpenAPI document: 1.0.0
|
||||
Generated by: https://openapi-generator.tech
|
||||
"""
|
||||
|
||||
|
||||
import re # noqa: F401
|
||||
import sys # noqa: F401
|
||||
|
||||
from flamenco.manager.model_utils import ( # noqa: F401
|
||||
ApiTypeError,
|
||||
ModelComposed,
|
||||
ModelNormal,
|
||||
ModelSimple,
|
||||
cached_property,
|
||||
change_keys_js_to_python,
|
||||
convert_js_args_to_python_args,
|
||||
date,
|
||||
datetime,
|
||||
file_type,
|
||||
none_type,
|
||||
validate_get_composed_info,
|
||||
OpenApiModel
|
||||
)
|
||||
from flamenco.manager.exceptions import ApiAttributeError
|
||||
|
||||
|
||||
def lazy_import():
|
||||
from flamenco.manager.model.worker_cluster import WorkerCluster
|
||||
globals()['WorkerCluster'] = WorkerCluster
|
||||
|
||||
|
||||
class WorkerClusterList(ModelNormal):
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator.
|
||||
Ref: https://openapi-generator.tech
|
||||
|
||||
Do not edit the class manually.
|
||||
|
||||
Attributes:
|
||||
allowed_values (dict): The key is the tuple path to the attribute
|
||||
and the for var_name this is (var_name,). The value is a dict
|
||||
with a capitalized key describing the allowed value and an allowed
|
||||
value. These dicts store the allowed enum values.
|
||||
attribute_map (dict): The key is attribute name
|
||||
and the value is json key in definition.
|
||||
discriminator_value_class_map (dict): A dict to go from the discriminator
|
||||
variable value to the discriminator class name.
|
||||
validations (dict): The key is the tuple path to the attribute
|
||||
and the for var_name this is (var_name,). The value is a dict
|
||||
that stores validations for max_length, min_length, max_items,
|
||||
min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum,
|
||||
inclusive_minimum, and regex.
|
||||
additional_properties_type (tuple): A tuple of classes accepted
|
||||
as additional properties values.
|
||||
"""
|
||||
|
||||
allowed_values = {
|
||||
}
|
||||
|
||||
validations = {
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def additional_properties_type():
|
||||
"""
|
||||
This must be a method because a model may have properties that are
|
||||
of type self, this must run after the class is loaded
|
||||
"""
|
||||
lazy_import()
|
||||
return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501
|
||||
|
||||
_nullable = False
|
||||
|
||||
@cached_property
|
||||
def openapi_types():
|
||||
"""
|
||||
This must be a method because a model may have properties that are
|
||||
of type self, this must run after the class is loaded
|
||||
|
||||
Returns
|
||||
openapi_types (dict): The key is attribute name
|
||||
and the value is attribute type.
|
||||
"""
|
||||
lazy_import()
|
||||
return {
|
||||
'clusters': ([WorkerCluster],), # noqa: E501
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def discriminator():
|
||||
return None
|
||||
|
||||
|
||||
attribute_map = {
|
||||
'clusters': 'clusters', # noqa: E501
|
||||
}
|
||||
|
||||
read_only_vars = {
|
||||
}
|
||||
|
||||
_composed_schemas = {}
|
||||
|
||||
@classmethod
|
||||
@convert_js_args_to_python_args
|
||||
def _from_openapi_data(cls, *args, **kwargs): # noqa: E501
|
||||
"""WorkerClusterList - a model defined in OpenAPI
|
||||
|
||||
Keyword Args:
|
||||
_check_type (bool): if True, values for parameters in openapi_types
|
||||
will be type checked and a TypeError will be
|
||||
raised if the wrong type is input.
|
||||
Defaults to True
|
||||
_path_to_item (tuple/list): This is a list of keys or values to
|
||||
drill down to the model in received_data
|
||||
when deserializing a response
|
||||
_spec_property_naming (bool): True if the variable names in the input data
|
||||
are serialized names, as specified in the OpenAPI document.
|
||||
False if the variable names in the input data
|
||||
are pythonic names, e.g. snake case (default)
|
||||
_configuration (Configuration): the instance to use when
|
||||
deserializing a file_type parameter.
|
||||
If passed, type conversion is attempted
|
||||
If omitted no type conversion is done.
|
||||
_visited_composed_classes (tuple): This stores a tuple of
|
||||
classes that we have traveled through so that
|
||||
if we see that class again we will not use its
|
||||
discriminator again.
|
||||
When traveling through a discriminator, the
|
||||
composed schema that is
|
||||
is traveled through is added to this set.
|
||||
For example if Animal has a discriminator
|
||||
petType and we pass in "Dog", and the class Dog
|
||||
allOf includes Animal, we move through Animal
|
||||
once using the discriminator, and pick Dog.
|
||||
Then in Dog, we will make an instance of the
|
||||
Animal class but this time we won't travel
|
||||
through its discriminator because we passed in
|
||||
_visited_composed_classes = (Animal,)
|
||||
clusters ([WorkerCluster]): [optional] # noqa: E501
|
||||
"""
|
||||
|
||||
_check_type = kwargs.pop('_check_type', True)
|
||||
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
|
||||
_path_to_item = kwargs.pop('_path_to_item', ())
|
||||
_configuration = kwargs.pop('_configuration', None)
|
||||
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
|
||||
|
||||
self = super(OpenApiModel, cls).__new__(cls)
|
||||
|
||||
if args:
|
||||
raise ApiTypeError(
|
||||
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
|
||||
args,
|
||||
self.__class__.__name__,
|
||||
),
|
||||
path_to_item=_path_to_item,
|
||||
valid_classes=(self.__class__,),
|
||||
)
|
||||
|
||||
self._data_store = {}
|
||||
self._check_type = _check_type
|
||||
self._spec_property_naming = _spec_property_naming
|
||||
self._path_to_item = _path_to_item
|
||||
self._configuration = _configuration
|
||||
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
|
||||
|
||||
for var_name, var_value in kwargs.items():
|
||||
if var_name not in self.attribute_map and \
|
||||
self._configuration is not None and \
|
||||
self._configuration.discard_unknown_keys and \
|
||||
self.additional_properties_type is None:
|
||||
# discard variable.
|
||||
continue
|
||||
setattr(self, var_name, var_value)
|
||||
return self
|
||||
|
||||
required_properties = set([
|
||||
'_data_store',
|
||||
'_check_type',
|
||||
'_spec_property_naming',
|
||||
'_path_to_item',
|
||||
'_configuration',
|
||||
'_visited_composed_classes',
|
||||
])
|
||||
|
||||
@convert_js_args_to_python_args
|
||||
def __init__(self, *args, **kwargs): # noqa: E501
|
||||
"""WorkerClusterList - a model defined in OpenAPI
|
||||
|
||||
Keyword Args:
|
||||
_check_type (bool): if True, values for parameters in openapi_types
|
||||
will be type checked and a TypeError will be
|
||||
raised if the wrong type is input.
|
||||
Defaults to True
|
||||
_path_to_item (tuple/list): This is a list of keys or values to
|
||||
drill down to the model in received_data
|
||||
when deserializing a response
|
||||
_spec_property_naming (bool): True if the variable names in the input data
|
||||
are serialized names, as specified in the OpenAPI document.
|
||||
False if the variable names in the input data
|
||||
are pythonic names, e.g. snake case (default)
|
||||
_configuration (Configuration): the instance to use when
|
||||
deserializing a file_type parameter.
|
||||
If passed, type conversion is attempted
|
||||
If omitted no type conversion is done.
|
||||
_visited_composed_classes (tuple): This stores a tuple of
|
||||
classes that we have traveled through so that
|
||||
if we see that class again we will not use its
|
||||
discriminator again.
|
||||
When traveling through a discriminator, the
|
||||
composed schema that is
|
||||
is traveled through is added to this set.
|
||||
For example if Animal has a discriminator
|
||||
petType and we pass in "Dog", and the class Dog
|
||||
allOf includes Animal, we move through Animal
|
||||
once using the discriminator, and pick Dog.
|
||||
Then in Dog, we will make an instance of the
|
||||
Animal class but this time we won't travel
|
||||
through its discriminator because we passed in
|
||||
_visited_composed_classes = (Animal,)
|
||||
clusters ([WorkerCluster]): [optional] # noqa: E501
|
||||
"""
|
||||
|
||||
_check_type = kwargs.pop('_check_type', True)
|
||||
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
|
||||
_path_to_item = kwargs.pop('_path_to_item', ())
|
||||
_configuration = kwargs.pop('_configuration', None)
|
||||
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
|
||||
|
||||
if args:
|
||||
raise ApiTypeError(
|
||||
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
|
||||
args,
|
||||
self.__class__.__name__,
|
||||
),
|
||||
path_to_item=_path_to_item,
|
||||
valid_classes=(self.__class__,),
|
||||
)
|
||||
|
||||
self._data_store = {}
|
||||
self._check_type = _check_type
|
||||
self._spec_property_naming = _spec_property_naming
|
||||
self._path_to_item = _path_to_item
|
||||
self._configuration = _configuration
|
||||
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
|
||||
|
||||
for var_name, var_value in kwargs.items():
|
||||
if var_name not in self.attribute_map and \
|
||||
self._configuration is not None and \
|
||||
self._configuration.discard_unknown_keys and \
|
||||
self.additional_properties_type is None:
|
||||
# discard variable.
|
||||
continue
|
||||
setattr(self, var_name, var_value)
|
||||
if var_name in self.read_only_vars:
|
||||
raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
|
||||
f"class with read only attributes.")
|
3
addon/flamenco/manager/models/__init__.py
generated
3
addon/flamenco/manager/models/__init__.py
generated
@ -76,6 +76,9 @@ from flamenco.manager.model.task_update import TaskUpdate
|
||||
from flamenco.manager.model.task_worker import TaskWorker
|
||||
from flamenco.manager.model.worker import Worker
|
||||
from flamenco.manager.model.worker_all_of import WorkerAllOf
|
||||
from flamenco.manager.model.worker_cluster import WorkerCluster
|
||||
from flamenco.manager.model.worker_cluster_change_request import WorkerClusterChangeRequest
|
||||
from flamenco.manager.model.worker_cluster_list import WorkerClusterList
|
||||
from flamenco.manager.model.worker_list import WorkerList
|
||||
from flamenco.manager.model.worker_registration import WorkerRegistration
|
||||
from flamenco.manager.model.worker_sign_on import WorkerSignOn
|
||||
|
11
addon/flamenco/manager_README.md
generated
11
addon/flamenco/manager_README.md
generated
@ -4,7 +4,7 @@ Render Farm manager API
|
||||
The `flamenco.manager` package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 1.0.0
|
||||
- Package version: 3.2
|
||||
- Package version: 3.3-alpha0
|
||||
- Build package: org.openapitools.codegen.languages.PythonClientCodegen
|
||||
For more information, please visit [https://flamenco.io/](https://flamenco.io/)
|
||||
|
||||
@ -117,12 +117,18 @@ Class | Method | HTTP request | Description
|
||||
*WorkerApi* | [**task_update**](flamenco/manager/docs/WorkerApi.md#task_update) | **POST** /api/v3/worker/task/{task_id} | Update the task, typically to indicate progress, completion, or failure.
|
||||
*WorkerApi* | [**worker_state**](flamenco/manager/docs/WorkerApi.md#worker_state) | **GET** /api/v3/worker/state |
|
||||
*WorkerApi* | [**worker_state_changed**](flamenco/manager/docs/WorkerApi.md#worker_state_changed) | **POST** /api/v3/worker/state-changed | Worker changed state. This could be as acknowledgement of a Manager-requested state change, or in response to worker-local signals.
|
||||
*WorkerMgtApi* | [**create_worker_cluster**](flamenco/manager/docs/WorkerMgtApi.md#create_worker_cluster) | **POST** /api/v3/worker-mgt/clusters | Create a new worker cluster.
|
||||
*WorkerMgtApi* | [**delete_worker**](flamenco/manager/docs/WorkerMgtApi.md#delete_worker) | **DELETE** /api/v3/worker-mgt/workers/{worker_id} | Remove the given worker. It is recommended to only call this function when the worker is in `offline` state. If the worker is still running, stop it first. Any task still assigned to the worker will be requeued.
|
||||
*WorkerMgtApi* | [**delete_worker_cluster**](flamenco/manager/docs/WorkerMgtApi.md#delete_worker_cluster) | **DELETE** /api/v3/worker-mgt/cluster/{cluster_id} | Remove this worker cluster. This unassigns all workers from the cluster and removes it.
|
||||
*WorkerMgtApi* | [**fetch_worker**](flamenco/manager/docs/WorkerMgtApi.md#fetch_worker) | **GET** /api/v3/worker-mgt/workers/{worker_id} | Fetch info about the worker.
|
||||
*WorkerMgtApi* | [**fetch_worker_cluster**](flamenco/manager/docs/WorkerMgtApi.md#fetch_worker_cluster) | **GET** /api/v3/worker-mgt/cluster/{cluster_id} | Get a single worker cluster.
|
||||
*WorkerMgtApi* | [**fetch_worker_clusters**](flamenco/manager/docs/WorkerMgtApi.md#fetch_worker_clusters) | **GET** /api/v3/worker-mgt/clusters | Get list of worker clusters.
|
||||
*WorkerMgtApi* | [**fetch_worker_sleep_schedule**](flamenco/manager/docs/WorkerMgtApi.md#fetch_worker_sleep_schedule) | **GET** /api/v3/worker-mgt/workers/{worker_id}/sleep-schedule |
|
||||
*WorkerMgtApi* | [**fetch_workers**](flamenco/manager/docs/WorkerMgtApi.md#fetch_workers) | **GET** /api/v3/worker-mgt/workers | Get list of workers.
|
||||
*WorkerMgtApi* | [**request_worker_status_change**](flamenco/manager/docs/WorkerMgtApi.md#request_worker_status_change) | **POST** /api/v3/worker-mgt/workers/{worker_id}/setstatus |
|
||||
*WorkerMgtApi* | [**set_worker_clusters**](flamenco/manager/docs/WorkerMgtApi.md#set_worker_clusters) | **POST** /api/v3/worker-mgt/workers/{worker_id}/setclusters |
|
||||
*WorkerMgtApi* | [**set_worker_sleep_schedule**](flamenco/manager/docs/WorkerMgtApi.md#set_worker_sleep_schedule) | **POST** /api/v3/worker-mgt/workers/{worker_id}/sleep-schedule |
|
||||
*WorkerMgtApi* | [**update_worker_cluster**](flamenco/manager/docs/WorkerMgtApi.md#update_worker_cluster) | **PUT** /api/v3/worker-mgt/cluster/{cluster_id} | Update an existing worker cluster.
|
||||
|
||||
|
||||
## Documentation For Models
|
||||
@ -194,6 +200,9 @@ Class | Method | HTTP request | Description
|
||||
- [TaskWorker](flamenco/manager/docs/TaskWorker.md)
|
||||
- [Worker](flamenco/manager/docs/Worker.md)
|
||||
- [WorkerAllOf](flamenco/manager/docs/WorkerAllOf.md)
|
||||
- [WorkerCluster](flamenco/manager/docs/WorkerCluster.md)
|
||||
- [WorkerClusterChangeRequest](flamenco/manager/docs/WorkerClusterChangeRequest.md)
|
||||
- [WorkerClusterList](flamenco/manager/docs/WorkerClusterList.md)
|
||||
- [WorkerList](flamenco/manager/docs/WorkerList.md)
|
||||
- [WorkerRegistration](flamenco/manager/docs/WorkerRegistration.md)
|
||||
- [WorkerSignOn](flamenco/manager/docs/WorkerSignOn.md)
|
||||
|
@ -10,7 +10,7 @@ from urllib3.exceptions import HTTPError, MaxRetryError
|
||||
|
||||
import bpy
|
||||
|
||||
from . import job_types, job_submission, preferences
|
||||
from . import job_types, job_submission, preferences, worker_clusters
|
||||
from .job_types_propgroup import JobTypePropertyGroup
|
||||
from .bat.submodules import bpathlib
|
||||
|
||||
@ -83,6 +83,37 @@ class FLAMENCO_OT_fetch_job_types(FlamencoOpMixin, bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class FLAMENCO_OT_fetch_worker_clusters(FlamencoOpMixin, bpy.types.Operator):
|
||||
bl_idname = "flamenco.fetch_worker_clusters"
|
||||
bl_label = "Fetch Worker Clusters"
|
||||
bl_description = "Query Flamenco Manager to obtain the available worker clusters"
|
||||
|
||||
def execute(self, context: bpy.types.Context) -> set[str]:
|
||||
api_client = self.get_api_client(context)
|
||||
|
||||
from flamenco.manager import ApiException
|
||||
|
||||
scene = context.scene
|
||||
old_cluster = getattr(scene, "flamenco_worker_cluster", "")
|
||||
|
||||
try:
|
||||
worker_clusters.refresh(context, api_client)
|
||||
except ApiException as ex:
|
||||
self.report({"ERROR"}, "Error getting job types: %s" % ex)
|
||||
return {"CANCELLED"}
|
||||
except MaxRetryError as ex:
|
||||
# This is the common error, when for example the port number is
|
||||
# incorrect and nothing is listening.
|
||||
self.report({"ERROR"}, "Unable to reach Manager")
|
||||
return {"CANCELLED"}
|
||||
|
||||
if old_cluster:
|
||||
# TODO: handle cases where the old cluster no longer exists.
|
||||
scene.flamenco_worker_cluster = old_cluster
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class FLAMENCO_OT_ping_manager(FlamencoOpMixin, bpy.types.Operator):
|
||||
bl_idname = "flamenco.ping_manager"
|
||||
bl_label = "Flamenco: Ping Manager"
|
||||
@ -165,7 +196,9 @@ class FLAMENCO_OT_submit_job(FlamencoOpMixin, bpy.types.Operator):
|
||||
|
||||
if not context.blend_data.filepath:
|
||||
# The file path needs to be known before the file can be submitted.
|
||||
self.report({"ERROR"}, "Please save your .blend file before submitting to Flamenco")
|
||||
self.report(
|
||||
{"ERROR"}, "Please save your .blend file before submitting to Flamenco"
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
filepath = self._save_blendfile(context)
|
||||
@ -633,6 +666,7 @@ class FLAMENCO3_OT_explore_file_path(bpy.types.Operator):
|
||||
|
||||
classes = (
|
||||
FLAMENCO_OT_fetch_job_types,
|
||||
FLAMENCO_OT_fetch_worker_clusters,
|
||||
FLAMENCO_OT_ping_manager,
|
||||
FLAMENCO_OT_eval_setting,
|
||||
FLAMENCO_OT_submit_job,
|
||||
|
@ -34,6 +34,12 @@ def _manager_url_updated(prefs, context):
|
||||
comms.ping_manager_with_report(context.window_manager, api_client, prefs)
|
||||
|
||||
|
||||
class WorkerCluster(bpy.types.PropertyGroup):
|
||||
id: bpy.props.StringProperty(name="id")
|
||||
name: bpy.props.StringProperty(name="Name")
|
||||
description: bpy.props.StringProperty(name="Description")
|
||||
|
||||
|
||||
class FlamencoPreferences(bpy.types.AddonPreferences):
|
||||
bl_idname = "flamenco"
|
||||
|
||||
@ -71,6 +77,13 @@ class FlamencoPreferences(bpy.types.AddonPreferences):
|
||||
get=lambda prefs: prefs.job_storage,
|
||||
)
|
||||
|
||||
worker_clusters: bpy.props.CollectionProperty( # type: ignore
|
||||
type=WorkerCluster,
|
||||
name="Worker Clusters",
|
||||
description="Cache for the worker clusters available on the configured Manager",
|
||||
options={"HIDDEN"},
|
||||
)
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
layout = self.layout
|
||||
layout.use_property_decorate = False
|
||||
@ -117,7 +130,10 @@ def manager_url(context: bpy.types.Context) -> str:
|
||||
return str(prefs.manager_url)
|
||||
|
||||
|
||||
classes = (FlamencoPreferences,)
|
||||
classes = (
|
||||
WorkerCluster,
|
||||
FlamencoPreferences,
|
||||
)
|
||||
_register, _unregister = bpy.utils.register_classes_factory(classes)
|
||||
|
||||
|
||||
|
74
addon/flamenco/worker_clusters.py
Normal file
74
addon/flamenco/worker_clusters.py
Normal file
@ -0,0 +1,74 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
import bpy
|
||||
|
||||
from . import preferences
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from flamenco.manager import ApiClient as _ApiClient
|
||||
else:
|
||||
_ApiClient = object
|
||||
|
||||
|
||||
_enum_items: list[Union[tuple[str, str, str], tuple[str, str, str, int, int]]] = []
|
||||
|
||||
|
||||
def refresh(context: bpy.types.Context, api_client: _ApiClient) -> None:
|
||||
"""Fetch the available worker clusters from the Manager."""
|
||||
from flamenco.manager import ApiClient
|
||||
from flamenco.manager.api import worker_mgt_api
|
||||
from flamenco.manager.model.worker_cluster_list import WorkerClusterList
|
||||
|
||||
assert isinstance(api_client, ApiClient)
|
||||
|
||||
api = worker_mgt_api.WorkerMgtApi(api_client)
|
||||
response: WorkerClusterList = api.fetch_worker_clusters()
|
||||
|
||||
# Store on the preferences, so a cached version persists until the next refresh.
|
||||
prefs = preferences.get(context)
|
||||
prefs.worker_clusters.clear()
|
||||
|
||||
for cluster in response.clusters:
|
||||
rna_cluster = prefs.worker_clusters.add()
|
||||
rna_cluster.id = cluster.id
|
||||
rna_cluster.name = cluster.name
|
||||
rna_cluster.description = getattr(cluster, "description", "")
|
||||
|
||||
|
||||
def _get_enum_items(self, context):
|
||||
global _enum_items
|
||||
prefs = preferences.get(context)
|
||||
|
||||
_enum_items = [
|
||||
("-", "All", "No specific cluster assigned, any worker can handle this job"),
|
||||
]
|
||||
_enum_items.extend(
|
||||
(cluster.id, cluster.name, cluster.description)
|
||||
for cluster in prefs.worker_clusters
|
||||
)
|
||||
return _enum_items
|
||||
|
||||
|
||||
def register() -> None:
|
||||
bpy.types.Scene.flamenco_worker_cluster = bpy.props.EnumProperty(
|
||||
name="Worker Cluster",
|
||||
items=_get_enum_items,
|
||||
description="The set of Workers that can handle tasks of this job",
|
||||
)
|
||||
|
||||
|
||||
def unregister() -> None:
|
||||
to_del = ((bpy.types.Scene, "flamenco_worker_cluster"),)
|
||||
for ob, attr in to_del:
|
||||
try:
|
||||
delattr(ob, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
print(doctest.testmod())
|
5
go.mod
5
go.mod
@ -51,11 +51,12 @@ require (
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
golang.org/x/mod v0.7.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||
golang.org/x/tools v0.5.1-0.20230117180257-8aba49bb5ea2 // indirect
|
||||
golang.org/x/tools v0.6.1-0.20230217175706-3102dad5faf9 // indirect
|
||||
golang.org/x/vuln v0.0.0-20230320232729-bfc1eaef17a4 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
modernc.org/libc v1.16.17 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
|
6
go.sum
6
go.sum
@ -185,6 +185,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
@ -254,6 +256,10 @@ golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.5.1-0.20230117180257-8aba49bb5ea2 h1:v0FhRDmSCNH/0EurAT6T8KRY4aNuUhz6/WwBMxG+gvQ=
|
||||
golang.org/x/tools v0.5.1-0.20230117180257-8aba49bb5ea2/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
|
||||
golang.org/x/tools v0.6.1-0.20230217175706-3102dad5faf9 h1:IuFp2CklNBim6OdHXn/1P4VoeKt5pA2jcDKWlboqtlQ=
|
||||
golang.org/x/tools v0.6.1-0.20230217175706-3102dad5faf9/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/vuln v0.0.0-20230320232729-bfc1eaef17a4 h1:E/sS+2T8wsKgQNbdkQFdIFrytP7CK17WA5z0wbVoFgU=
|
||||
golang.org/x/vuln v0.0.0-20230320232729-bfc1eaef17a4/go.mod h1:ydpjOTRSBwOBFJRP/w5NF2HSPnFg1JxobEZQGOirxgo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -10,10 +10,18 @@ import (
|
||||
"github.com/adrg/xdg"
|
||||
)
|
||||
|
||||
// customHome can be set at link time to specify the home directory for the worker.
|
||||
// This can be overruled at runtime by setting the FLAMENCO_HOME enviroment variable.
|
||||
// Only used in InFlamencoHome() function.
|
||||
var customHome = ""
|
||||
|
||||
// InFlamencoHome returns the filename in the 'flamenco home' dir, and ensures
|
||||
// that the directory exists.
|
||||
func InFlamencoHome(filename string) (string, error) {
|
||||
flamencoHome := os.Getenv("FLAMENCO_HOME")
|
||||
flamencoHome := customHome
|
||||
if envHome, ok := os.LookupEnv("FLAMENCO_HOME"); ok {
|
||||
flamencoHome = envHome
|
||||
}
|
||||
if flamencoHome == "" {
|
||||
return xdg.DataFile(path.Join(xdgApplicationName, filename))
|
||||
}
|
||||
|
@ -65,6 +65,14 @@ type PersistenceService interface {
|
||||
RemoveFromJobBlocklist(ctx context.Context, jobUUID, workerUUID, taskType string) error
|
||||
ClearJobBlocklist(ctx context.Context, job *persistence.Job) error
|
||||
|
||||
// Worker cluster management.
|
||||
WorkerSetClusters(ctx context.Context, worker *persistence.Worker, clusterUUIDs []string) error
|
||||
CreateWorkerCluster(ctx context.Context, cluster *persistence.WorkerCluster) error
|
||||
FetchWorkerCluster(ctx context.Context, uuid string) (*persistence.WorkerCluster, error)
|
||||
FetchWorkerClusters(ctx context.Context) ([]*persistence.WorkerCluster, error)
|
||||
DeleteWorkerCluster(ctx context.Context, uuid string) error
|
||||
SaveWorkerCluster(ctx context.Context, cluster *persistence.WorkerCluster) error
|
||||
|
||||
// WorkersLeftToRun returns a set of worker UUIDs that can run tasks of the given type on the given job.
|
||||
WorkersLeftToRun(ctx context.Context, job *persistence.Job, taskType string) (map[string]bool, error)
|
||||
// CountTaskFailuresOfWorker returns the number of task failures of this worker, on this particular job and task type.
|
||||
|
@ -618,6 +618,9 @@ func jobDBtoAPI(dbJob *persistence.Job) api.Job {
|
||||
if dbJob.DeleteRequestedAt.Valid {
|
||||
apiJob.DeleteRequestedAt = &dbJob.DeleteRequestedAt.Time
|
||||
}
|
||||
if dbJob.WorkerCluster != nil {
|
||||
apiJob.WorkerCluster = &dbJob.WorkerCluster.UUID
|
||||
}
|
||||
|
||||
return apiJob
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import (
|
||||
"git.blender.org/flamenco/pkg/moremock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func ptr[T any](value T) *T {
|
||||
@ -319,6 +320,103 @@ func TestSubmitJobWithShamanCheckoutID(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSubmitJobWithWorkerCluster(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mf := newMockedFlamenco(mockCtrl)
|
||||
worker := testWorker()
|
||||
|
||||
workerClusterUUID := "04435762-9dc8-4f13-80b7-643a6fa5b6fd"
|
||||
cluster := persistence.WorkerCluster{
|
||||
Model: persistence.Model{ID: 47},
|
||||
UUID: workerClusterUUID,
|
||||
Name: "first cluster",
|
||||
Description: "my first cluster",
|
||||
}
|
||||
|
||||
submittedJob := api.SubmittedJob{
|
||||
Name: "поднео посао",
|
||||
Type: "test",
|
||||
Priority: 50,
|
||||
SubmitterPlatform: worker.Platform,
|
||||
WorkerCluster: &workerClusterUUID,
|
||||
}
|
||||
|
||||
mf.expectConvertTwoWayVariables(t,
|
||||
config.VariableAudienceWorkers,
|
||||
config.VariablePlatform(worker.Platform),
|
||||
map[string]string{},
|
||||
)
|
||||
|
||||
// Expect the job compiler to be called.
|
||||
authoredJob := job_compilers.AuthoredJob{
|
||||
JobID: "afc47568-bd9d-4368-8016-e91d945db36d",
|
||||
WorkerClusterUUID: workerClusterUUID,
|
||||
|
||||
Name: submittedJob.Name,
|
||||
JobType: submittedJob.Type,
|
||||
Priority: submittedJob.Priority,
|
||||
Status: api.JobStatusUnderConstruction,
|
||||
Created: mf.clock.Now(),
|
||||
}
|
||||
mf.jobCompiler.EXPECT().Compile(gomock.Any(), submittedJob).Return(&authoredJob, nil)
|
||||
|
||||
// Expect the job to be saved with 'queued' status:
|
||||
queuedJob := authoredJob
|
||||
queuedJob.Status = api.JobStatusQueued
|
||||
mf.persistence.EXPECT().StoreAuthoredJob(gomock.Any(), queuedJob).Return(nil)
|
||||
|
||||
// Expect the job to be fetched from the database again:
|
||||
dbJob := persistence.Job{
|
||||
Model: persistence.Model{
|
||||
ID: 47,
|
||||
CreatedAt: mf.clock.Now(),
|
||||
UpdatedAt: mf.clock.Now(),
|
||||
},
|
||||
UUID: queuedJob.JobID,
|
||||
Name: queuedJob.Name,
|
||||
JobType: queuedJob.JobType,
|
||||
Priority: queuedJob.Priority,
|
||||
Status: queuedJob.Status,
|
||||
Settings: persistence.StringInterfaceMap{},
|
||||
Metadata: persistence.StringStringMap{},
|
||||
|
||||
WorkerClusterID: &cluster.ID,
|
||||
WorkerCluster: &cluster,
|
||||
}
|
||||
mf.persistence.EXPECT().FetchJob(gomock.Any(), queuedJob.JobID).Return(&dbJob, nil)
|
||||
|
||||
// Expect the new job to be broadcast.
|
||||
jobUpdate := api.SocketIOJobUpdate{
|
||||
Id: dbJob.UUID,
|
||||
Name: &dbJob.Name,
|
||||
Priority: dbJob.Priority,
|
||||
Status: dbJob.Status,
|
||||
Type: dbJob.JobType,
|
||||
Updated: dbJob.UpdatedAt,
|
||||
}
|
||||
mf.broadcaster.EXPECT().BroadcastNewJob(jobUpdate)
|
||||
|
||||
// Do the call.
|
||||
echoCtx := mf.prepareMockedJSONRequest(submittedJob)
|
||||
requestWorkerStore(echoCtx, &worker)
|
||||
require.NoError(t, mf.flamenco.SubmitJob(echoCtx))
|
||||
|
||||
submittedJob.Metadata = new(api.JobMetadata)
|
||||
submittedJob.Settings = new(api.JobSettings)
|
||||
submittedJob.SubmitterPlatform = "" // Not persisted in the database.
|
||||
assertResponseJSON(t, echoCtx, http.StatusOK, api.Job{
|
||||
SubmittedJob: submittedJob,
|
||||
Id: dbJob.UUID,
|
||||
Created: dbJob.CreatedAt,
|
||||
Updated: dbJob.UpdatedAt,
|
||||
DeleteRequestedAt: nil,
|
||||
Activity: "",
|
||||
Status: api.JobStatusQueued,
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetJobTypeHappy(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
86
internal/manager/api_impl/mocks/api_impl_mock.gen.go
generated
86
internal/manager/api_impl/mocks/api_impl_mock.gen.go
generated
@ -141,6 +141,20 @@ func (mr *MockPersistenceServiceMockRecorder) CreateWorker(arg0, arg1 interface{
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateWorker", reflect.TypeOf((*MockPersistenceService)(nil).CreateWorker), arg0, arg1)
|
||||
}
|
||||
|
||||
// CreateWorkerCluster mocks base method.
|
||||
func (m *MockPersistenceService) CreateWorkerCluster(arg0 context.Context, arg1 *persistence.WorkerCluster) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateWorkerCluster", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateWorkerCluster indicates an expected call of CreateWorkerCluster.
|
||||
func (mr *MockPersistenceServiceMockRecorder) CreateWorkerCluster(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateWorkerCluster", reflect.TypeOf((*MockPersistenceService)(nil).CreateWorkerCluster), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeleteWorker mocks base method.
|
||||
func (m *MockPersistenceService) DeleteWorker(arg0 context.Context, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
@ -155,6 +169,20 @@ func (mr *MockPersistenceServiceMockRecorder) DeleteWorker(arg0, arg1 interface{
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWorker", reflect.TypeOf((*MockPersistenceService)(nil).DeleteWorker), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeleteWorkerCluster mocks base method.
|
||||
func (m *MockPersistenceService) DeleteWorkerCluster(arg0 context.Context, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteWorkerCluster", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteWorkerCluster indicates an expected call of DeleteWorkerCluster.
|
||||
func (mr *MockPersistenceServiceMockRecorder) DeleteWorkerCluster(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWorkerCluster", reflect.TypeOf((*MockPersistenceService)(nil).DeleteWorkerCluster), arg0, arg1)
|
||||
}
|
||||
|
||||
// FetchJob mocks base method.
|
||||
func (m *MockPersistenceService) FetchJob(arg0 context.Context, arg1 string) (*persistence.Job, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -230,6 +258,36 @@ func (mr *MockPersistenceServiceMockRecorder) FetchWorker(arg0, arg1 interface{}
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchWorker", reflect.TypeOf((*MockPersistenceService)(nil).FetchWorker), arg0, arg1)
|
||||
}
|
||||
|
||||
// FetchWorkerCluster mocks base method.
|
||||
func (m *MockPersistenceService) FetchWorkerCluster(arg0 context.Context, arg1 string) (*persistence.WorkerCluster, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FetchWorkerCluster", arg0, arg1)
|
||||
ret0, _ := ret[0].(*persistence.WorkerCluster)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FetchWorkerCluster indicates an expected call of FetchWorkerCluster.
|
||||
func (mr *MockPersistenceServiceMockRecorder) FetchWorkerCluster(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchWorkerCluster", reflect.TypeOf((*MockPersistenceService)(nil).FetchWorkerCluster), arg0, arg1)
|
||||
}
|
||||
|
||||
// FetchWorkerClusters mocks base method.
|
||||
func (m *MockPersistenceService) FetchWorkerClusters(arg0 context.Context) ([]*persistence.WorkerCluster, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "FetchWorkerClusters", arg0)
|
||||
ret0, _ := ret[0].([]*persistence.WorkerCluster)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FetchWorkerClusters indicates an expected call of FetchWorkerClusters.
|
||||
func (mr *MockPersistenceServiceMockRecorder) FetchWorkerClusters(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchWorkerClusters", reflect.TypeOf((*MockPersistenceService)(nil).FetchWorkerClusters), arg0)
|
||||
}
|
||||
|
||||
// FetchWorkerTask mocks base method.
|
||||
func (m *MockPersistenceService) FetchWorkerTask(arg0 context.Context, arg1 *persistence.Worker) (*persistence.Task, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -375,6 +433,20 @@ func (mr *MockPersistenceServiceMockRecorder) SaveWorker(arg0, arg1 interface{})
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveWorker", reflect.TypeOf((*MockPersistenceService)(nil).SaveWorker), arg0, arg1)
|
||||
}
|
||||
|
||||
// SaveWorkerCluster mocks base method.
|
||||
func (m *MockPersistenceService) SaveWorkerCluster(arg0 context.Context, arg1 *persistence.WorkerCluster) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SaveWorkerCluster", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SaveWorkerCluster indicates an expected call of SaveWorkerCluster.
|
||||
func (mr *MockPersistenceServiceMockRecorder) SaveWorkerCluster(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveWorkerCluster", reflect.TypeOf((*MockPersistenceService)(nil).SaveWorkerCluster), arg0, arg1)
|
||||
}
|
||||
|
||||
// SaveWorkerStatus mocks base method.
|
||||
func (m *MockPersistenceService) SaveWorkerStatus(arg0 context.Context, arg1 *persistence.Worker) error {
|
||||
m.ctrl.T.Helper()
|
||||
@ -460,6 +532,20 @@ func (mr *MockPersistenceServiceMockRecorder) WorkerSeen(arg0, arg1 interface{})
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkerSeen", reflect.TypeOf((*MockPersistenceService)(nil).WorkerSeen), arg0, arg1)
|
||||
}
|
||||
|
||||
// WorkerSetClusters mocks base method.
|
||||
func (m *MockPersistenceService) WorkerSetClusters(arg0 context.Context, arg1 *persistence.Worker, arg2 []string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "WorkerSetClusters", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// WorkerSetClusters indicates an expected call of WorkerSetClusters.
|
||||
func (mr *MockPersistenceServiceMockRecorder) WorkerSetClusters(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkerSetClusters", reflect.TypeOf((*MockPersistenceService)(nil).WorkerSetClusters), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// WorkersLeftToRun mocks base method.
|
||||
func (m *MockPersistenceService) WorkersLeftToRun(arg0 context.Context, arg1 *persistence.Job, arg2 string) (map[string]bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -17,7 +17,7 @@ func (f *Flamenco) FetchWorkers(e echo.Context) error {
|
||||
logger := requestLogger(e)
|
||||
dbWorkers, err := f.persist.FetchWorkers(e.Request().Context())
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("error fetching all workers")
|
||||
logger.Error().Err(err).Msg("fetching all workers")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "error fetching workers: %v", err)
|
||||
}
|
||||
|
||||
@ -47,13 +47,13 @@ func (f *Flamenco) FetchWorker(e echo.Context, workerUUID string) error {
|
||||
return sendAPIError(e, http.StatusNotFound, "worker %q not found", workerUUID)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("error fetching worker")
|
||||
logger.Error().Err(err).Msg("fetching worker")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "error fetching worker: %v", err)
|
||||
}
|
||||
|
||||
dbTask, err := f.persist.FetchWorkerTask(ctx, dbWorker)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("error fetching task assigned to worker")
|
||||
logger.Error().Err(err).Msg("fetching task assigned to worker")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "error fetching task assigned to worker: %v", err)
|
||||
}
|
||||
|
||||
@ -91,14 +91,14 @@ func (f *Flamenco) DeleteWorker(e echo.Context, workerUUID string) error {
|
||||
return sendAPIError(e, http.StatusNotFound, "worker %q not found", workerUUID)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("error fetching worker for deletion")
|
||||
logger.Error().Err(err).Msg("fetching worker for deletion")
|
||||
return sendAPIError(e, http.StatusInternalServerError,
|
||||
"error fetching worker for deletion: %v", err)
|
||||
}
|
||||
|
||||
err = f.stateMachine.RequeueActiveTasksOfWorker(ctx, worker, "worker is being deleted")
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("error requeueing tasks before deleting worker")
|
||||
logger.Error().Err(err).Msg("requeueing tasks before deleting worker")
|
||||
return sendAPIError(e, http.StatusInternalServerError,
|
||||
"error requeueing tasks before deleting worker: %v", err)
|
||||
}
|
||||
@ -110,7 +110,7 @@ func (f *Flamenco) DeleteWorker(e echo.Context, workerUUID string) error {
|
||||
return sendAPIError(e, http.StatusNotFound, "worker %q not found", workerUUID)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("error deleting worker")
|
||||
logger.Error().Err(err).Msg("deleting worker")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "error deleting worker: %v", err)
|
||||
}
|
||||
logger.Info().Msg("deleted worker")
|
||||
@ -150,7 +150,7 @@ func (f *Flamenco) RequestWorkerStatusChange(e echo.Context, workerUUID string)
|
||||
return sendAPIError(e, http.StatusNotFound, "worker %q not found", workerUUID)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("error fetching worker")
|
||||
logger.Error().Err(err).Msg("fetching worker")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "error fetching worker: %v", err)
|
||||
}
|
||||
|
||||
@ -171,7 +171,7 @@ func (f *Flamenco) RequestWorkerStatusChange(e echo.Context, workerUUID string)
|
||||
|
||||
// Store the status change.
|
||||
if err := f.persist.SaveWorker(e.Request().Context(), dbWorker); err != nil {
|
||||
logger.Error().Err(err).Msg("error saving worker after status change request")
|
||||
logger.Error().Err(err).Msg("saving worker after status change request")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "error saving worker: %v", err)
|
||||
}
|
||||
|
||||
@ -182,6 +182,202 @@ func (f *Flamenco) RequestWorkerStatusChange(e echo.Context, workerUUID string)
|
||||
return e.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (f *Flamenco) SetWorkerClusters(e echo.Context, workerUUID string) error {
|
||||
ctx := e.Request().Context()
|
||||
logger := requestLogger(e)
|
||||
logger = logger.With().Str("worker", workerUUID).Logger()
|
||||
|
||||
if !uuid.IsValid(workerUUID) {
|
||||
return sendAPIError(e, http.StatusBadRequest, "not a valid UUID")
|
||||
}
|
||||
|
||||
// Decode the request body.
|
||||
var change api.WorkerClusterChangeRequest
|
||||
if err := e.Bind(&change); err != nil {
|
||||
logger.Warn().Err(err).Msg("bad request received")
|
||||
return sendAPIError(e, http.StatusBadRequest, "invalid format")
|
||||
}
|
||||
|
||||
// Fetch the worker.
|
||||
dbWorker, err := f.persist.FetchWorker(ctx, workerUUID)
|
||||
if errors.Is(err, persistence.ErrWorkerNotFound) {
|
||||
logger.Debug().Msg("non-existent worker requested")
|
||||
return sendAPIError(e, http.StatusNotFound, "worker %q not found", workerUUID)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("fetching worker")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "error fetching worker: %v", err)
|
||||
}
|
||||
|
||||
logger = logger.With().
|
||||
Strs("clusters", change.ClusterIds).
|
||||
Logger()
|
||||
logger.Info().Msg("worker cluster change requested")
|
||||
|
||||
// Store the new cluster assignment.
|
||||
if err := f.persist.WorkerSetClusters(ctx, dbWorker, change.ClusterIds); err != nil {
|
||||
logger.Error().Err(err).Msg("saving worker after cluster change request")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "error saving worker: %v", err)
|
||||
}
|
||||
|
||||
// Broadcast the change.
|
||||
update := webupdates.NewWorkerUpdate(dbWorker)
|
||||
f.broadcaster.BroadcastWorkerUpdate(update)
|
||||
|
||||
return e.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (f *Flamenco) DeleteWorkerCluster(e echo.Context, clusterUUID string) error {
|
||||
ctx := e.Request().Context()
|
||||
logger := requestLogger(e)
|
||||
logger = logger.With().Str("cluster", clusterUUID).Logger()
|
||||
|
||||
if !uuid.IsValid(clusterUUID) {
|
||||
return sendAPIError(e, http.StatusBadRequest, "not a valid UUID")
|
||||
}
|
||||
|
||||
err := f.persist.DeleteWorkerCluster(ctx, clusterUUID)
|
||||
switch {
|
||||
case errors.Is(err, persistence.ErrWorkerClusterNotFound):
|
||||
logger.Debug().Msg("non-existent worker cluster requested")
|
||||
return sendAPIError(e, http.StatusNotFound, "worker cluster %q not found", clusterUUID)
|
||||
case err != nil:
|
||||
logger.Error().Err(err).Msg("deleting worker cluster")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "error deleting worker cluster: %v", err)
|
||||
}
|
||||
|
||||
// TODO: SocketIO broadcast of cluster deletion.
|
||||
|
||||
logger.Info().Msg("worker cluster deleted")
|
||||
return e.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (f *Flamenco) FetchWorkerCluster(e echo.Context, clusterUUID string) error {
|
||||
ctx := e.Request().Context()
|
||||
logger := requestLogger(e)
|
||||
logger = logger.With().Str("cluster", clusterUUID).Logger()
|
||||
|
||||
if !uuid.IsValid(clusterUUID) {
|
||||
return sendAPIError(e, http.StatusBadRequest, "not a valid UUID")
|
||||
}
|
||||
|
||||
cluster, err := f.persist.FetchWorkerCluster(ctx, clusterUUID)
|
||||
switch {
|
||||
case errors.Is(err, persistence.ErrWorkerClusterNotFound):
|
||||
logger.Debug().Msg("non-existent worker cluster requested")
|
||||
return sendAPIError(e, http.StatusNotFound, "worker cluster %q not found", clusterUUID)
|
||||
case err != nil:
|
||||
logger.Error().Err(err).Msg("fetching worker cluster")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "error fetching worker cluster: %v", err)
|
||||
}
|
||||
|
||||
return e.JSON(http.StatusOK, workerClusterDBtoAPI(*cluster))
|
||||
}
|
||||
|
||||
func (f *Flamenco) UpdateWorkerCluster(e echo.Context, clusterUUID string) error {
|
||||
ctx := e.Request().Context()
|
||||
logger := requestLogger(e)
|
||||
logger = logger.With().Str("cluster", clusterUUID).Logger()
|
||||
|
||||
if !uuid.IsValid(clusterUUID) {
|
||||
return sendAPIError(e, http.StatusBadRequest, "not a valid UUID")
|
||||
}
|
||||
|
||||
// Decode the request body.
|
||||
var update api.UpdateWorkerClusterJSONBody
|
||||
if err := e.Bind(&update); err != nil {
|
||||
logger.Warn().Err(err).Msg("bad request received")
|
||||
return sendAPIError(e, http.StatusBadRequest, "invalid format")
|
||||
}
|
||||
|
||||
dbCluster, err := f.persist.FetchWorkerCluster(ctx, clusterUUID)
|
||||
switch {
|
||||
case errors.Is(err, persistence.ErrWorkerClusterNotFound):
|
||||
logger.Debug().Msg("non-existent worker cluster requested")
|
||||
return sendAPIError(e, http.StatusNotFound, "worker cluster %q not found", clusterUUID)
|
||||
case err != nil:
|
||||
logger.Error().Err(err).Msg("fetching worker cluster")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "error fetching worker cluster: %v", err)
|
||||
}
|
||||
|
||||
// Update the cluster.
|
||||
dbCluster.Name = update.Name
|
||||
if update.Description == nil {
|
||||
dbCluster.Description = ""
|
||||
} else {
|
||||
dbCluster.Description = *update.Description
|
||||
}
|
||||
|
||||
if err := f.persist.SaveWorkerCluster(ctx, dbCluster); err != nil {
|
||||
logger.Error().Err(err).Msg("saving worker cluster")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "error saving worker cluster")
|
||||
}
|
||||
|
||||
// TODO: SocketIO broadcast of cluster update.
|
||||
|
||||
return e.NoContent(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (f *Flamenco) FetchWorkerClusters(e echo.Context) error {
|
||||
ctx := e.Request().Context()
|
||||
logger := requestLogger(e)
|
||||
|
||||
dbClusters, err := f.persist.FetchWorkerClusters(ctx)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("fetching worker clusters")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "error saving worker cluster")
|
||||
}
|
||||
|
||||
apiClusters := []api.WorkerCluster{}
|
||||
for _, dbCluster := range dbClusters {
|
||||
apiCluster := workerClusterDBtoAPI(*dbCluster)
|
||||
apiClusters = append(apiClusters, apiCluster)
|
||||
}
|
||||
|
||||
clusterList := api.WorkerClusterList{
|
||||
Clusters: &apiClusters,
|
||||
}
|
||||
return e.JSON(http.StatusOK, &clusterList)
|
||||
}
|
||||
|
||||
func (f *Flamenco) CreateWorkerCluster(e echo.Context) error {
|
||||
ctx := e.Request().Context()
|
||||
logger := requestLogger(e)
|
||||
|
||||
// Decode the request body.
|
||||
var apiCluster api.CreateWorkerClusterJSONBody
|
||||
if err := e.Bind(&apiCluster); err != nil {
|
||||
logger.Warn().Err(err).Msg("bad request received")
|
||||
return sendAPIError(e, http.StatusBadRequest, "invalid format")
|
||||
}
|
||||
|
||||
// Convert to persistence layer model.
|
||||
var clusterUUID string
|
||||
if apiCluster.Id != nil && *apiCluster.Id != "" {
|
||||
clusterUUID = *apiCluster.Id
|
||||
} else {
|
||||
clusterUUID = uuid.New()
|
||||
}
|
||||
|
||||
dbCluster := persistence.WorkerCluster{
|
||||
UUID: clusterUUID,
|
||||
Name: apiCluster.Name,
|
||||
}
|
||||
if apiCluster.Description != nil {
|
||||
dbCluster.Description = *apiCluster.Description
|
||||
}
|
||||
|
||||
// Store in the database.
|
||||
if err := f.persist.CreateWorkerCluster(ctx, &dbCluster); err != nil {
|
||||
logger.Error().Err(err).Msg("creating worker cluster")
|
||||
return sendAPIError(e, http.StatusInternalServerError, "error creating worker cluster")
|
||||
}
|
||||
|
||||
// TODO: SocketIO broadcast of cluster creation.
|
||||
|
||||
return e.JSON(http.StatusOK, workerClusterDBtoAPI(dbCluster))
|
||||
}
|
||||
|
||||
func workerSummary(w persistence.Worker) api.WorkerSummary {
|
||||
summary := api.WorkerSummary{
|
||||
Id: w.UUID,
|
||||
@ -211,5 +407,26 @@ func workerDBtoAPI(w persistence.Worker) api.Worker {
|
||||
SupportedTaskTypes: w.TaskTypes(),
|
||||
}
|
||||
|
||||
if len(w.Clusters) > 0 {
|
||||
clusters := []api.WorkerCluster{}
|
||||
for i := range w.Clusters {
|
||||
clusters = append(clusters, workerClusterDBtoAPI(*w.Clusters[i]))
|
||||
}
|
||||
apiWorker.Clusters = &clusters
|
||||
}
|
||||
|
||||
return apiWorker
|
||||
}
|
||||
|
||||
func workerClusterDBtoAPI(wc persistence.WorkerCluster) api.WorkerCluster {
|
||||
uuid := wc.UUID // Take a copy for safety.
|
||||
|
||||
apiCluster := api.WorkerCluster{
|
||||
Id: &uuid,
|
||||
Name: wc.Name,
|
||||
}
|
||||
if len(wc.Description) > 0 {
|
||||
apiCluster.Description = &wc.Description
|
||||
}
|
||||
return apiCluster
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"git.blender.org/flamenco/internal/manager/persistence"
|
||||
"git.blender.org/flamenco/pkg/api"
|
||||
@ -260,3 +261,59 @@ func TestRequestWorkerStatusChangeRevert(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assertResponseNoContent(t, echo)
|
||||
}
|
||||
|
||||
func TestWorkerClusterCRUDHappyFlow(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mf := newMockedFlamenco(mockCtrl)
|
||||
|
||||
// Create a cluster.
|
||||
UUID := "18d9234e-5135-458f-a1ba-a350c3d4e837"
|
||||
apiCluster := api.WorkerCluster{
|
||||
Id: &UUID,
|
||||
Name: "ʻO nā manu ʻino",
|
||||
Description: ptr("Ke aloha"),
|
||||
}
|
||||
expectDBCluster := persistence.WorkerCluster{
|
||||
UUID: UUID,
|
||||
Name: apiCluster.Name,
|
||||
Description: *apiCluster.Description,
|
||||
}
|
||||
mf.persistence.EXPECT().CreateWorkerCluster(gomock.Any(), &expectDBCluster)
|
||||
// TODO: expect SocketIO broadcast of the cluster creation.
|
||||
echo := mf.prepareMockedJSONRequest(apiCluster)
|
||||
require.NoError(t, mf.flamenco.CreateWorkerCluster(echo))
|
||||
assertResponseJSON(t, echo, http.StatusOK, &apiCluster)
|
||||
|
||||
// Fetch the cluster
|
||||
mf.persistence.EXPECT().FetchWorkerCluster(gomock.Any(), UUID).Return(&expectDBCluster, nil)
|
||||
echo = mf.prepareMockedRequest(nil)
|
||||
require.NoError(t, mf.flamenco.FetchWorkerCluster(echo, UUID))
|
||||
assertResponseJSON(t, echo, http.StatusOK, &apiCluster)
|
||||
|
||||
// Update & save.
|
||||
newUUID := "60442762-83d3-4fc3-bf75-6ab5799cdbaa"
|
||||
newAPICluster := api.WorkerCluster{
|
||||
Id: &newUUID, // Intentionally change the UUID. This should just be ignored.
|
||||
Name: "updated name",
|
||||
}
|
||||
expectNewDBCluster := persistence.WorkerCluster{
|
||||
UUID: UUID,
|
||||
Name: newAPICluster.Name,
|
||||
Description: "",
|
||||
}
|
||||
// TODO: expect SocketIO broadcast of the cluster update.
|
||||
mf.persistence.EXPECT().FetchWorkerCluster(gomock.Any(), UUID).Return(&expectDBCluster, nil)
|
||||
mf.persistence.EXPECT().SaveWorkerCluster(gomock.Any(), &expectNewDBCluster)
|
||||
echo = mf.prepareMockedJSONRequest(newAPICluster)
|
||||
require.NoError(t, mf.flamenco.UpdateWorkerCluster(echo, UUID))
|
||||
assertResponseNoContent(t, echo)
|
||||
|
||||
// Delete.
|
||||
mf.persistence.EXPECT().DeleteWorkerCluster(gomock.Any(), UUID)
|
||||
// TODO: expect SocketIO broadcast of the cluster deletion.
|
||||
echo = mf.prepareMockedJSONRequest(newAPICluster)
|
||||
require.NoError(t, mf.flamenco.DeleteWorkerCluster(echo, UUID))
|
||||
assertResponseNoContent(t, echo)
|
||||
}
|
||||
|
@ -33,8 +33,12 @@ func (f *Flamenco) RegisterWorker(e echo.Context) error {
|
||||
}
|
||||
|
||||
// TODO: validate the request, should at least have non-empty name, secret, and platform.
|
||||
|
||||
logger.Info().Str("name", req.Name).Msg("registering new worker")
|
||||
workerUUID := uuid.New()
|
||||
logger = logger.With().
|
||||
Str("name", req.Name).
|
||||
Str("uuid", workerUUID).
|
||||
Logger()
|
||||
logger.Info().Msg("registering new worker")
|
||||
|
||||
hashedPassword, err := passwordHasher.GenerateHashedPassword([]byte(req.Secret))
|
||||
if err != nil {
|
||||
@ -43,7 +47,7 @@ func (f *Flamenco) RegisterWorker(e echo.Context) error {
|
||||
}
|
||||
|
||||
dbWorker := persistence.Worker{
|
||||
UUID: uuid.New(),
|
||||
UUID: workerUUID,
|
||||
Name: req.Name,
|
||||
Secret: string(hashedPassword),
|
||||
Platform: req.Platform,
|
||||
|
@ -25,9 +25,12 @@ import (
|
||||
shaman_config "git.blender.org/flamenco/pkg/shaman/config"
|
||||
)
|
||||
|
||||
const (
|
||||
configFilename = "flamenco-manager.yaml"
|
||||
// configFilename is used to specify where flamenco will write its config file.
|
||||
// If the path is not absolute, it will use the flamenco binary location as the
|
||||
// relative root path. This is not intended to be changed during runtime.
|
||||
var configFilename = "flamenco-manager.yaml"
|
||||
|
||||
const (
|
||||
latestConfigVersion = 3
|
||||
|
||||
// // relative to the Flamenco Server Base URL:
|
||||
|
@ -20,7 +20,9 @@ type Author struct {
|
||||
}
|
||||
|
||||
type AuthoredJob struct {
|
||||
JobID string
|
||||
JobID string
|
||||
WorkerClusterUUID string
|
||||
|
||||
Name string
|
||||
JobType string
|
||||
Priority int
|
||||
|
@ -127,6 +127,10 @@ func (s *Service) Compile(ctx context.Context, sj api.SubmittedJob) (*AuthoredJo
|
||||
aj.Storage.ShamanCheckoutID = *sj.Storage.ShamanCheckoutId
|
||||
}
|
||||
|
||||
if sj.WorkerCluster != nil {
|
||||
aj.WorkerClusterUUID = *sj.WorkerCluster
|
||||
}
|
||||
|
||||
compiler, err := vm.getCompileJob()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -139,12 +143,13 @@ func (s *Service) Compile(ctx context.Context, sj api.SubmittedJob) (*AuthoredJo
|
||||
Int("num_tasks", len(aj.Tasks)).
|
||||
Str("name", aj.Name).
|
||||
Str("jobtype", aj.JobType).
|
||||
Str("job", aj.JobID).
|
||||
Msg("job compiled")
|
||||
|
||||
return &aj, nil
|
||||
}
|
||||
|
||||
// ListJobTypes returns the list of available job types.
|
||||
// ListJobTypes returns the list of available job types.
|
||||
func (s *Service) ListJobTypes() api.AvailableJobTypes {
|
||||
jobTypes := make([]api.AvailableJobType, 0)
|
||||
|
||||
|
@ -45,11 +45,12 @@ func exampleSubmittedJob() api.SubmittedJob {
|
||||
"user.name": "Sybren Stüvel",
|
||||
}}
|
||||
sj := api.SubmittedJob{
|
||||
Name: "3Д рендеринг",
|
||||
Priority: 50,
|
||||
Type: "simple-blender-render",
|
||||
Settings: &settings,
|
||||
Metadata: &metadata,
|
||||
Name: "3Д рендеринг",
|
||||
Priority: 50,
|
||||
Type: "simple-blender-render",
|
||||
Settings: &settings,
|
||||
Metadata: &metadata,
|
||||
WorkerCluster: ptr("acce9983-e663-4210-b3cc-f7bfa629cb21"),
|
||||
}
|
||||
return sj
|
||||
}
|
||||
@ -79,6 +80,7 @@ func TestSimpleBlenderRenderHappy(t *testing.T) {
|
||||
|
||||
// Properties should be copied as-is.
|
||||
assert.Equal(t, sj.Name, aj.Name)
|
||||
assert.Equal(t, *sj.WorkerCluster, aj.WorkerClusterUUID)
|
||||
assert.Equal(t, sj.Type, aj.JobType)
|
||||
assert.Equal(t, sj.Priority, aj.Priority)
|
||||
assert.EqualValues(t, sj.Settings.AdditionalProperties, aj.Settings)
|
||||
@ -137,6 +139,35 @@ func TestSimpleBlenderRenderHappy(t *testing.T) {
|
||||
assert.Equal(t, expectDeps, tVideo.Dependencies)
|
||||
}
|
||||
|
||||
func TestJobWithoutCluster(t *testing.T) {
|
||||
c := mockedClock(t)
|
||||
|
||||
s, err := Load(c)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Compiling a job should be really fast.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
sj := exampleSubmittedJob()
|
||||
|
||||
// Try with nil WorkerCluster.
|
||||
{
|
||||
sj.WorkerCluster = nil
|
||||
aj, err := s.Compile(ctx, sj)
|
||||
require.NoError(t, err)
|
||||
assert.Zero(t, aj.WorkerClusterUUID)
|
||||
}
|
||||
|
||||
// Try with empty WorkerCluster.
|
||||
{
|
||||
sj.WorkerCluster = ptr("")
|
||||
aj, err := s.Compile(ctx, sj)
|
||||
require.NoError(t, err)
|
||||
assert.Zero(t, aj.WorkerClusterUUID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleBlenderRenderWindowsPaths(t *testing.T) {
|
||||
c := mockedClock(t)
|
||||
|
||||
|
@ -28,7 +28,13 @@ import (
|
||||
// memory at a time. This is variable to allow unit testing with lower limits.
|
||||
var jobDeletionQueueSize = defaultJobDeletionQueueSize
|
||||
|
||||
const defaultJobDeletionQueueSize = 100
|
||||
const (
|
||||
defaultJobDeletionQueueSize = 100
|
||||
|
||||
// jobDeletionCheckInterval determines how often the database is checked for
|
||||
// jobs that have been requested to be deleted.
|
||||
jobDeletionCheckInterval = 1 * time.Minute
|
||||
)
|
||||
|
||||
// Service can mark jobs as "deletion requested", as well as delete those jobs
|
||||
// in a background goroutine.
|
||||
@ -106,7 +112,7 @@ func (s *Service) Run(ctx context.Context) {
|
||||
return
|
||||
case jobUUID := <-s.queue:
|
||||
s.deleteJob(ctx, jobUUID)
|
||||
case <-time.After(1 * time.Minute):
|
||||
case <-time.After(jobDeletionCheckInterval):
|
||||
// Inspect the database to see if there was anything marked for deletion
|
||||
// without getting into our queue. This can happen when lots of jobs are
|
||||
// queued in quick succession, as then the queue channel gets full.
|
||||
@ -126,13 +132,20 @@ func (s *Service) queuePendingDeletions(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, jobUUID := range jobUUIDs {
|
||||
numDeletionsQueued := len(jobUUIDs)
|
||||
queueLoop:
|
||||
for index, jobUUID := range jobUUIDs {
|
||||
select {
|
||||
case s.queue <- jobUUID:
|
||||
log.Debug().Str("job", jobUUID).Msg("job deleter: job queued for deletion")
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
log.Info().Msg("job deleter: job deletion queue is full")
|
||||
break
|
||||
numRemaining := numDeletionsQueued - index
|
||||
log.Info().
|
||||
Int("deletionsQueued", len(s.queue)).
|
||||
Int("deletionsRemaining", numRemaining).
|
||||
Stringer("checkInterval", jobDeletionCheckInterval).
|
||||
Msg("job deleter: job deletion queue is full, remaining deletions will be picked up later")
|
||||
break queueLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -145,13 +158,13 @@ func (s *Service) deleteJob(ctx context.Context, jobUUID string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info().Msg("job deleter: removing logs, last-rendered images, etc.")
|
||||
logger.Debug().Msg("job deleter: removing logs, last-rendered images, etc.")
|
||||
if err := s.storage.RemoveJobStorage(ctx, jobUUID); err != nil {
|
||||
logger.Error().Err(err).Msg("job deleter: error removing job logs, job deletion aborted")
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info().Msg("job deleter: removing job from database")
|
||||
logger.Debug().Msg("job deleter: removing job from database")
|
||||
if err := s.persist.DeleteJob(ctx, jobUUID); err != nil {
|
||||
logger.Error().Err(err).Msg("job deleter: unable to remove job from database")
|
||||
return err
|
||||
|
@ -16,6 +16,7 @@ func (db *DB) migrate() error {
|
||||
&Task{},
|
||||
&TaskFailure{},
|
||||
&Worker{},
|
||||
&WorkerCluster{},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to automigrate database: %v", err)
|
||||
|
@ -9,9 +9,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrJobNotFound = PersistenceError{Message: "job not found", Err: gorm.ErrRecordNotFound}
|
||||
ErrTaskNotFound = PersistenceError{Message: "task not found", Err: gorm.ErrRecordNotFound}
|
||||
ErrWorkerNotFound = PersistenceError{Message: "worker not found", Err: gorm.ErrRecordNotFound}
|
||||
ErrJobNotFound = PersistenceError{Message: "job not found", Err: gorm.ErrRecordNotFound}
|
||||
ErrTaskNotFound = PersistenceError{Message: "task not found", Err: gorm.ErrRecordNotFound}
|
||||
ErrWorkerNotFound = PersistenceError{Message: "worker not found", Err: gorm.ErrRecordNotFound}
|
||||
ErrWorkerClusterNotFound = PersistenceError{Message: "worker cluster not found", Err: gorm.ErrRecordNotFound}
|
||||
)
|
||||
|
||||
type PersistenceError struct {
|
||||
@ -39,6 +40,10 @@ func workerError(errorToWrap error, message string, msgArgs ...interface{}) erro
|
||||
return wrapError(translateGormWorkerError(errorToWrap), message, msgArgs...)
|
||||
}
|
||||
|
||||
func workerClusterError(errorToWrap error, message string, msgArgs ...interface{}) error {
|
||||
return wrapError(translateGormWorkerClusterError(errorToWrap), message, msgArgs...)
|
||||
}
|
||||
|
||||
func wrapError(errorToWrap error, message string, format ...interface{}) error {
|
||||
// Only format if there are arguments for formatting.
|
||||
var formattedMsg string
|
||||
@ -80,3 +85,12 @@ func translateGormWorkerError(gormError error) error {
|
||||
}
|
||||
return gormError
|
||||
}
|
||||
|
||||
// translateGormWorkerClusterError translates a Gorm error to a persistence layer error.
|
||||
// This helps to keep Gorm as "implementation detail" of the persistence layer.
|
||||
func translateGormWorkerClusterError(gormError error) error {
|
||||
if errors.Is(gormError, gorm.ErrRecordNotFound) {
|
||||
return ErrWorkerClusterNotFound
|
||||
}
|
||||
return gormError
|
||||
}
|
||||
|
@ -35,6 +35,9 @@ type Job struct {
|
||||
DeleteRequestedAt sql.NullTime
|
||||
|
||||
Storage JobStorageInfo `gorm:"embedded;embeddedPrefix:storage_"`
|
||||
|
||||
WorkerClusterID *uint
|
||||
WorkerCluster *WorkerCluster `gorm:"foreignkey:WorkerClusterID;references:ID;constraint:OnDelete:SET NULL"`
|
||||
}
|
||||
|
||||
type StringInterfaceMap map[string]interface{}
|
||||
@ -145,6 +148,16 @@ func (db *DB) StoreAuthoredJob(ctx context.Context, authoredJob job_compilers.Au
|
||||
},
|
||||
}
|
||||
|
||||
// Find and assign the worker cluster.
|
||||
if authoredJob.WorkerClusterUUID != "" {
|
||||
dbCluster, err := fetchWorkerCluster(tx, authoredJob.WorkerClusterUUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbJob.WorkerClusterID = &dbCluster.ID
|
||||
dbJob.WorkerCluster = dbCluster
|
||||
}
|
||||
|
||||
if err := tx.Create(&dbJob).Error; err != nil {
|
||||
return jobError(err, "storing job")
|
||||
}
|
||||
@ -212,6 +225,7 @@ func (db *DB) FetchJob(ctx context.Context, jobUUID string) (*Job, error) {
|
||||
dbJob := Job{}
|
||||
findResult := db.gormDB.WithContext(ctx).
|
||||
Limit(1).
|
||||
Preload("WorkerCluster").
|
||||
Find(&dbJob, "uuid = ?", jobUUID)
|
||||
if findResult.Error != nil {
|
||||
return nil, jobError(findResult.Error, "fetching job")
|
||||
|
@ -103,13 +103,26 @@ func (db *DB) WorkersLeftToRun(ctx context.Context, job *Job, taskType string) (
|
||||
Where("JB.job_id = ?", job.ID).
|
||||
Where("JB.task_type = ?", taskType)
|
||||
|
||||
// Find the workers NOT blocked.
|
||||
workers := []*Worker{}
|
||||
tx := db.gormDB.WithContext(ctx).
|
||||
query := db.gormDB.WithContext(ctx).
|
||||
Model(&Worker{}).
|
||||
Select("uuid").
|
||||
Where("id not in (?)", blockedWorkers).
|
||||
Scan(&workers)
|
||||
Where("id not in (?)", blockedWorkers)
|
||||
|
||||
if job.WorkerClusterID == nil {
|
||||
// Count all workers, so no extra restrictions are necessary.
|
||||
} else {
|
||||
// Only count workers in the job's cluster.
|
||||
jobCluster := db.gormDB.
|
||||
Table("worker_cluster_membership").
|
||||
Select("worker_id").
|
||||
Where("worker_cluster_id = ?", *job.WorkerClusterID)
|
||||
query = query.
|
||||
Where("id in (?)", jobCluster)
|
||||
}
|
||||
|
||||
// Find the workers NOT blocked.
|
||||
workers := []*Worker{}
|
||||
tx := query.Scan(&workers)
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
@ -125,6 +126,16 @@ func TestWorkersLeftToRun(t *testing.T) {
|
||||
worker1 := createWorker(ctx, t, db)
|
||||
worker2 := createWorkerFrom(ctx, t, db, *worker1)
|
||||
|
||||
// Create one worker cluster. It will not be used by this job, but one of the
|
||||
// workers will be assigned to it. It can get this job's tasks, though.
|
||||
// Because the job is clusterless, it can be run by all.
|
||||
cluster1 := WorkerCluster{UUID: "11157623-4b14-4801-bee2-271dddab6309", Name: "Cluster 1"}
|
||||
require.NoError(t, db.CreateWorkerCluster(ctx, &cluster1))
|
||||
workerC1 := createWorker(ctx, t, db, func(w *Worker) {
|
||||
w.UUID = "c1c1c1c1-0000-1111-2222-333333333333"
|
||||
w.Clusters = []*WorkerCluster{&cluster1}
|
||||
})
|
||||
|
||||
uuidMap := func(workers ...*Worker) map[string]bool {
|
||||
theMap := map[string]bool{}
|
||||
for _, worker := range workers {
|
||||
@ -133,21 +144,22 @@ func TestWorkersLeftToRun(t *testing.T) {
|
||||
return theMap
|
||||
}
|
||||
|
||||
// Two workers, no blocklist.
|
||||
// Three workers, no blocklist.
|
||||
left, err = db.WorkersLeftToRun(ctx, job, "blender")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, uuidMap(worker1, worker2), left)
|
||||
assert.Equal(t, uuidMap(worker1, worker2, workerC1), left)
|
||||
}
|
||||
|
||||
// Two workers, one blocked.
|
||||
_ = db.AddWorkerToJobBlocklist(ctx, job, worker1, "blender")
|
||||
left, err = db.WorkersLeftToRun(ctx, job, "blender")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, uuidMap(worker2), left)
|
||||
assert.Equal(t, uuidMap(worker2, workerC1), left)
|
||||
}
|
||||
|
||||
// Two workers, both blocked.
|
||||
// All workers blocked.
|
||||
_ = db.AddWorkerToJobBlocklist(ctx, job, worker2, "blender")
|
||||
_ = db.AddWorkerToJobBlocklist(ctx, job, workerC1, "blender")
|
||||
left, err = db.WorkersLeftToRun(ctx, job, "blender")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, left)
|
||||
@ -156,10 +168,75 @@ func TestWorkersLeftToRun(t *testing.T) {
|
||||
fakeJob := Job{Model: Model{ID: 327}}
|
||||
left, err = db.WorkersLeftToRun(ctx, &fakeJob, "blender")
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, uuidMap(worker1, worker2), left)
|
||||
assert.Equal(t, uuidMap(worker1, worker2, workerC1), left)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkersLeftToRunWithClusters(t *testing.T) {
|
||||
ctx, cancel, db := persistenceTestFixtures(t, schedulerTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Create clusters.
|
||||
cluster1 := WorkerCluster{UUID: "11157623-4b14-4801-bee2-271dddab6309", Name: "Cluster 1"}
|
||||
cluster2 := WorkerCluster{UUID: "22257623-4b14-4801-bee2-271dddab6309", Name: "Cluster 2"}
|
||||
cluster3 := WorkerCluster{UUID: "33357623-4b14-4801-bee2-271dddab6309", Name: "Cluster 3"}
|
||||
require.NoError(t, db.CreateWorkerCluster(ctx, &cluster1))
|
||||
require.NoError(t, db.CreateWorkerCluster(ctx, &cluster2))
|
||||
require.NoError(t, db.CreateWorkerCluster(ctx, &cluster3))
|
||||
|
||||
// Create a job in cluster1.
|
||||
authoredJob := createTestAuthoredJobWithTasks()
|
||||
authoredJob.WorkerClusterUUID = cluster1.UUID
|
||||
job := persistAuthoredJob(t, ctx, db, authoredJob)
|
||||
|
||||
// Clusters 1 + 3
|
||||
workerC13 := createWorker(ctx, t, db, func(w *Worker) {
|
||||
w.UUID = "c13c1313-0000-1111-2222-333333333333"
|
||||
w.Clusters = []*WorkerCluster{&cluster1, &cluster3}
|
||||
})
|
||||
// Cluster 1
|
||||
workerC1 := createWorker(ctx, t, db, func(w *Worker) {
|
||||
w.UUID = "c1c1c1c1-0000-1111-2222-333333333333"
|
||||
w.Clusters = []*WorkerCluster{&cluster1}
|
||||
})
|
||||
// Cluster 2 worker, this one should never appear.
|
||||
createWorker(ctx, t, db, func(w *Worker) {
|
||||
w.UUID = "c2c2c2c2-0000-1111-2222-333333333333"
|
||||
w.Clusters = []*WorkerCluster{&cluster2}
|
||||
})
|
||||
// No clusters, so should be able to run only clusterless jobs. Which is none
|
||||
// in this test.
|
||||
createWorker(ctx, t, db, func(w *Worker) {
|
||||
w.UUID = "00000000-0000-1111-2222-333333333333"
|
||||
w.Clusters = nil
|
||||
})
|
||||
|
||||
uuidMap := func(workers ...*Worker) map[string]bool {
|
||||
theMap := map[string]bool{}
|
||||
for _, worker := range workers {
|
||||
theMap[worker.UUID] = true
|
||||
}
|
||||
return theMap
|
||||
}
|
||||
|
||||
// All Cluster 1 workers, no blocklist.
|
||||
left, err := db.WorkersLeftToRun(ctx, job, "blender")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uuidMap(workerC13, workerC1), left)
|
||||
|
||||
// One worker blocked, one worker remain.
|
||||
_ = db.AddWorkerToJobBlocklist(ctx, job, workerC1, "blender")
|
||||
left, err = db.WorkersLeftToRun(ctx, job, "blender")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uuidMap(workerC13), left)
|
||||
|
||||
// All clustered workers blocked.
|
||||
_ = db.AddWorkerToJobBlocklist(ctx, job, workerC13, "blender")
|
||||
left, err = db.WorkersLeftToRun(ctx, job, "blender")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, left)
|
||||
}
|
||||
|
||||
func TestCountTaskFailuresOfWorker(t *testing.T) {
|
||||
ctx, close, db, dbJob, authoredJob := jobTasksTestFixtures(t)
|
||||
defer close()
|
||||
|
@ -64,6 +64,8 @@ func (db *DB) QueryJobs(ctx context.Context, apiQ api.JobsQuery) ([]*Job, error)
|
||||
}
|
||||
}
|
||||
|
||||
q.Preload("Cluster")
|
||||
|
||||
result := []*Job{}
|
||||
tx := q.Scan(&result)
|
||||
return result, tx.Error
|
||||
|
@ -676,7 +676,7 @@ func jobTasksTestFixtures(t *testing.T) (context.Context, context.CancelFunc, *D
|
||||
return ctx, cancel, db, dbJob, authoredJob
|
||||
}
|
||||
|
||||
func createWorker(ctx context.Context, t *testing.T, db *DB) *Worker {
|
||||
func createWorker(ctx context.Context, t *testing.T, db *DB, updaters ...func(*Worker)) *Worker {
|
||||
w := Worker{
|
||||
UUID: "f0a123a9-ab05-4ce2-8577-94802cfe74a4",
|
||||
Name: "дрон",
|
||||
@ -685,6 +685,11 @@ func createWorker(ctx context.Context, t *testing.T, db *DB) *Worker {
|
||||
Software: "3.0",
|
||||
Status: api.WorkerStatusAwake,
|
||||
SupportedTaskTypes: "blender,ffmpeg,file-management",
|
||||
Clusters: nil,
|
||||
}
|
||||
|
||||
for _, updater := range updaters {
|
||||
updater(&w)
|
||||
}
|
||||
|
||||
err := db.CreateWorker(ctx, &w)
|
||||
|
@ -26,13 +26,18 @@ func (db *DB) ScheduleTask(ctx context.Context, w *Worker) (*Task, error) {
|
||||
logger := log.With().Str("worker", w.UUID).Logger()
|
||||
logger.Trace().Msg("finding task for worker")
|
||||
|
||||
hasWorkerClusters, err := db.HasWorkerClusters(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Run two queries in one transaction:
|
||||
// 1. find task, and
|
||||
// 2. assign the task to the worker.
|
||||
var task *Task
|
||||
txErr := db.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var err error
|
||||
task, err = findTaskForWorker(tx, w)
|
||||
task, err = findTaskForWorker(tx, w, hasWorkerClusters)
|
||||
if err != nil {
|
||||
if isDatabaseBusyError(err) {
|
||||
logger.Trace().Err(err).Msg("database busy while finding task for worker")
|
||||
@ -79,7 +84,7 @@ func (db *DB) ScheduleTask(ctx context.Context, w *Worker) (*Task, error) {
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func findTaskForWorker(tx *gorm.DB, w *Worker) (*Task, error) {
|
||||
func findTaskForWorker(tx *gorm.DB, w *Worker, checkWorkerClusters bool) (*Task, error) {
|
||||
task := Task{}
|
||||
|
||||
// If a task is alreay active & assigned to this worker, return just that.
|
||||
@ -114,18 +119,37 @@ func findTaskForWorker(tx *gorm.DB, w *Worker) (*Task, error) {
|
||||
// a 'schedulable' status might have been assigned to a worker, representing
|
||||
// the last worker to touch it -- it's not meant to indicate "ownership" of
|
||||
// the task.
|
||||
findTaskResult := tx.
|
||||
Model(&task).
|
||||
findTaskQuery := tx.Model(&task).
|
||||
Joins("left join jobs on tasks.job_id = jobs.id").
|
||||
Joins("left join task_failures TF on tasks.id = TF.task_id and TF.worker_id=?", w.ID).
|
||||
Where("tasks.status in ?", schedulableTaskStatuses). // Schedulable task statuses
|
||||
Where("jobs.status in ?", schedulableJobStatuses). // Schedulable job statuses
|
||||
Where("tasks.type in ?", w.TaskTypes()). // Supported task types
|
||||
Where("tasks.id not in (?)", incompleteDepsQuery). // Dependencies completed
|
||||
Where("TF.worker_id is NULL"). // Not failed before
|
||||
Where("tasks.type not in (?)", blockedTaskTypesQuery). // Non-blocklisted
|
||||
Order("jobs.priority desc"). // Highest job priority
|
||||
Order("tasks.priority desc"). // Highest task priority
|
||||
Where("tasks.status in ?", schedulableTaskStatuses). // Schedulable task statuses
|
||||
Where("jobs.status in ?", schedulableJobStatuses). // Schedulable job statuses
|
||||
Where("tasks.type in ?", w.TaskTypes()). // Supported task types
|
||||
Where("tasks.id not in (?)", incompleteDepsQuery). // Dependencies completed
|
||||
Where("TF.worker_id is NULL"). // Not failed before
|
||||
Where("tasks.type not in (?)", blockedTaskTypesQuery) // Non-blocklisted
|
||||
|
||||
if checkWorkerClusters {
|
||||
// The system has one or more clusters, so limit the available jobs to those
|
||||
// that have no cluster, or overlap with the Worker's clusters.
|
||||
if len(w.Clusters) == 0 {
|
||||
// Clusterless workers only get clusterless jobs.
|
||||
findTaskQuery = findTaskQuery.
|
||||
Where("jobs.worker_cluster_id is NULL")
|
||||
} else {
|
||||
// Clustered workers get clusterless jobs AND jobs of their own clusters.
|
||||
clusterIDs := []uint{}
|
||||
for _, cluster := range w.Clusters {
|
||||
clusterIDs = append(clusterIDs, cluster.ID)
|
||||
}
|
||||
findTaskQuery = findTaskQuery.
|
||||
Where("jobs.worker_cluster_id is NULL or worker_cluster_id in ?", clusterIDs)
|
||||
}
|
||||
}
|
||||
|
||||
findTaskResult := findTaskQuery.
|
||||
Order("jobs.priority desc"). // Highest job priority
|
||||
Order("tasks.priority desc"). // Highest task priority
|
||||
Limit(1).
|
||||
Preload("Job").
|
||||
Find(&task)
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"git.blender.org/flamenco/internal/manager/job_compilers"
|
||||
"git.blender.org/flamenco/internal/uuid"
|
||||
@ -289,6 +290,90 @@ func TestPreviouslyFailed(t *testing.T) {
|
||||
assert.Equal(t, att2.Name, task.Name, "the second task should have been chosen")
|
||||
}
|
||||
|
||||
func TestWorkerClusterJobWithCluster(t *testing.T) {
|
||||
ctx, cancel, db := persistenceTestFixtures(t, schedulerTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Create worker clusters:
|
||||
cluster1 := WorkerCluster{UUID: "f0157623-4b14-4801-bee2-271dddab6309", Name: "Cluster 1"}
|
||||
cluster2 := WorkerCluster{UUID: "2f71dba1-cf92-4752-8386-f5926affabd5", Name: "Cluster 2"}
|
||||
require.NoError(t, db.CreateWorkerCluster(ctx, &cluster1))
|
||||
require.NoError(t, db.CreateWorkerCluster(ctx, &cluster2))
|
||||
|
||||
// Create a worker in cluster1:
|
||||
workerC := linuxWorker(t, db, func(w *Worker) {
|
||||
w.Clusters = []*WorkerCluster{&cluster1}
|
||||
})
|
||||
|
||||
// Create a worker without cluster:
|
||||
workerNC := linuxWorker(t, db, func(w *Worker) {
|
||||
w.UUID = "c53f8f68-4149-4790-991c-ba73a326551e"
|
||||
w.Clusters = nil
|
||||
})
|
||||
|
||||
{ // Test job with different cluster:
|
||||
authTask := authorTestTask("the task", "blender")
|
||||
job := authorTestJob("499cf0f8-e83d-4cb1-837a-df94789d07db", "simple-blender-render", authTask)
|
||||
job.WorkerClusterUUID = cluster2.UUID
|
||||
constructTestJob(ctx, t, db, job)
|
||||
|
||||
task, err := db.ScheduleTask(ctx, &workerC)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, task, "job with different cluster should not be scheduled")
|
||||
}
|
||||
|
||||
{ // Test job with matching cluster:
|
||||
authTask := authorTestTask("the task", "blender")
|
||||
job := authorTestJob("5d4c2321-0bb7-4c13-a9dd-32a2c0cd156e", "simple-blender-render", authTask)
|
||||
job.WorkerClusterUUID = cluster1.UUID
|
||||
constructTestJob(ctx, t, db, job)
|
||||
|
||||
task, err := db.ScheduleTask(ctx, &workerC)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, task, "job with matching cluster should be scheduled")
|
||||
assert.Equal(t, authTask.UUID, task.UUID)
|
||||
|
||||
task, err = db.ScheduleTask(ctx, &workerNC)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, task, "job with cluster should not be scheduled for worker without cluster")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkerClusterJobWithoutCluster(t *testing.T) {
|
||||
ctx, cancel, db := persistenceTestFixtures(t, schedulerTestTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Create worker cluster:
|
||||
cluster1 := WorkerCluster{UUID: "f0157623-4b14-4801-bee2-271dddab6309", Name: "Cluster 1"}
|
||||
require.NoError(t, db.CreateWorkerCluster(ctx, &cluster1))
|
||||
|
||||
// Create a worker in cluster1:
|
||||
workerC := linuxWorker(t, db, func(w *Worker) {
|
||||
w.Clusters = []*WorkerCluster{&cluster1}
|
||||
})
|
||||
|
||||
// Create a worker without cluster:
|
||||
workerNC := linuxWorker(t, db, func(w *Worker) {
|
||||
w.UUID = "c53f8f68-4149-4790-991c-ba73a326551e"
|
||||
w.Clusters = nil
|
||||
})
|
||||
|
||||
// Test cluster-less job:
|
||||
authTask := authorTestTask("the task", "blender")
|
||||
job := authorTestJob("b6a1d859-122f-4791-8b78-b943329a9989", "simple-blender-render", authTask)
|
||||
constructTestJob(ctx, t, db, job)
|
||||
|
||||
task, err := db.ScheduleTask(ctx, &workerC)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, task, "job without cluster should always be scheduled to worker in some cluster")
|
||||
assert.Equal(t, authTask.UUID, task.UUID)
|
||||
|
||||
task, err = db.ScheduleTask(ctx, &workerNC)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, task, "job without cluster should always be scheduled to worker without cluster")
|
||||
assert.Equal(t, authTask.UUID, task.UUID)
|
||||
}
|
||||
|
||||
func TestBlocklisted(t *testing.T) {
|
||||
ctx, cancel, db := persistenceTestFixtures(t, schedulerTestTimeout)
|
||||
defer cancel()
|
||||
@ -383,7 +468,7 @@ func setTaskStatus(t *testing.T, db *DB, taskUUID string, status api.TaskStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func linuxWorker(t *testing.T, db *DB) Worker {
|
||||
func linuxWorker(t *testing.T, db *DB, updaters ...func(worker *Worker)) Worker {
|
||||
w := Worker{
|
||||
UUID: "b13b8322-3e96-41c3-940a-3d581008a5f8",
|
||||
Name: "Linux",
|
||||
@ -392,6 +477,10 @@ func linuxWorker(t *testing.T, db *DB) Worker {
|
||||
SupportedTaskTypes: "blender,ffmpeg,file-management,misc",
|
||||
}
|
||||
|
||||
for _, updater := range updaters {
|
||||
updater(&w)
|
||||
}
|
||||
|
||||
err := db.gormDB.Save(&w).Error
|
||||
if err != nil {
|
||||
t.Logf("cannot save Linux worker: %v", err)
|
||||
|
@ -10,9 +10,12 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.blender.org/flamenco/internal/uuid"
|
||||
"git.blender.org/flamenco/pkg/api"
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@ -87,3 +90,44 @@ func persistenceTestFixtures(t *testing.T, testContextTimeout time.Duration) (co
|
||||
|
||||
return ctx, cancel, db
|
||||
}
|
||||
|
||||
type WorkerTestFixture struct {
|
||||
db *DB
|
||||
ctx context.Context
|
||||
done func()
|
||||
|
||||
worker *Worker
|
||||
cluster *WorkerCluster
|
||||
}
|
||||
|
||||
func workerTestFixtures(t *testing.T, testContextTimeout time.Duration) WorkerTestFixture {
|
||||
ctx, cancel, db := persistenceTestFixtures(t, testContextTimeout)
|
||||
|
||||
w := Worker{
|
||||
UUID: uuid.New(),
|
||||
Name: "дрон",
|
||||
Address: "fe80::5054:ff:fede:2ad7",
|
||||
Platform: "linux",
|
||||
Software: "3.0",
|
||||
Status: api.WorkerStatusAwake,
|
||||
SupportedTaskTypes: "blender,ffmpeg,file-management",
|
||||
}
|
||||
|
||||
wc := WorkerCluster{
|
||||
UUID: uuid.New(),
|
||||
Name: "arbejdsklynge",
|
||||
Description: "Worker cluster in Danish",
|
||||
}
|
||||
|
||||
require.NoError(t, db.CreateWorker(ctx, &w))
|
||||
require.NoError(t, db.CreateWorkerCluster(ctx, &wc))
|
||||
|
||||
return WorkerTestFixture{
|
||||
db: db,
|
||||
ctx: ctx,
|
||||
done: cancel,
|
||||
|
||||
worker: &w,
|
||||
cluster: &wc,
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ func TestFetchTimedOutTasks(t *testing.T) {
|
||||
// tests that the expected task is returned.
|
||||
assert.Equal(t, task.UUID, timedout[0].UUID)
|
||||
assert.Equal(t, job, timedout[0].Job, "the job should be included in the result as well")
|
||||
assert.Equal(t, w, timedout[0].Worker, "the worker should be included in the result as well")
|
||||
assert.Equal(t, w.UUID, timedout[0].Worker.UUID, "the worker should be included in the result as well")
|
||||
}
|
||||
}
|
||||
|
||||
|
112
internal/manager/persistence/worker_cluster.go
Normal file
112
internal/manager/persistence/worker_cluster.go
Normal file
@ -0,0 +1,112 @@
|
||||
package persistence
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type WorkerCluster struct {
|
||||
Model
|
||||
|
||||
UUID string `gorm:"type:char(36);default:'';unique;index"`
|
||||
Name string `gorm:"type:varchar(64);default:'';unique"`
|
||||
Description string `gorm:"type:varchar(255);default:''"`
|
||||
|
||||
Workers []*Worker `gorm:"many2many:worker_cluster_membership;constraint:OnDelete:CASCADE"`
|
||||
}
|
||||
|
||||
func (db *DB) CreateWorkerCluster(ctx context.Context, wc *WorkerCluster) error {
|
||||
if err := db.gormDB.WithContext(ctx).Create(wc).Error; err != nil {
|
||||
return fmt.Errorf("creating new worker cluster: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasWorkerClusters returns whether there are any clusters defined at all.
|
||||
func (db *DB) HasWorkerClusters(ctx context.Context) (bool, error) {
|
||||
var count int64
|
||||
tx := db.gormDB.WithContext(ctx).
|
||||
Model(&WorkerCluster{}).
|
||||
Count(&count)
|
||||
if err := tx.Error; err != nil {
|
||||
return false, workerClusterError(err, "counting worker clusters")
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func (db *DB) FetchWorkerCluster(ctx context.Context, uuid string) (*WorkerCluster, error) {
|
||||
tx := db.gormDB.WithContext(ctx)
|
||||
return fetchWorkerCluster(tx, uuid)
|
||||
}
|
||||
|
||||
// fetchWorkerCluster fetches the worker cluster using the given database instance.
|
||||
func fetchWorkerCluster(gormDB *gorm.DB, uuid string) (*WorkerCluster, error) {
|
||||
w := WorkerCluster{}
|
||||
tx := gormDB.First(&w, "uuid = ?", uuid)
|
||||
if tx.Error != nil {
|
||||
return nil, workerClusterError(tx.Error, "fetching worker cluster")
|
||||
}
|
||||
return &w, nil
|
||||
}
|
||||
|
||||
func (db *DB) SaveWorkerCluster(ctx context.Context, cluster *WorkerCluster) error {
|
||||
if err := db.gormDB.WithContext(ctx).Save(cluster).Error; err != nil {
|
||||
return workerClusterError(err, "saving worker cluster")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteWorkerCluster deletes the given cluster, after unassigning all workers from it.
|
||||
func (db *DB) DeleteWorkerCluster(ctx context.Context, uuid string) error {
|
||||
tx := db.gormDB.WithContext(ctx).
|
||||
Where("uuid = ?", uuid).
|
||||
Delete(&WorkerCluster{})
|
||||
if tx.Error != nil {
|
||||
return workerClusterError(tx.Error, "deleting worker cluster")
|
||||
}
|
||||
if tx.RowsAffected == 0 {
|
||||
return ErrWorkerClusterNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) FetchWorkerClusters(ctx context.Context) ([]*WorkerCluster, error) {
|
||||
clusters := make([]*WorkerCluster, 0)
|
||||
tx := db.gormDB.WithContext(ctx).Model(&WorkerCluster{}).Scan(&clusters)
|
||||
if tx.Error != nil {
|
||||
return nil, workerClusterError(tx.Error, "fetching all worker clusters")
|
||||
}
|
||||
return clusters, nil
|
||||
}
|
||||
|
||||
func (db *DB) fetchWorkerClustersWithUUID(ctx context.Context, clusterUUIDs []string) ([]*WorkerCluster, error) {
|
||||
clusters := make([]*WorkerCluster, 0)
|
||||
tx := db.gormDB.WithContext(ctx).
|
||||
Model(&WorkerCluster{}).
|
||||
Where("uuid in ?", clusterUUIDs).
|
||||
Scan(&clusters)
|
||||
if tx.Error != nil {
|
||||
return nil, workerClusterError(tx.Error, "fetching all worker clusters")
|
||||
}
|
||||
return clusters, nil
|
||||
}
|
||||
|
||||
func (db *DB) WorkerSetClusters(ctx context.Context, worker *Worker, clusterUUIDs []string) error {
|
||||
clusters, err := db.fetchWorkerClustersWithUUID(ctx, clusterUUIDs)
|
||||
if err != nil {
|
||||
return workerClusterError(err, "fetching worker clusters")
|
||||
}
|
||||
|
||||
err = db.gormDB.WithContext(ctx).
|
||||
Model(worker).
|
||||
Association("Clusters").
|
||||
Replace(clusters)
|
||||
if err != nil {
|
||||
return workerClusterError(err, "updating worker clusters")
|
||||
}
|
||||
return nil
|
||||
}
|
165
internal/manager/persistence/worker_cluster_test.go
Normal file
165
internal/manager/persistence/worker_cluster_test.go
Normal file
@ -0,0 +1,165 @@
|
||||
package persistence
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.blender.org/flamenco/internal/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateFetchCluster(t *testing.T) {
|
||||
f := workerTestFixtures(t, 1*time.Second)
|
||||
defer f.done()
|
||||
|
||||
// Test fetching non-existent cluster
|
||||
fetchedCluster, err := f.db.FetchWorkerCluster(f.ctx, "7ee21bc8-ff1a-42d2-a6b6-cc4b529b189f")
|
||||
assert.ErrorIs(t, err, ErrWorkerClusterNotFound)
|
||||
assert.Nil(t, fetchedCluster)
|
||||
|
||||
// New cluster creation is already done in the workerTestFixtures() call.
|
||||
assert.NotNil(t, f.cluster)
|
||||
|
||||
fetchedCluster, err = f.db.FetchWorkerCluster(f.ctx, f.cluster.UUID)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, fetchedCluster)
|
||||
|
||||
// Test contents of fetched cluster.
|
||||
assert.Equal(t, f.cluster.UUID, fetchedCluster.UUID)
|
||||
assert.Equal(t, f.cluster.Name, fetchedCluster.Name)
|
||||
assert.Equal(t, f.cluster.Description, fetchedCluster.Description)
|
||||
assert.Zero(t, fetchedCluster.Workers)
|
||||
}
|
||||
|
||||
func TestFetchDeleteClusters(t *testing.T) {
|
||||
f := workerTestFixtures(t, 1*time.Second)
|
||||
defer f.done()
|
||||
|
||||
// Single cluster was created by fixture.
|
||||
has, err := f.db.HasWorkerClusters(f.ctx)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, has, "expecting HasWorkerClusters to return true")
|
||||
|
||||
secondCluster := WorkerCluster{
|
||||
UUID: uuid.New(),
|
||||
Name: "arbeiderscluster",
|
||||
Description: "Worker cluster in Dutch",
|
||||
}
|
||||
|
||||
require.NoError(t, f.db.CreateWorkerCluster(f.ctx, &secondCluster))
|
||||
|
||||
allClusters, err := f.db.FetchWorkerClusters(f.ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, allClusters, 2)
|
||||
var allClusterIDs [2]string
|
||||
for idx := range allClusters {
|
||||
allClusterIDs[idx] = allClusters[idx].UUID
|
||||
}
|
||||
assert.Contains(t, allClusterIDs, f.cluster.UUID)
|
||||
assert.Contains(t, allClusterIDs, secondCluster.UUID)
|
||||
|
||||
has, err = f.db.HasWorkerClusters(f.ctx)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, has, "expecting HasWorkerClusters to return true")
|
||||
|
||||
// Test deleting the 2nd cluster.
|
||||
require.NoError(t, f.db.DeleteWorkerCluster(f.ctx, secondCluster.UUID))
|
||||
|
||||
allClusters, err = f.db.FetchWorkerClusters(f.ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, allClusters, 1)
|
||||
assert.Equal(t, f.cluster.UUID, allClusters[0].UUID)
|
||||
|
||||
// Test deleting the 1st cluster.
|
||||
require.NoError(t, f.db.DeleteWorkerCluster(f.ctx, f.cluster.UUID))
|
||||
has, err = f.db.HasWorkerClusters(f.ctx)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, has, "expecting HasWorkerClusters to return false")
|
||||
}
|
||||
|
||||
func TestAssignUnassignWorkerClusters(t *testing.T) {
|
||||
f := workerTestFixtures(t, 1*time.Second)
|
||||
defer f.done()
|
||||
|
||||
assertClusters := func(msgLabel string, clusterUUIDs ...string) {
|
||||
w, err := f.db.FetchWorker(f.ctx, f.worker.UUID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Catch doubly-reported clusters, as the maps below would hide those cases.
|
||||
assert.Len(t, w.Clusters, len(clusterUUIDs), msgLabel)
|
||||
|
||||
expectClusters := make(map[string]bool)
|
||||
for _, cid := range clusterUUIDs {
|
||||
expectClusters[cid] = true
|
||||
}
|
||||
|
||||
actualClusters := make(map[string]bool)
|
||||
for _, c := range w.Clusters {
|
||||
actualClusters[c.UUID] = true
|
||||
}
|
||||
|
||||
assert.Equal(t, expectClusters, actualClusters, msgLabel)
|
||||
}
|
||||
|
||||
secondCluster := WorkerCluster{
|
||||
UUID: uuid.New(),
|
||||
Name: "arbeiderscluster",
|
||||
Description: "Worker cluster in Dutch",
|
||||
}
|
||||
|
||||
require.NoError(t, f.db.CreateWorkerCluster(f.ctx, &secondCluster))
|
||||
|
||||
// By default the Worker should not be part of a cluster.
|
||||
assertClusters("default cluster assignment")
|
||||
|
||||
require.NoError(t, f.db.WorkerSetClusters(f.ctx, f.worker, []string{f.cluster.UUID}))
|
||||
assertClusters("setting one cluster", f.cluster.UUID)
|
||||
|
||||
// Double assignments should also just work.
|
||||
require.NoError(t, f.db.WorkerSetClusters(f.ctx, f.worker, []string{f.cluster.UUID, f.cluster.UUID}))
|
||||
assertClusters("setting twice the same cluster", f.cluster.UUID)
|
||||
|
||||
// Multiple cluster memberships.
|
||||
require.NoError(t, f.db.WorkerSetClusters(f.ctx, f.worker, []string{f.cluster.UUID, secondCluster.UUID}))
|
||||
assertClusters("setting two different clusters", f.cluster.UUID, secondCluster.UUID)
|
||||
|
||||
// Remove memberships.
|
||||
require.NoError(t, f.db.WorkerSetClusters(f.ctx, f.worker, []string{secondCluster.UUID}))
|
||||
assertClusters("unassigning from first cluster", secondCluster.UUID)
|
||||
require.NoError(t, f.db.WorkerSetClusters(f.ctx, f.worker, []string{}))
|
||||
assertClusters("unassigning from second cluster")
|
||||
}
|
||||
|
||||
func TestSaveWorkerCluster(t *testing.T) {
|
||||
f := workerTestFixtures(t, 1*time.Second)
|
||||
defer f.done()
|
||||
|
||||
f.cluster.Name = "übercluster"
|
||||
f.cluster.Description = "ʻO kēlā hui ma laila"
|
||||
require.NoError(t, f.db.SaveWorkerCluster(f.ctx, f.cluster))
|
||||
|
||||
fetched, err := f.db.FetchWorkerCluster(f.ctx, f.cluster.UUID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, f.cluster.Name, fetched.Name)
|
||||
assert.Equal(t, f.cluster.Description, fetched.Description)
|
||||
}
|
||||
|
||||
func TestDeleteWorkerClusterWithWorkersAssigned(t *testing.T) {
|
||||
f := workerTestFixtures(t, 1*time.Second)
|
||||
defer f.done()
|
||||
|
||||
// Assign the worker.
|
||||
require.NoError(t, f.db.WorkerSetClusters(f.ctx, f.worker, []string{f.cluster.UUID}))
|
||||
|
||||
// Delete the cluster.
|
||||
require.NoError(t, f.db.DeleteWorkerCluster(f.ctx, f.cluster.UUID))
|
||||
|
||||
// Check the Worker has been unassigned from the cluster.
|
||||
w, err := f.db.FetchWorker(f.ctx, f.worker.UUID)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, w.Clusters)
|
||||
}
|
@ -16,7 +16,7 @@ type Worker struct {
|
||||
Model
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
|
||||
UUID string `gorm:"type:char(36);default:'';unique;index;default:''"`
|
||||
UUID string `gorm:"type:char(36);default:'';unique;index"`
|
||||
Secret string `gorm:"type:varchar(255);default:''"`
|
||||
Name string `gorm:"type:varchar(64);default:''"`
|
||||
|
||||
@ -30,6 +30,8 @@ type Worker struct {
|
||||
LazyStatusRequest bool `gorm:"type:smallint;default:0"`
|
||||
|
||||
SupportedTaskTypes string `gorm:"type:varchar(255);default:''"` // comma-separated list of task types.
|
||||
|
||||
Clusters []*WorkerCluster `gorm:"many2many:worker_cluster_membership;constraint:OnDelete:CASCADE"`
|
||||
}
|
||||
|
||||
func (w *Worker) Identifier() string {
|
||||
@ -71,6 +73,7 @@ func (db *DB) CreateWorker(ctx context.Context, w *Worker) error {
|
||||
func (db *DB) FetchWorker(ctx context.Context, uuid string) (*Worker, error) {
|
||||
w := Worker{}
|
||||
tx := db.gormDB.WithContext(ctx).
|
||||
Preload("Clusters").
|
||||
First(&w, "uuid = ?", uuid)
|
||||
if tx.Error != nil {
|
||||
return nil, workerError(tx.Error, "fetching worker")
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"git.blender.org/flamenco/internal/uuid"
|
||||
"git.blender.org/flamenco/pkg/api"
|
||||
@ -317,3 +318,19 @@ func TestDeleteWorker(t *testing.T) {
|
||||
assert.True(t, fetchedTask.Worker.DeletedAt.Valid)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteWorkerWithClusterAssigned(t *testing.T) {
|
||||
f := workerTestFixtures(t, 1*time.Second)
|
||||
defer f.done()
|
||||
|
||||
// Assign the worker.
|
||||
require.NoError(t, f.db.WorkerSetClusters(f.ctx, f.worker, []string{f.cluster.UUID}))
|
||||
|
||||
// Delete the Worker.
|
||||
require.NoError(t, f.db.DeleteWorker(f.ctx, f.worker.UUID))
|
||||
|
||||
// Check the Worker has been unassigned from the cluster.
|
||||
cluster, err := f.db.FetchWorkerCluster(f.ctx, f.cluster.UUID)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, cluster.Workers)
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ func NewWorkerUpdate(worker *persistence.Worker) api.SocketIOWorkerUpdate {
|
||||
workerUpdate.LastSeen = &worker.LastSeenAt
|
||||
}
|
||||
|
||||
// TODO: add cluster IDs.
|
||||
|
||||
return workerUpdate
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,11 @@ var (
|
||||
errURLWithoutHostName = errors.New("manager URL should contain a host name")
|
||||
)
|
||||
|
||||
const (
|
||||
var (
|
||||
// config- and credentialsFilename are used to specify where flamenco will
|
||||
// write its config/credentials file. If the path is not absolute, it will
|
||||
// use the flamenco binary location as the relative root path. These are not
|
||||
// intended to be changed during runtime.
|
||||
credentialsFilename = "flamenco-worker-credentials.yaml"
|
||||
configFilename = "flamenco-worker.yaml"
|
||||
)
|
||||
|
180
internal/worker/mocks/client.gen.go
generated
180
internal/worker/mocks/client.gen.go
generated
@ -116,6 +116,46 @@ func (mr *MockFlamencoClientMockRecorder) CheckSharedStoragePathWithResponse(arg
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckSharedStoragePathWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).CheckSharedStoragePathWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// CreateWorkerClusterWithBodyWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) CreateWorkerClusterWithBodyWithResponse(arg0 context.Context, arg1 string, arg2 io.Reader, arg3 ...api.RequestEditorFn) (*api.CreateWorkerClusterResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1, arg2}
|
||||
for _, a := range arg3 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "CreateWorkerClusterWithBodyWithResponse", varargs...)
|
||||
ret0, _ := ret[0].(*api.CreateWorkerClusterResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateWorkerClusterWithBodyWithResponse indicates an expected call of CreateWorkerClusterWithBodyWithResponse.
|
||||
func (mr *MockFlamencoClientMockRecorder) CreateWorkerClusterWithBodyWithResponse(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1, arg2}, arg3...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateWorkerClusterWithBodyWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).CreateWorkerClusterWithBodyWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// CreateWorkerClusterWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) CreateWorkerClusterWithResponse(arg0 context.Context, arg1 api.CreateWorkerClusterJSONRequestBody, arg2 ...api.RequestEditorFn) (*api.CreateWorkerClusterResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "CreateWorkerClusterWithResponse", varargs...)
|
||||
ret0, _ := ret[0].(*api.CreateWorkerClusterResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// CreateWorkerClusterWithResponse indicates an expected call of CreateWorkerClusterWithResponse.
|
||||
func (mr *MockFlamencoClientMockRecorder) CreateWorkerClusterWithResponse(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateWorkerClusterWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).CreateWorkerClusterWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// DeleteJobWhatWouldItDoWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) DeleteJobWhatWouldItDoWithResponse(arg0 context.Context, arg1 string, arg2 ...api.RequestEditorFn) (*api.DeleteJobWhatWouldItDoResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -156,6 +196,26 @@ func (mr *MockFlamencoClientMockRecorder) DeleteJobWithResponse(arg0, arg1 inter
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteJobWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).DeleteJobWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// DeleteWorkerClusterWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) DeleteWorkerClusterWithResponse(arg0 context.Context, arg1 string, arg2 ...api.RequestEditorFn) (*api.DeleteWorkerClusterResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "DeleteWorkerClusterWithResponse", varargs...)
|
||||
ret0, _ := ret[0].(*api.DeleteWorkerClusterResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DeleteWorkerClusterWithResponse indicates an expected call of DeleteWorkerClusterWithResponse.
|
||||
func (mr *MockFlamencoClientMockRecorder) DeleteWorkerClusterWithResponse(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWorkerClusterWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).DeleteWorkerClusterWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// DeleteWorkerWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) DeleteWorkerWithResponse(arg0 context.Context, arg1 string, arg2 ...api.RequestEditorFn) (*api.DeleteWorkerResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -336,6 +396,46 @@ func (mr *MockFlamencoClientMockRecorder) FetchTaskWithResponse(arg0, arg1 inter
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchTaskWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).FetchTaskWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// FetchWorkerClusterWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) FetchWorkerClusterWithResponse(arg0 context.Context, arg1 string, arg2 ...api.RequestEditorFn) (*api.FetchWorkerClusterResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1}
|
||||
for _, a := range arg2 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "FetchWorkerClusterWithResponse", varargs...)
|
||||
ret0, _ := ret[0].(*api.FetchWorkerClusterResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FetchWorkerClusterWithResponse indicates an expected call of FetchWorkerClusterWithResponse.
|
||||
func (mr *MockFlamencoClientMockRecorder) FetchWorkerClusterWithResponse(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchWorkerClusterWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).FetchWorkerClusterWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// FetchWorkerClustersWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) FetchWorkerClustersWithResponse(arg0 context.Context, arg1 ...api.RequestEditorFn) (*api.FetchWorkerClustersResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0}
|
||||
for _, a := range arg1 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "FetchWorkerClustersWithResponse", varargs...)
|
||||
ret0, _ := ret[0].(*api.FetchWorkerClustersResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// FetchWorkerClustersWithResponse indicates an expected call of FetchWorkerClustersWithResponse.
|
||||
func (mr *MockFlamencoClientMockRecorder) FetchWorkerClustersWithResponse(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0}, arg1...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchWorkerClustersWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).FetchWorkerClustersWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// FetchWorkerSleepScheduleWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) FetchWorkerSleepScheduleWithResponse(arg0 context.Context, arg1 string, arg2 ...api.RequestEditorFn) (*api.FetchWorkerSleepScheduleResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -916,6 +1016,46 @@ func (mr *MockFlamencoClientMockRecorder) SetTaskStatusWithResponse(arg0, arg1,
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTaskStatusWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).SetTaskStatusWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// SetWorkerClustersWithBodyWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) SetWorkerClustersWithBodyWithResponse(arg0 context.Context, arg1, arg2 string, arg3 io.Reader, arg4 ...api.RequestEditorFn) (*api.SetWorkerClustersResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1, arg2, arg3}
|
||||
for _, a := range arg4 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "SetWorkerClustersWithBodyWithResponse", varargs...)
|
||||
ret0, _ := ret[0].(*api.SetWorkerClustersResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SetWorkerClustersWithBodyWithResponse indicates an expected call of SetWorkerClustersWithBodyWithResponse.
|
||||
func (mr *MockFlamencoClientMockRecorder) SetWorkerClustersWithBodyWithResponse(arg0, arg1, arg2, arg3 interface{}, arg4 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1, arg2, arg3}, arg4...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWorkerClustersWithBodyWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).SetWorkerClustersWithBodyWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// SetWorkerClustersWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) SetWorkerClustersWithResponse(arg0 context.Context, arg1 string, arg2 api.SetWorkerClustersJSONRequestBody, arg3 ...api.RequestEditorFn) (*api.SetWorkerClustersResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1, arg2}
|
||||
for _, a := range arg3 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "SetWorkerClustersWithResponse", varargs...)
|
||||
ret0, _ := ret[0].(*api.SetWorkerClustersResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// SetWorkerClustersWithResponse indicates an expected call of SetWorkerClustersWithResponse.
|
||||
func (mr *MockFlamencoClientMockRecorder) SetWorkerClustersWithResponse(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1, arg2}, arg3...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWorkerClustersWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).SetWorkerClustersWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// SetWorkerSleepScheduleWithBodyWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) SetWorkerSleepScheduleWithBodyWithResponse(arg0 context.Context, arg1, arg2 string, arg3 io.Reader, arg4 ...api.RequestEditorFn) (*api.SetWorkerSleepScheduleResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -1316,6 +1456,46 @@ func (mr *MockFlamencoClientMockRecorder) TaskUpdateWithResponse(arg0, arg1, arg
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TaskUpdateWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).TaskUpdateWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// UpdateWorkerClusterWithBodyWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) UpdateWorkerClusterWithBodyWithResponse(arg0 context.Context, arg1, arg2 string, arg3 io.Reader, arg4 ...api.RequestEditorFn) (*api.UpdateWorkerClusterResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1, arg2, arg3}
|
||||
for _, a := range arg4 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "UpdateWorkerClusterWithBodyWithResponse", varargs...)
|
||||
ret0, _ := ret[0].(*api.UpdateWorkerClusterResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateWorkerClusterWithBodyWithResponse indicates an expected call of UpdateWorkerClusterWithBodyWithResponse.
|
||||
func (mr *MockFlamencoClientMockRecorder) UpdateWorkerClusterWithBodyWithResponse(arg0, arg1, arg2, arg3 interface{}, arg4 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1, arg2, arg3}, arg4...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkerClusterWithBodyWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).UpdateWorkerClusterWithBodyWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// UpdateWorkerClusterWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) UpdateWorkerClusterWithResponse(arg0 context.Context, arg1 string, arg2 api.UpdateWorkerClusterJSONRequestBody, arg3 ...api.RequestEditorFn) (*api.UpdateWorkerClusterResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []interface{}{arg0, arg1, arg2}
|
||||
for _, a := range arg3 {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "UpdateWorkerClusterWithResponse", varargs...)
|
||||
ret0, _ := ret[0].(*api.UpdateWorkerClusterResponse)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateWorkerClusterWithResponse indicates an expected call of UpdateWorkerClusterWithResponse.
|
||||
func (mr *MockFlamencoClientMockRecorder) UpdateWorkerClusterWithResponse(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]interface{}{arg0, arg1, arg2}, arg3...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkerClusterWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).UpdateWorkerClusterWithResponse), varargs...)
|
||||
}
|
||||
|
||||
// WorkerStateChangedWithBodyWithResponse mocks base method.
|
||||
func (m *MockFlamencoClient) WorkerStateChangedWithBodyWithResponse(arg0 context.Context, arg1 string, arg2 io.Reader, arg3 ...api.RequestEditorFn) (*api.WorkerStateChangedResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -565,6 +565,33 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/api/v3/worker-mgt/workers/{worker_id}/setclusters:
|
||||
summary: Update the cluster membership of this Worker.
|
||||
post:
|
||||
operationId: setWorkerClusters
|
||||
tags: [worker-mgt]
|
||||
parameters:
|
||||
- name: worker_id
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
requestBody:
|
||||
description: The list of cluster IDs this worker should be a member of.
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/WorkerClusterChangeRequest"
|
||||
responses:
|
||||
"204":
|
||||
description: Status change was accepted.
|
||||
default:
|
||||
description: Unexpected error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/api/v3/worker-mgt/workers/{worker_id}/sleep-schedule:
|
||||
summary: Get or update the worker's sleep schedule.
|
||||
get:
|
||||
@ -615,6 +642,91 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
/api/v3/worker-mgt/clusters:
|
||||
summary: Manage worker clusters.
|
||||
get:
|
||||
operationId: fetchWorkerClusters
|
||||
summary: Get list of worker clusters.
|
||||
tags: [worker-mgt]
|
||||
responses:
|
||||
"200":
|
||||
description: Worker clusters.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/WorkerClusterList" }
|
||||
post:
|
||||
operationId: createWorkerCluster
|
||||
summary: Create a new worker cluster.
|
||||
tags: [worker-mgt]
|
||||
requestBody:
|
||||
description: The worker cluster.
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/WorkerCluster"
|
||||
responses:
|
||||
"200":
|
||||
description: The cluster was created. The created cluster is returned, so that the caller can know its UUID.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/WorkerCluster" }
|
||||
default:
|
||||
description: Error message
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Error" }
|
||||
|
||||
/api/v3/worker-mgt/cluster/{cluster_id}:
|
||||
summary: Get, update, or delete a worker cluster.
|
||||
parameters:
|
||||
- name: cluster_id
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string, format: uuid }
|
||||
get:
|
||||
operationId: fetchWorkerCluster
|
||||
summary: Get a single worker cluster.
|
||||
tags: [worker-mgt]
|
||||
responses:
|
||||
"200":
|
||||
description: The worker cluster.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/WorkerCluster" }
|
||||
put:
|
||||
operationId: updateWorkerCluster
|
||||
summary: Update an existing worker cluster.
|
||||
tags: [worker-mgt]
|
||||
requestBody:
|
||||
description: The updated worker cluster.
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/WorkerCluster"
|
||||
responses:
|
||||
"204":
|
||||
description: The cluster update has been stored.
|
||||
default:
|
||||
description: Error message
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: "#/components/schemas/Error" }
|
||||
delete:
|
||||
operationId: deleteWorkerCluster
|
||||
summary: Remove this worker cluster. This unassigns all workers from the cluster and removes it.
|
||||
tags: [worker-mgt]
|
||||
responses:
|
||||
"204":
|
||||
description: The cluster has been removed.
|
||||
default:
|
||||
description: Unexpected error.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
## Jobs
|
||||
|
||||
/api/v3/jobs/types:
|
||||
@ -1394,6 +1506,12 @@ components:
|
||||
type: array
|
||||
items: { type: string }
|
||||
name: { type: string }
|
||||
example:
|
||||
"name": "example-worker"
|
||||
"secret": "do-not-tell-anyone"
|
||||
"platform": "linux"
|
||||
"software": "3.2"
|
||||
"supported_task_types": ["blender", "ffmpeg", "file-management", "misc"]
|
||||
|
||||
RegisteredWorker:
|
||||
type: object
|
||||
@ -1681,6 +1799,13 @@ components:
|
||||
test/debug scripts easier, as they can use a static document on all
|
||||
platforms.
|
||||
"storage": { $ref: "#/components/schemas/JobStorageInfo" }
|
||||
"worker_cluster":
|
||||
type: string
|
||||
format: uuid
|
||||
description: >
|
||||
Worker Cluster that should execute this job. When a cluster ID is
|
||||
given, only Workers in that cluster will be scheduled to work on it.
|
||||
If empty or ommitted, all workers can work on this job.
|
||||
required: [name, type, priority, submitter_platform]
|
||||
example:
|
||||
type: "simple-blender-render"
|
||||
@ -1794,7 +1919,7 @@ components:
|
||||
description: Filter by job settings, using `LIKE` notation.
|
||||
example:
|
||||
"limit": 5
|
||||
"order_by": ["updated", "status"]
|
||||
"order_by": ["updated_at", "status"]
|
||||
"status_in": ["active", "queued", "failed"]
|
||||
"metadata": { project: "Sprite Fright" }
|
||||
|
||||
@ -2298,6 +2423,10 @@ components:
|
||||
type: array
|
||||
items: { type: string }
|
||||
"task": { $ref: "#/components/schemas/WorkerTask" }
|
||||
"clusters":
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/WorkerCluster" }
|
||||
description: Clusters of which this Worker is a member.
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
@ -2351,6 +2480,47 @@ components:
|
||||
start_time: "09:00"
|
||||
end_time: "18:00"
|
||||
|
||||
WorkerCluster:
|
||||
type: object
|
||||
description: >
|
||||
Cluster of workers. A job can optionally specify which cluster it should
|
||||
be limited to. Workers can be part of multiple clusters simultaneously.
|
||||
properties:
|
||||
"id":
|
||||
type: string
|
||||
format: uuid
|
||||
description: >
|
||||
UUID of the cluster. Can be ommitted when creating a new cluster, in
|
||||
which case a random UUID will be assigned.
|
||||
"name":
|
||||
type: string
|
||||
"description":
|
||||
type: string
|
||||
required: [name]
|
||||
example:
|
||||
name: GPU-EEVEE
|
||||
description: All workers that can do GPU rendering with EEVEE.
|
||||
|
||||
WorkerClusterList:
|
||||
type: object
|
||||
properties:
|
||||
"clusters":
|
||||
type: array
|
||||
items: { $ref: "#/components/schemas/WorkerCluster" }
|
||||
|
||||
WorkerClusterChangeRequest:
|
||||
type: object
|
||||
description: Request to change which clusters this Worker is assigned to.
|
||||
properties:
|
||||
"cluster_ids":
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
required: [cluster_ids]
|
||||
example:
|
||||
"cluster_ids": ["4312d68c-ea6d-4566-9bf6-e9f09be48ceb"]
|
||||
|
||||
securitySchemes:
|
||||
worker_auth:
|
||||
description: Username is the worker ID, password is the secret given at worker registration.
|
||||
|
759
pkg/api/openapi_client.gen.go
generated
759
pkg/api/openapi_client.gen.go
generated
@ -212,6 +212,25 @@ type ClientInterface interface {
|
||||
// GetVersion request
|
||||
GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// DeleteWorkerCluster request
|
||||
DeleteWorkerCluster(ctx context.Context, clusterId string, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// FetchWorkerCluster request
|
||||
FetchWorkerCluster(ctx context.Context, clusterId string, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// UpdateWorkerCluster request with any body
|
||||
UpdateWorkerClusterWithBody(ctx context.Context, clusterId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
UpdateWorkerCluster(ctx context.Context, clusterId string, body UpdateWorkerClusterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// FetchWorkerClusters request
|
||||
FetchWorkerClusters(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// CreateWorkerCluster request with any body
|
||||
CreateWorkerClusterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
CreateWorkerCluster(ctx context.Context, body CreateWorkerClusterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// FetchWorkers request
|
||||
FetchWorkers(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
@ -221,6 +240,11 @@ type ClientInterface interface {
|
||||
// FetchWorker request
|
||||
FetchWorker(ctx context.Context, workerId string, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// SetWorkerClusters request with any body
|
||||
SetWorkerClustersWithBody(ctx context.Context, workerId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
SetWorkerClusters(ctx context.Context, workerId string, body SetWorkerClustersJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
// RequestWorkerStatusChange request with any body
|
||||
RequestWorkerStatusChangeWithBody(ctx context.Context, workerId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
|
||||
|
||||
@ -803,6 +827,90 @@ func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn)
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteWorkerCluster(ctx context.Context, clusterId string, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewDeleteWorkerClusterRequest(c.Server, clusterId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) FetchWorkerCluster(ctx context.Context, clusterId string, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewFetchWorkerClusterRequest(c.Server, clusterId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) UpdateWorkerClusterWithBody(ctx context.Context, clusterId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewUpdateWorkerClusterRequestWithBody(c.Server, clusterId, contentType, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) UpdateWorkerCluster(ctx context.Context, clusterId string, body UpdateWorkerClusterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewUpdateWorkerClusterRequest(c.Server, clusterId, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) FetchWorkerClusters(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewFetchWorkerClustersRequest(c.Server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) CreateWorkerClusterWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewCreateWorkerClusterRequestWithBody(c.Server, contentType, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) CreateWorkerCluster(ctx context.Context, body CreateWorkerClusterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewCreateWorkerClusterRequest(c.Server, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) FetchWorkers(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewFetchWorkersRequest(c.Server)
|
||||
if err != nil {
|
||||
@ -839,6 +947,30 @@ func (c *Client) FetchWorker(ctx context.Context, workerId string, reqEditors ..
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) SetWorkerClustersWithBody(ctx context.Context, workerId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewSetWorkerClustersRequestWithBody(c.Server, workerId, contentType, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) SetWorkerClusters(ctx context.Context, workerId string, body SetWorkerClustersJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewSetWorkerClustersRequest(c.Server, workerId, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Client.Do(req)
|
||||
}
|
||||
|
||||
func (c *Client) RequestWorkerStatusChangeWithBody(ctx context.Context, workerId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
|
||||
req, err := NewRequestWorkerStatusChangeRequestWithBody(c.Server, workerId, contentType, body)
|
||||
if err != nil {
|
||||
@ -2277,6 +2409,188 @@ func NewGetVersionRequest(server string) (*http.Request, error) {
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewDeleteWorkerClusterRequest generates requests for DeleteWorkerCluster
|
||||
func NewDeleteWorkerClusterRequest(server string, clusterId string) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
var pathParam0 string
|
||||
|
||||
pathParam0, err = runtime.StyleParamWithLocation("simple", false, "cluster_id", runtime.ParamLocationPath, clusterId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/api/v3/worker-mgt/cluster/%s", pathParam0)
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("DELETE", queryURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewFetchWorkerClusterRequest generates requests for FetchWorkerCluster
|
||||
func NewFetchWorkerClusterRequest(server string, clusterId string) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
var pathParam0 string
|
||||
|
||||
pathParam0, err = runtime.StyleParamWithLocation("simple", false, "cluster_id", runtime.ParamLocationPath, clusterId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/api/v3/worker-mgt/cluster/%s", pathParam0)
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", queryURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewUpdateWorkerClusterRequest calls the generic UpdateWorkerCluster builder with application/json body
|
||||
func NewUpdateWorkerClusterRequest(server string, clusterId string, body UpdateWorkerClusterJSONRequestBody) (*http.Request, error) {
|
||||
var bodyReader io.Reader
|
||||
buf, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodyReader = bytes.NewReader(buf)
|
||||
return NewUpdateWorkerClusterRequestWithBody(server, clusterId, "application/json", bodyReader)
|
||||
}
|
||||
|
||||
// NewUpdateWorkerClusterRequestWithBody generates requests for UpdateWorkerCluster with any type of body
|
||||
func NewUpdateWorkerClusterRequestWithBody(server string, clusterId string, contentType string, body io.Reader) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
var pathParam0 string
|
||||
|
||||
pathParam0, err = runtime.StyleParamWithLocation("simple", false, "cluster_id", runtime.ParamLocationPath, clusterId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/api/v3/worker-mgt/cluster/%s", pathParam0)
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PUT", queryURL.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", contentType)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewFetchWorkerClustersRequest generates requests for FetchWorkerClusters
|
||||
func NewFetchWorkerClustersRequest(server string) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/api/v3/worker-mgt/clusters")
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", queryURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewCreateWorkerClusterRequest calls the generic CreateWorkerCluster builder with application/json body
|
||||
func NewCreateWorkerClusterRequest(server string, body CreateWorkerClusterJSONRequestBody) (*http.Request, error) {
|
||||
var bodyReader io.Reader
|
||||
buf, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodyReader = bytes.NewReader(buf)
|
||||
return NewCreateWorkerClusterRequestWithBody(server, "application/json", bodyReader)
|
||||
}
|
||||
|
||||
// NewCreateWorkerClusterRequestWithBody generates requests for CreateWorkerCluster with any type of body
|
||||
func NewCreateWorkerClusterRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/api/v3/worker-mgt/clusters")
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", queryURL.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", contentType)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewFetchWorkersRequest generates requests for FetchWorkers
|
||||
func NewFetchWorkersRequest(server string) (*http.Request, error) {
|
||||
var err error
|
||||
@ -2372,6 +2686,53 @@ func NewFetchWorkerRequest(server string, workerId string) (*http.Request, error
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewSetWorkerClustersRequest calls the generic SetWorkerClusters builder with application/json body
|
||||
func NewSetWorkerClustersRequest(server string, workerId string, body SetWorkerClustersJSONRequestBody) (*http.Request, error) {
|
||||
var bodyReader io.Reader
|
||||
buf, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodyReader = bytes.NewReader(buf)
|
||||
return NewSetWorkerClustersRequestWithBody(server, workerId, "application/json", bodyReader)
|
||||
}
|
||||
|
||||
// NewSetWorkerClustersRequestWithBody generates requests for SetWorkerClusters with any type of body
|
||||
func NewSetWorkerClustersRequestWithBody(server string, workerId string, contentType string, body io.Reader) (*http.Request, error) {
|
||||
var err error
|
||||
|
||||
var pathParam0 string
|
||||
|
||||
pathParam0, err = runtime.StyleParamWithLocation("simple", false, "worker_id", runtime.ParamLocationPath, workerId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serverURL, err := url.Parse(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
operationPath := fmt.Sprintf("/api/v3/worker-mgt/workers/%s/setclusters", pathParam0)
|
||||
if operationPath[0] == '/' {
|
||||
operationPath = "." + operationPath
|
||||
}
|
||||
|
||||
queryURL, err := serverURL.Parse(operationPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", queryURL.String(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", contentType)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// NewRequestWorkerStatusChangeRequest calls the generic RequestWorkerStatusChange builder with application/json body
|
||||
func NewRequestWorkerStatusChangeRequest(server string, workerId string, body RequestWorkerStatusChangeJSONRequestBody) (*http.Request, error) {
|
||||
var bodyReader io.Reader
|
||||
@ -3028,6 +3389,25 @@ type ClientWithResponsesInterface interface {
|
||||
// GetVersion request
|
||||
GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error)
|
||||
|
||||
// DeleteWorkerCluster request
|
||||
DeleteWorkerClusterWithResponse(ctx context.Context, clusterId string, reqEditors ...RequestEditorFn) (*DeleteWorkerClusterResponse, error)
|
||||
|
||||
// FetchWorkerCluster request
|
||||
FetchWorkerClusterWithResponse(ctx context.Context, clusterId string, reqEditors ...RequestEditorFn) (*FetchWorkerClusterResponse, error)
|
||||
|
||||
// UpdateWorkerCluster request with any body
|
||||
UpdateWorkerClusterWithBodyWithResponse(ctx context.Context, clusterId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateWorkerClusterResponse, error)
|
||||
|
||||
UpdateWorkerClusterWithResponse(ctx context.Context, clusterId string, body UpdateWorkerClusterJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateWorkerClusterResponse, error)
|
||||
|
||||
// FetchWorkerClusters request
|
||||
FetchWorkerClustersWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*FetchWorkerClustersResponse, error)
|
||||
|
||||
// CreateWorkerCluster request with any body
|
||||
CreateWorkerClusterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateWorkerClusterResponse, error)
|
||||
|
||||
CreateWorkerClusterWithResponse(ctx context.Context, body CreateWorkerClusterJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateWorkerClusterResponse, error)
|
||||
|
||||
// FetchWorkers request
|
||||
FetchWorkersWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*FetchWorkersResponse, error)
|
||||
|
||||
@ -3037,6 +3417,11 @@ type ClientWithResponsesInterface interface {
|
||||
// FetchWorker request
|
||||
FetchWorkerWithResponse(ctx context.Context, workerId string, reqEditors ...RequestEditorFn) (*FetchWorkerResponse, error)
|
||||
|
||||
// SetWorkerClusters request with any body
|
||||
SetWorkerClustersWithBodyWithResponse(ctx context.Context, workerId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetWorkerClustersResponse, error)
|
||||
|
||||
SetWorkerClustersWithResponse(ctx context.Context, workerId string, body SetWorkerClustersJSONRequestBody, reqEditors ...RequestEditorFn) (*SetWorkerClustersResponse, error)
|
||||
|
||||
// RequestWorkerStatusChange request with any body
|
||||
RequestWorkerStatusChangeWithBodyWithResponse(ctx context.Context, workerId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RequestWorkerStatusChangeResponse, error)
|
||||
|
||||
@ -3814,6 +4199,117 @@ func (r GetVersionResponse) StatusCode() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
type DeleteWorkerClusterResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSONDefault *Error
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r DeleteWorkerClusterResponse) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r DeleteWorkerClusterResponse) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type FetchWorkerClusterResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSON200 *WorkerCluster
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r FetchWorkerClusterResponse) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r FetchWorkerClusterResponse) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type UpdateWorkerClusterResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSONDefault *Error
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r UpdateWorkerClusterResponse) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r UpdateWorkerClusterResponse) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type FetchWorkerClustersResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSON200 *WorkerClusterList
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r FetchWorkerClustersResponse) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r FetchWorkerClustersResponse) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type CreateWorkerClusterResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSON200 *WorkerCluster
|
||||
JSONDefault *Error
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r CreateWorkerClusterResponse) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r CreateWorkerClusterResponse) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type FetchWorkersResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
@ -3880,6 +4376,28 @@ func (r FetchWorkerResponse) StatusCode() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
type SetWorkerClustersResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
JSONDefault *Error
|
||||
}
|
||||
|
||||
// Status returns HTTPResponse.Status
|
||||
func (r SetWorkerClustersResponse) Status() string {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.Status
|
||||
}
|
||||
return http.StatusText(0)
|
||||
}
|
||||
|
||||
// StatusCode returns HTTPResponse.StatusCode
|
||||
func (r SetWorkerClustersResponse) StatusCode() int {
|
||||
if r.HTTPResponse != nil {
|
||||
return r.HTTPResponse.StatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type RequestWorkerStatusChangeResponse struct {
|
||||
Body []byte
|
||||
HTTPResponse *http.Response
|
||||
@ -4561,6 +5079,67 @@ func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEdi
|
||||
return ParseGetVersionResponse(rsp)
|
||||
}
|
||||
|
||||
// DeleteWorkerClusterWithResponse request returning *DeleteWorkerClusterResponse
|
||||
func (c *ClientWithResponses) DeleteWorkerClusterWithResponse(ctx context.Context, clusterId string, reqEditors ...RequestEditorFn) (*DeleteWorkerClusterResponse, error) {
|
||||
rsp, err := c.DeleteWorkerCluster(ctx, clusterId, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseDeleteWorkerClusterResponse(rsp)
|
||||
}
|
||||
|
||||
// FetchWorkerClusterWithResponse request returning *FetchWorkerClusterResponse
|
||||
func (c *ClientWithResponses) FetchWorkerClusterWithResponse(ctx context.Context, clusterId string, reqEditors ...RequestEditorFn) (*FetchWorkerClusterResponse, error) {
|
||||
rsp, err := c.FetchWorkerCluster(ctx, clusterId, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseFetchWorkerClusterResponse(rsp)
|
||||
}
|
||||
|
||||
// UpdateWorkerClusterWithBodyWithResponse request with arbitrary body returning *UpdateWorkerClusterResponse
|
||||
func (c *ClientWithResponses) UpdateWorkerClusterWithBodyWithResponse(ctx context.Context, clusterId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateWorkerClusterResponse, error) {
|
||||
rsp, err := c.UpdateWorkerClusterWithBody(ctx, clusterId, contentType, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseUpdateWorkerClusterResponse(rsp)
|
||||
}
|
||||
|
||||
func (c *ClientWithResponses) UpdateWorkerClusterWithResponse(ctx context.Context, clusterId string, body UpdateWorkerClusterJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateWorkerClusterResponse, error) {
|
||||
rsp, err := c.UpdateWorkerCluster(ctx, clusterId, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseUpdateWorkerClusterResponse(rsp)
|
||||
}
|
||||
|
||||
// FetchWorkerClustersWithResponse request returning *FetchWorkerClustersResponse
|
||||
func (c *ClientWithResponses) FetchWorkerClustersWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*FetchWorkerClustersResponse, error) {
|
||||
rsp, err := c.FetchWorkerClusters(ctx, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseFetchWorkerClustersResponse(rsp)
|
||||
}
|
||||
|
||||
// CreateWorkerClusterWithBodyWithResponse request with arbitrary body returning *CreateWorkerClusterResponse
|
||||
func (c *ClientWithResponses) CreateWorkerClusterWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateWorkerClusterResponse, error) {
|
||||
rsp, err := c.CreateWorkerClusterWithBody(ctx, contentType, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseCreateWorkerClusterResponse(rsp)
|
||||
}
|
||||
|
||||
func (c *ClientWithResponses) CreateWorkerClusterWithResponse(ctx context.Context, body CreateWorkerClusterJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateWorkerClusterResponse, error) {
|
||||
rsp, err := c.CreateWorkerCluster(ctx, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseCreateWorkerClusterResponse(rsp)
|
||||
}
|
||||
|
||||
// FetchWorkersWithResponse request returning *FetchWorkersResponse
|
||||
func (c *ClientWithResponses) FetchWorkersWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*FetchWorkersResponse, error) {
|
||||
rsp, err := c.FetchWorkers(ctx, reqEditors...)
|
||||
@ -4588,6 +5167,23 @@ func (c *ClientWithResponses) FetchWorkerWithResponse(ctx context.Context, worke
|
||||
return ParseFetchWorkerResponse(rsp)
|
||||
}
|
||||
|
||||
// SetWorkerClustersWithBodyWithResponse request with arbitrary body returning *SetWorkerClustersResponse
|
||||
func (c *ClientWithResponses) SetWorkerClustersWithBodyWithResponse(ctx context.Context, workerId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetWorkerClustersResponse, error) {
|
||||
rsp, err := c.SetWorkerClustersWithBody(ctx, workerId, contentType, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseSetWorkerClustersResponse(rsp)
|
||||
}
|
||||
|
||||
func (c *ClientWithResponses) SetWorkerClustersWithResponse(ctx context.Context, workerId string, body SetWorkerClustersJSONRequestBody, reqEditors ...RequestEditorFn) (*SetWorkerClustersResponse, error) {
|
||||
rsp, err := c.SetWorkerClusters(ctx, workerId, body, reqEditors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ParseSetWorkerClustersResponse(rsp)
|
||||
}
|
||||
|
||||
// RequestWorkerStatusChangeWithBodyWithResponse request with arbitrary body returning *RequestWorkerStatusChangeResponse
|
||||
func (c *ClientWithResponses) RequestWorkerStatusChangeWithBodyWithResponse(ctx context.Context, workerId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RequestWorkerStatusChangeResponse, error) {
|
||||
rsp, err := c.RequestWorkerStatusChangeWithBody(ctx, workerId, contentType, body, reqEditors...)
|
||||
@ -5714,6 +6310,143 @@ func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) {
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseDeleteWorkerClusterResponse parses an HTTP response from a DeleteWorkerClusterWithResponse call
|
||||
func ParseDeleteWorkerClusterResponse(rsp *http.Response) (*DeleteWorkerClusterResponse, error) {
|
||||
bodyBytes, err := ioutil.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &DeleteWorkerClusterResponse{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true:
|
||||
var dest Error
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSONDefault = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseFetchWorkerClusterResponse parses an HTTP response from a FetchWorkerClusterWithResponse call
|
||||
func ParseFetchWorkerClusterResponse(rsp *http.Response) (*FetchWorkerClusterResponse, error) {
|
||||
bodyBytes, err := ioutil.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &FetchWorkerClusterResponse{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
|
||||
var dest WorkerCluster
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON200 = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseUpdateWorkerClusterResponse parses an HTTP response from a UpdateWorkerClusterWithResponse call
|
||||
func ParseUpdateWorkerClusterResponse(rsp *http.Response) (*UpdateWorkerClusterResponse, error) {
|
||||
bodyBytes, err := ioutil.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &UpdateWorkerClusterResponse{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true:
|
||||
var dest Error
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSONDefault = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseFetchWorkerClustersResponse parses an HTTP response from a FetchWorkerClustersWithResponse call
|
||||
func ParseFetchWorkerClustersResponse(rsp *http.Response) (*FetchWorkerClustersResponse, error) {
|
||||
bodyBytes, err := ioutil.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &FetchWorkerClustersResponse{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
|
||||
var dest WorkerClusterList
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON200 = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseCreateWorkerClusterResponse parses an HTTP response from a CreateWorkerClusterWithResponse call
|
||||
func ParseCreateWorkerClusterResponse(rsp *http.Response) (*CreateWorkerClusterResponse, error) {
|
||||
bodyBytes, err := ioutil.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &CreateWorkerClusterResponse{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
|
||||
var dest WorkerCluster
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSON200 = &dest
|
||||
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true:
|
||||
var dest Error
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSONDefault = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseFetchWorkersResponse parses an HTTP response from a FetchWorkersWithResponse call
|
||||
func ParseFetchWorkersResponse(rsp *http.Response) (*FetchWorkersResponse, error) {
|
||||
bodyBytes, err := ioutil.ReadAll(rsp.Body)
|
||||
@ -5792,6 +6525,32 @@ func ParseFetchWorkerResponse(rsp *http.Response) (*FetchWorkerResponse, error)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseSetWorkerClustersResponse parses an HTTP response from a SetWorkerClustersWithResponse call
|
||||
func ParseSetWorkerClustersResponse(rsp *http.Response) (*SetWorkerClustersResponse, error) {
|
||||
bodyBytes, err := ioutil.ReadAll(rsp.Body)
|
||||
defer func() { _ = rsp.Body.Close() }()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &SetWorkerClustersResponse{
|
||||
Body: bodyBytes,
|
||||
HTTPResponse: rsp,
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true:
|
||||
var dest Error
|
||||
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response.JSONDefault = &dest
|
||||
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ParseRequestWorkerStatusChangeResponse parses an HTTP response from a RequestWorkerStatusChangeWithResponse call
|
||||
func ParseRequestWorkerStatusChangeResponse(rsp *http.Response) (*RequestWorkerStatusChangeResponse, error) {
|
||||
bodyBytes, err := ioutil.ReadAll(rsp.Body)
|
||||
|
106
pkg/api/openapi_server.gen.go
generated
106
pkg/api/openapi_server.gen.go
generated
@ -110,6 +110,21 @@ type ServerInterface interface {
|
||||
// Get the Flamenco version of this Manager
|
||||
// (GET /api/v3/version)
|
||||
GetVersion(ctx echo.Context) error
|
||||
// Remove this worker cluster. This unassigns all workers from the cluster and removes it.
|
||||
// (DELETE /api/v3/worker-mgt/cluster/{cluster_id})
|
||||
DeleteWorkerCluster(ctx echo.Context, clusterId string) error
|
||||
// Get a single worker cluster.
|
||||
// (GET /api/v3/worker-mgt/cluster/{cluster_id})
|
||||
FetchWorkerCluster(ctx echo.Context, clusterId string) error
|
||||
// Update an existing worker cluster.
|
||||
// (PUT /api/v3/worker-mgt/cluster/{cluster_id})
|
||||
UpdateWorkerCluster(ctx echo.Context, clusterId string) error
|
||||
// Get list of worker clusters.
|
||||
// (GET /api/v3/worker-mgt/clusters)
|
||||
FetchWorkerClusters(ctx echo.Context) error
|
||||
// Create a new worker cluster.
|
||||
// (POST /api/v3/worker-mgt/clusters)
|
||||
CreateWorkerCluster(ctx echo.Context) error
|
||||
// Get list of workers.
|
||||
// (GET /api/v3/worker-mgt/workers)
|
||||
FetchWorkers(ctx echo.Context) error
|
||||
@ -120,6 +135,9 @@ type ServerInterface interface {
|
||||
// (GET /api/v3/worker-mgt/workers/{worker_id})
|
||||
FetchWorker(ctx echo.Context, workerId string) error
|
||||
|
||||
// (POST /api/v3/worker-mgt/workers/{worker_id}/setclusters)
|
||||
SetWorkerClusters(ctx echo.Context, workerId string) error
|
||||
|
||||
// (POST /api/v3/worker-mgt/workers/{worker_id}/setstatus)
|
||||
RequestWorkerStatusChange(ctx echo.Context, workerId string) error
|
||||
|
||||
@ -646,6 +664,72 @@ func (w *ServerInterfaceWrapper) GetVersion(ctx echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteWorkerCluster converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) DeleteWorkerCluster(ctx echo.Context) error {
|
||||
var err error
|
||||
// ------------- Path parameter "cluster_id" -------------
|
||||
var clusterId string
|
||||
|
||||
err = runtime.BindStyledParameterWithLocation("simple", false, "cluster_id", runtime.ParamLocationPath, ctx.Param("cluster_id"), &clusterId)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter cluster_id: %s", err))
|
||||
}
|
||||
|
||||
// Invoke the callback with all the unmarshalled arguments
|
||||
err = w.Handler.DeleteWorkerCluster(ctx, clusterId)
|
||||
return err
|
||||
}
|
||||
|
||||
// FetchWorkerCluster converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) FetchWorkerCluster(ctx echo.Context) error {
|
||||
var err error
|
||||
// ------------- Path parameter "cluster_id" -------------
|
||||
var clusterId string
|
||||
|
||||
err = runtime.BindStyledParameterWithLocation("simple", false, "cluster_id", runtime.ParamLocationPath, ctx.Param("cluster_id"), &clusterId)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter cluster_id: %s", err))
|
||||
}
|
||||
|
||||
// Invoke the callback with all the unmarshalled arguments
|
||||
err = w.Handler.FetchWorkerCluster(ctx, clusterId)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateWorkerCluster converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) UpdateWorkerCluster(ctx echo.Context) error {
|
||||
var err error
|
||||
// ------------- Path parameter "cluster_id" -------------
|
||||
var clusterId string
|
||||
|
||||
err = runtime.BindStyledParameterWithLocation("simple", false, "cluster_id", runtime.ParamLocationPath, ctx.Param("cluster_id"), &clusterId)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter cluster_id: %s", err))
|
||||
}
|
||||
|
||||
// Invoke the callback with all the unmarshalled arguments
|
||||
err = w.Handler.UpdateWorkerCluster(ctx, clusterId)
|
||||
return err
|
||||
}
|
||||
|
||||
// FetchWorkerClusters converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) FetchWorkerClusters(ctx echo.Context) error {
|
||||
var err error
|
||||
|
||||
// Invoke the callback with all the unmarshalled arguments
|
||||
err = w.Handler.FetchWorkerClusters(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateWorkerCluster converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) CreateWorkerCluster(ctx echo.Context) error {
|
||||
var err error
|
||||
|
||||
// Invoke the callback with all the unmarshalled arguments
|
||||
err = w.Handler.CreateWorkerCluster(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// FetchWorkers converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) FetchWorkers(ctx echo.Context) error {
|
||||
var err error
|
||||
@ -687,6 +771,22 @@ func (w *ServerInterfaceWrapper) FetchWorker(ctx echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// SetWorkerClusters converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) SetWorkerClusters(ctx echo.Context) error {
|
||||
var err error
|
||||
// ------------- Path parameter "worker_id" -------------
|
||||
var workerId string
|
||||
|
||||
err = runtime.BindStyledParameterWithLocation("simple", false, "worker_id", runtime.ParamLocationPath, ctx.Param("worker_id"), &workerId)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter worker_id: %s", err))
|
||||
}
|
||||
|
||||
// Invoke the callback with all the unmarshalled arguments
|
||||
err = w.Handler.SetWorkerClusters(ctx, workerId)
|
||||
return err
|
||||
}
|
||||
|
||||
// RequestWorkerStatusChange converts echo context to params.
|
||||
func (w *ServerInterfaceWrapper) RequestWorkerStatusChange(ctx echo.Context) error {
|
||||
var err error
|
||||
@ -931,9 +1031,15 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
|
||||
router.GET(baseURL+"/api/v3/tasks/:task_id/logtail", wrapper.FetchTaskLogTail)
|
||||
router.POST(baseURL+"/api/v3/tasks/:task_id/setstatus", wrapper.SetTaskStatus)
|
||||
router.GET(baseURL+"/api/v3/version", wrapper.GetVersion)
|
||||
router.DELETE(baseURL+"/api/v3/worker-mgt/cluster/:cluster_id", wrapper.DeleteWorkerCluster)
|
||||
router.GET(baseURL+"/api/v3/worker-mgt/cluster/:cluster_id", wrapper.FetchWorkerCluster)
|
||||
router.PUT(baseURL+"/api/v3/worker-mgt/cluster/:cluster_id", wrapper.UpdateWorkerCluster)
|
||||
router.GET(baseURL+"/api/v3/worker-mgt/clusters", wrapper.FetchWorkerClusters)
|
||||
router.POST(baseURL+"/api/v3/worker-mgt/clusters", wrapper.CreateWorkerCluster)
|
||||
router.GET(baseURL+"/api/v3/worker-mgt/workers", wrapper.FetchWorkers)
|
||||
router.DELETE(baseURL+"/api/v3/worker-mgt/workers/:worker_id", wrapper.DeleteWorker)
|
||||
router.GET(baseURL+"/api/v3/worker-mgt/workers/:worker_id", wrapper.FetchWorker)
|
||||
router.POST(baseURL+"/api/v3/worker-mgt/workers/:worker_id/setclusters", wrapper.SetWorkerClusters)
|
||||
router.POST(baseURL+"/api/v3/worker-mgt/workers/:worker_id/setstatus", wrapper.RequestWorkerStatusChange)
|
||||
router.GET(baseURL+"/api/v3/worker-mgt/workers/:worker_id/sleep-schedule", wrapper.FetchWorkerSleepSchedule)
|
||||
router.POST(baseURL+"/api/v3/worker-mgt/workers/:worker_id/sleep-schedule", wrapper.SetWorkerSleepSchedule)
|
||||
|
416
pkg/api/openapi_spec.gen.go
generated
416
pkg/api/openapi_spec.gen.go
generated
@ -18,208 +18,220 @@ import (
|
||||
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||
var swaggerSpec = []string{
|
||||
|
||||
"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",
|
||||
"53C5gh9/+/j/AgAA//9+ZK/OI/YAAA==",
|
||||
"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",
|
||||
}
|
||||
|
||||
// GetSwagger returns the content of the embedded swagger specification file
|
||||
|
43
pkg/api/openapi_types.gen.go
generated
43
pkg/api/openapi_types.gen.go
generated
@ -648,6 +648,9 @@ type SubmittedJob struct {
|
||||
// Hash of the job type, copied from the `AvailableJobType.etag` property of the job type. The job will be rejected if this field doesn't match the actual job type on the Manager. This prevents job submission with old settings, after the job compiler script has been updated.
|
||||
// If this field is ommitted, the check is bypassed.
|
||||
TypeEtag *string `json:"type_etag,omitempty"`
|
||||
|
||||
// Worker Cluster that should execute this job. When a cluster ID is given, only Workers in that cluster will be scheduled to work on it. If empty or ommitted, all workers can work on this job.
|
||||
WorkerCluster *string `json:"worker_cluster,omitempty"`
|
||||
}
|
||||
|
||||
// The task as it exists in the Manager database, i.e. before variable replacement.
|
||||
@ -735,6 +738,9 @@ type Worker struct {
|
||||
// Embedded struct due to allOf(#/components/schemas/WorkerSummary)
|
||||
WorkerSummary `yaml:",inline"`
|
||||
// Embedded fields due to inline allOf schema
|
||||
// Clusters of which this Worker is a member.
|
||||
Clusters *[]WorkerCluster `json:"clusters,omitempty"`
|
||||
|
||||
// IP address of the Worker
|
||||
IpAddress string `json:"ip_address"`
|
||||
|
||||
@ -746,6 +752,25 @@ type Worker struct {
|
||||
Task *WorkerTask `json:"task,omitempty"`
|
||||
}
|
||||
|
||||
// Cluster of workers. A job can optionally specify which cluster it should be limited to. Workers can be part of multiple clusters simultaneously.
|
||||
type WorkerCluster struct {
|
||||
Description *string `json:"description,omitempty"`
|
||||
|
||||
// UUID of the cluster. Can be ommitted when creating a new cluster, in which case a random UUID will be assigned.
|
||||
Id *string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Request to change which clusters this Worker is assigned to.
|
||||
type WorkerClusterChangeRequest struct {
|
||||
ClusterIds []string `json:"cluster_ids"`
|
||||
}
|
||||
|
||||
// WorkerClusterList defines model for WorkerClusterList.
|
||||
type WorkerClusterList struct {
|
||||
Clusters *[]WorkerCluster `json:"clusters,omitempty"`
|
||||
}
|
||||
|
||||
// List of workers.
|
||||
type WorkerList struct {
|
||||
Workers []WorkerSummary `json:"workers"`
|
||||
@ -865,6 +890,15 @@ type ShamanFileStoreParams struct {
|
||||
// SetTaskStatusJSONBody defines parameters for SetTaskStatus.
|
||||
type SetTaskStatusJSONBody TaskStatusChange
|
||||
|
||||
// UpdateWorkerClusterJSONBody defines parameters for UpdateWorkerCluster.
|
||||
type UpdateWorkerClusterJSONBody WorkerCluster
|
||||
|
||||
// CreateWorkerClusterJSONBody defines parameters for CreateWorkerCluster.
|
||||
type CreateWorkerClusterJSONBody WorkerCluster
|
||||
|
||||
// SetWorkerClustersJSONBody defines parameters for SetWorkerClusters.
|
||||
type SetWorkerClustersJSONBody WorkerClusterChangeRequest
|
||||
|
||||
// RequestWorkerStatusChangeJSONBody defines parameters for RequestWorkerStatusChange.
|
||||
type RequestWorkerStatusChangeJSONBody WorkerStatusChangeRequest
|
||||
|
||||
@ -922,6 +956,15 @@ type ShamanCheckoutRequirementsJSONRequestBody ShamanCheckoutRequirementsJSONBod
|
||||
// SetTaskStatusJSONRequestBody defines body for SetTaskStatus for application/json ContentType.
|
||||
type SetTaskStatusJSONRequestBody SetTaskStatusJSONBody
|
||||
|
||||
// UpdateWorkerClusterJSONRequestBody defines body for UpdateWorkerCluster for application/json ContentType.
|
||||
type UpdateWorkerClusterJSONRequestBody UpdateWorkerClusterJSONBody
|
||||
|
||||
// CreateWorkerClusterJSONRequestBody defines body for CreateWorkerCluster for application/json ContentType.
|
||||
type CreateWorkerClusterJSONRequestBody CreateWorkerClusterJSONBody
|
||||
|
||||
// SetWorkerClustersJSONRequestBody defines body for SetWorkerClusters for application/json ContentType.
|
||||
type SetWorkerClustersJSONRequestBody SetWorkerClustersJSONBody
|
||||
|
||||
// RequestWorkerStatusChangeJSONRequestBody defines body for RequestWorkerStatusChange for application/json ContentType.
|
||||
type RequestWorkerStatusChangeJSONRequestBody RequestWorkerStatusChangeJSONBody
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
const flashAfterCopyDuration = 150;
|
||||
|
||||
|
||||
/**
|
||||
* Copy the inner text of an element to the clipboard.
|
||||
*
|
||||
@ -14,9 +15,24 @@ const flashAfterCopyDuration = 150;
|
||||
*/
|
||||
export function copyElementText(clickEvent) {
|
||||
const sourceElement = clickEvent.target;
|
||||
copyElementValue(sourceElement, sourceElement.innerText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the inner text of an element to the clipboard.
|
||||
*
|
||||
* @param {Event } clickEvent the click event that triggered this function call.
|
||||
*/
|
||||
export function copyElementData(clickEvent) {
|
||||
const sourceElement = clickEvent.target;
|
||||
window.sourceElement = sourceElement;
|
||||
copyElementValue(sourceElement, sourceElement.dataset.clipboard);
|
||||
}
|
||||
|
||||
function copyElementValue(sourceElement, value) {
|
||||
const inputElement = document.createElement("input");
|
||||
document.body.appendChild(inputElement);
|
||||
inputElement.setAttribute("value", sourceElement.innerText);
|
||||
inputElement.setAttribute("value", value);
|
||||
inputElement.select();
|
||||
|
||||
// Note that the `navigator.clipboard` interface is only available when using
|
||||
@ -27,7 +43,6 @@ export function copyElementText(clickEvent) {
|
||||
document.execCommand("copy");
|
||||
|
||||
document.body.removeChild(inputElement);
|
||||
|
||||
flashElement(sourceElement);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<label>
|
||||
<label :title="title">
|
||||
<span class="switch">
|
||||
<input type="checkbox" :checked="isChecked" @change="$emit('switchToggle')">
|
||||
<span class="slider round"></span>
|
||||
@ -9,7 +9,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps(['isChecked', 'label']);
|
||||
const props = defineProps(['isChecked', 'label', 'title']);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -32,6 +32,14 @@
|
||||
<dt class="field-name" title="ID">ID</dt>
|
||||
<dd><span @click="copyElementText" class="click-to-copy">{{ jobData.id }}</span></dd>
|
||||
|
||||
<template v-if="workerCluster">
|
||||
<!-- TODO: fetch cluster name and show that instead, and allow editing of the cluster. -->
|
||||
<dt class="field-name" title="Worker Cluster">Cluster</dt>
|
||||
<dd :title="workerCluster.description"><span @click="copyElementData" class="click-to-copy"
|
||||
:data-clipboard="workerCluster.id">{{
|
||||
workerCluster.name }}</span></dd>
|
||||
</template>
|
||||
|
||||
<dt class="field-name" title="Name">Name</dt>
|
||||
<dd>{{ jobData.name }}</dd>
|
||||
|
||||
@ -82,7 +90,8 @@ import Blocklist from './Blocklist.vue'
|
||||
import TabItem from '@/components/TabItem.vue'
|
||||
import TabsWrapper from '@/components/TabsWrapper.vue'
|
||||
import PopoverEditableJobPriority from '@/components/PopoverEditableJobPriority.vue'
|
||||
import { copyElementText } from '@/clipboard';
|
||||
import { copyElementText, copyElementData } from '@/clipboard';
|
||||
import { useWorkers } from '@/stores/workers'
|
||||
|
||||
export default {
|
||||
props: [
|
||||
@ -102,11 +111,13 @@ export default {
|
||||
return {
|
||||
datetime: datetime, // So that the template can access it.
|
||||
copyElementText: copyElementText,
|
||||
copyElementData: copyElementData,
|
||||
simpleSettings: null, // Object with filtered job settings, or null if there is no job.
|
||||
jobsApi: new API.JobsApi(getAPIClient()),
|
||||
jobType: null, // API.AvailableJobType object for the current job type.
|
||||
jobTypeSettings: null, // Mapping from setting key to its definition in the job type.
|
||||
showAllSettings: false,
|
||||
workers: useWorkers(),
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
@ -116,6 +127,12 @@ export default {
|
||||
if (!objectEmpty(this.jobData)) {
|
||||
this._refreshJobSettings(this.jobData);
|
||||
}
|
||||
|
||||
this.workers.refreshClusters()
|
||||
.catch((error) => {
|
||||
const errorMsg = JSON.stringify(error); // TODO: handle API errors better.
|
||||
this.notifs.add(`Error: ${errorMsg}`);
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
hasJobData() {
|
||||
@ -139,6 +156,10 @@ export default {
|
||||
}
|
||||
return this.jobData.settings;
|
||||
},
|
||||
workerCluster() {
|
||||
if (!this.jobData.worker_cluster) return undefined;
|
||||
return this.workers.clustersByID[this.jobData.worker_cluster];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
jobData(newJobData) {
|
||||
|
@ -34,6 +34,23 @@
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<section class="worker-clusters" v-if="workers.clusters && workers.clusters.length">
|
||||
<h3 class="sub-title">Clusters</h3>
|
||||
<ul>
|
||||
<li v-for="cluster in workers.clusters">
|
||||
<switch-checkbox :isChecked="thisWorkerClusters[cluster.id]" :label="cluster.name" :title="cluster.description"
|
||||
@switch-toggle="toggleWorkerCluster(cluster.id)">
|
||||
</switch-checkbox>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="hint" v-if="hasClustersAssigned">
|
||||
This worker will only pick up jobs assigned to one of its clusters, and clusterless jobs.
|
||||
</p>
|
||||
<p class="hint" v-else>
|
||||
This worker will only pick up clusterless jobs.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="sleep-schedule" :class="{ 'is-schedule-active': workerSleepSchedule.is_active }">
|
||||
<h3 class="sub-title">
|
||||
<switch-checkbox :isChecked="workerSleepSchedule.is_active" @switch-toggle="toggleWorkerSleepSchedule">
|
||||
@ -120,9 +137,10 @@
|
||||
|
||||
<script>
|
||||
import { useNotifs } from '@/stores/notifications'
|
||||
import { useWorkers } from '@/stores/workers'
|
||||
|
||||
import * as datetime from "@/datetime";
|
||||
import { WorkerMgtApi, WorkerSleepSchedule } from '@/manager-api';
|
||||
import { WorkerMgtApi, WorkerSleepSchedule, WorkerClusterChangeRequest } from '@/manager-api';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
import { workerStatus } from "../../statusindicator";
|
||||
import LinkWorkerTask from '@/components/LinkWorkerTask.vue';
|
||||
@ -146,11 +164,19 @@ export default {
|
||||
isScheduleEditing: false,
|
||||
notifs: useNotifs(),
|
||||
copyElementText: copyElementText,
|
||||
workers: useWorkers(),
|
||||
thisWorkerClusters: {}, // Mapping from UUID to 'isAssigned' boolean.
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// Allow testing from the JS console:
|
||||
window.workerDetailsVue = this;
|
||||
|
||||
this.workers.refreshClusters()
|
||||
.catch((error) => {
|
||||
const errorMsg = JSON.stringify(error); // TODO: handle API errors better.
|
||||
this.notifs.add(`Error: ${errorMsg}`);
|
||||
});
|
||||
},
|
||||
watch: {
|
||||
workerData(newData, oldData) {
|
||||
@ -164,6 +190,8 @@ export default {
|
||||
if (((oldData && newData) && (oldData.id != newData.id)) || !oldData && newData) {
|
||||
this.fetchWorkerSleepSchedule();
|
||||
}
|
||||
|
||||
this.updateThisWorkerClusters(newData);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
@ -182,6 +210,10 @@ export default {
|
||||
workerSleepScheduleStatusLabel() {
|
||||
return this.workerSleepSchedule.is_active ? 'Enabled' : 'Disabled';
|
||||
},
|
||||
hasClustersAssigned() {
|
||||
const clusterIDs = this.getAssignedClusterIDs();
|
||||
return clusterIDs && clusterIDs.length > 0;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchWorkerSleepSchedule() {
|
||||
@ -230,6 +262,45 @@ export default {
|
||||
}
|
||||
this.api.deleteWorker(this.workerData.id);
|
||||
},
|
||||
updateThisWorkerClusters(newWorkerData) {
|
||||
if (!newWorkerData || !newWorkerData.clusters) {
|
||||
this.thisWorkerClusters = {};
|
||||
return;
|
||||
}
|
||||
|
||||
const assignedClusters = newWorkerData.clusters.reduce(
|
||||
(accu, cluster) => { accu[cluster.id] = true; return accu; },
|
||||
{});
|
||||
this.thisWorkerClusters = assignedClusters;
|
||||
},
|
||||
toggleWorkerCluster(clusterID) {
|
||||
console.log("Toggled", clusterID);
|
||||
this.thisWorkerClusters[clusterID] = !this.thisWorkerClusters[clusterID];
|
||||
console.log("New assignment:", plain(this.thisWorkerClusters))
|
||||
|
||||
// Construct cluster change request.
|
||||
const clusterIDs = this.getAssignedClusterIDs();
|
||||
const changeRequest = new WorkerClusterChangeRequest(clusterIDs);
|
||||
|
||||
// Send to the Manager.
|
||||
this.api.setWorkerClusters(this.workerData.id, changeRequest)
|
||||
.then(() => {
|
||||
this.notifs.add('Cluster assignment updated');
|
||||
})
|
||||
.catch((error) => {
|
||||
const errorMsg = JSON.stringify(error); // TODO: handle API errors better.
|
||||
this.notifs.add(`Error: ${errorMsg}`);
|
||||
});
|
||||
},
|
||||
getAssignedClusterIDs() {
|
||||
const clusterIDs = [];
|
||||
for (let clusterID in this.thisWorkerClusters) {
|
||||
// Values can exist and be set to 'false'.
|
||||
const isAssigned = this.thisWorkerClusters[clusterID];
|
||||
if (isAssigned) clusterIDs.push(clusterID);
|
||||
}
|
||||
return clusterIDs;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -305,4 +376,12 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.worker-clusters ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.worker-clusters ul li {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
2
web/app/src/manager-api/ApiClient.js
generated
2
web/app/src/manager-api/ApiClient.js
generated
@ -55,7 +55,7 @@ class ApiClient {
|
||||
* @default {}
|
||||
*/
|
||||
this.defaultHeaders = {
|
||||
'User-Agent': 'Flamenco/3.2 / webbrowser'
|
||||
'User-Agent': 'Flamenco/3.3-alpha0 / webbrowser'
|
||||
};
|
||||
|
||||
/**
|
||||
|
21
web/app/src/manager-api/index.js
generated
21
web/app/src/manager-api/index.js
generated
@ -75,6 +75,9 @@ import TaskUpdate from './model/TaskUpdate';
|
||||
import TaskWorker from './model/TaskWorker';
|
||||
import Worker from './model/Worker';
|
||||
import WorkerAllOf from './model/WorkerAllOf';
|
||||
import WorkerCluster from './model/WorkerCluster';
|
||||
import WorkerClusterChangeRequest from './model/WorkerClusterChangeRequest';
|
||||
import WorkerClusterList from './model/WorkerClusterList';
|
||||
import WorkerList from './model/WorkerList';
|
||||
import WorkerRegistration from './model/WorkerRegistration';
|
||||
import WorkerSignOn from './model/WorkerSignOn';
|
||||
@ -503,6 +506,24 @@ export {
|
||||
*/
|
||||
WorkerAllOf,
|
||||
|
||||
/**
|
||||
* The WorkerCluster model constructor.
|
||||
* @property {module:model/WorkerCluster}
|
||||
*/
|
||||
WorkerCluster,
|
||||
|
||||
/**
|
||||
* The WorkerClusterChangeRequest model constructor.
|
||||
* @property {module:model/WorkerClusterChangeRequest}
|
||||
*/
|
||||
WorkerClusterChangeRequest,
|
||||
|
||||
/**
|
||||
* The WorkerClusterList model constructor.
|
||||
* @property {module:model/WorkerClusterList}
|
||||
*/
|
||||
WorkerClusterList,
|
||||
|
||||
/**
|
||||
* The WorkerList model constructor.
|
||||
* @property {module:model/WorkerList}
|
||||
|
281
web/app/src/manager-api/manager/WorkerMgtApi.js
generated
281
web/app/src/manager-api/manager/WorkerMgtApi.js
generated
@ -15,6 +15,9 @@
|
||||
import ApiClient from "../ApiClient";
|
||||
import Error from '../model/Error';
|
||||
import Worker from '../model/Worker';
|
||||
import WorkerCluster from '../model/WorkerCluster';
|
||||
import WorkerClusterChangeRequest from '../model/WorkerClusterChangeRequest';
|
||||
import WorkerClusterList from '../model/WorkerClusterList';
|
||||
import WorkerList from '../model/WorkerList';
|
||||
import WorkerSleepSchedule from '../model/WorkerSleepSchedule';
|
||||
import WorkerStatusChangeRequest from '../model/WorkerStatusChangeRequest';
|
||||
@ -39,6 +42,51 @@ export default class WorkerMgtApi {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a new worker cluster.
|
||||
* @param {module:model/WorkerCluster} workerCluster The worker cluster.
|
||||
* @return {Promise} a {@link https://www.promisejs.org/|Promise}, with an object containing data of type {@link module:model/WorkerCluster} and HTTP response
|
||||
*/
|
||||
createWorkerClusterWithHttpInfo(workerCluster) {
|
||||
let postBody = workerCluster;
|
||||
// verify the required parameter 'workerCluster' is set
|
||||
if (workerCluster === undefined || workerCluster === null) {
|
||||
throw new Error("Missing the required parameter 'workerCluster' when calling createWorkerCluster");
|
||||
}
|
||||
|
||||
let pathParams = {
|
||||
};
|
||||
let queryParams = {
|
||||
};
|
||||
let headerParams = {
|
||||
};
|
||||
let formParams = {
|
||||
};
|
||||
|
||||
let authNames = [];
|
||||
let contentTypes = ['application/json'];
|
||||
let accepts = ['application/json'];
|
||||
let returnType = WorkerCluster;
|
||||
return this.apiClient.callApi(
|
||||
'/api/v3/worker-mgt/clusters', 'POST',
|
||||
pathParams, queryParams, headerParams, formParams, postBody,
|
||||
authNames, contentTypes, accepts, returnType, null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new worker cluster.
|
||||
* @param {module:model/WorkerCluster} workerCluster The worker cluster.
|
||||
* @return {Promise} a {@link https://www.promisejs.org/|Promise}, with data of type {@link module:model/WorkerCluster}
|
||||
*/
|
||||
createWorkerCluster(workerCluster) {
|
||||
return this.createWorkerClusterWithHttpInfo(workerCluster)
|
||||
.then(function(response_and_data) {
|
||||
return response_and_data.data;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the given worker. It is recommended to only call this function when the worker is in `offline` state. If the worker is still running, stop it first. Any task still assigned to the worker will be requeued.
|
||||
* @param {String} workerId
|
||||
@ -85,6 +133,52 @@ export default class WorkerMgtApi {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove this worker cluster. This unassigns all workers from the cluster and removes it.
|
||||
* @param {String} clusterId
|
||||
* @return {Promise} a {@link https://www.promisejs.org/|Promise}, with an object containing HTTP response
|
||||
*/
|
||||
deleteWorkerClusterWithHttpInfo(clusterId) {
|
||||
let postBody = null;
|
||||
// verify the required parameter 'clusterId' is set
|
||||
if (clusterId === undefined || clusterId === null) {
|
||||
throw new Error("Missing the required parameter 'clusterId' when calling deleteWorkerCluster");
|
||||
}
|
||||
|
||||
let pathParams = {
|
||||
'cluster_id': clusterId
|
||||
};
|
||||
let queryParams = {
|
||||
};
|
||||
let headerParams = {
|
||||
};
|
||||
let formParams = {
|
||||
};
|
||||
|
||||
let authNames = [];
|
||||
let contentTypes = [];
|
||||
let accepts = ['application/json'];
|
||||
let returnType = null;
|
||||
return this.apiClient.callApi(
|
||||
'/api/v3/worker-mgt/cluster/{cluster_id}', 'DELETE',
|
||||
pathParams, queryParams, headerParams, formParams, postBody,
|
||||
authNames, contentTypes, accepts, returnType, null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this worker cluster. This unassigns all workers from the cluster and removes it.
|
||||
* @param {String} clusterId
|
||||
* @return {Promise} a {@link https://www.promisejs.org/|Promise}
|
||||
*/
|
||||
deleteWorkerCluster(clusterId) {
|
||||
return this.deleteWorkerClusterWithHttpInfo(clusterId)
|
||||
.then(function(response_and_data) {
|
||||
return response_and_data.data;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch info about the worker.
|
||||
* @param {String} workerId
|
||||
@ -131,6 +225,91 @@ export default class WorkerMgtApi {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a single worker cluster.
|
||||
* @param {String} clusterId
|
||||
* @return {Promise} a {@link https://www.promisejs.org/|Promise}, with an object containing data of type {@link module:model/WorkerCluster} and HTTP response
|
||||
*/
|
||||
fetchWorkerClusterWithHttpInfo(clusterId) {
|
||||
let postBody = null;
|
||||
// verify the required parameter 'clusterId' is set
|
||||
if (clusterId === undefined || clusterId === null) {
|
||||
throw new Error("Missing the required parameter 'clusterId' when calling fetchWorkerCluster");
|
||||
}
|
||||
|
||||
let pathParams = {
|
||||
'cluster_id': clusterId
|
||||
};
|
||||
let queryParams = {
|
||||
};
|
||||
let headerParams = {
|
||||
};
|
||||
let formParams = {
|
||||
};
|
||||
|
||||
let authNames = [];
|
||||
let contentTypes = [];
|
||||
let accepts = ['application/json'];
|
||||
let returnType = WorkerCluster;
|
||||
return this.apiClient.callApi(
|
||||
'/api/v3/worker-mgt/cluster/{cluster_id}', 'GET',
|
||||
pathParams, queryParams, headerParams, formParams, postBody,
|
||||
authNames, contentTypes, accepts, returnType, null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single worker cluster.
|
||||
* @param {String} clusterId
|
||||
* @return {Promise} a {@link https://www.promisejs.org/|Promise}, with data of type {@link module:model/WorkerCluster}
|
||||
*/
|
||||
fetchWorkerCluster(clusterId) {
|
||||
return this.fetchWorkerClusterWithHttpInfo(clusterId)
|
||||
.then(function(response_and_data) {
|
||||
return response_and_data.data;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get list of worker clusters.
|
||||
* @return {Promise} a {@link https://www.promisejs.org/|Promise}, with an object containing data of type {@link module:model/WorkerClusterList} and HTTP response
|
||||
*/
|
||||
fetchWorkerClustersWithHttpInfo() {
|
||||
let postBody = null;
|
||||
|
||||
let pathParams = {
|
||||
};
|
||||
let queryParams = {
|
||||
};
|
||||
let headerParams = {
|
||||
};
|
||||
let formParams = {
|
||||
};
|
||||
|
||||
let authNames = [];
|
||||
let contentTypes = [];
|
||||
let accepts = ['application/json'];
|
||||
let returnType = WorkerClusterList;
|
||||
return this.apiClient.callApi(
|
||||
'/api/v3/worker-mgt/clusters', 'GET',
|
||||
pathParams, queryParams, headerParams, formParams, postBody,
|
||||
authNames, contentTypes, accepts, returnType, null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of worker clusters.
|
||||
* @return {Promise} a {@link https://www.promisejs.org/|Promise}, with data of type {@link module:model/WorkerClusterList}
|
||||
*/
|
||||
fetchWorkerClusters() {
|
||||
return this.fetchWorkerClustersWithHttpInfo()
|
||||
.then(function(response_and_data) {
|
||||
return response_and_data.data;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {String} workerId
|
||||
* @return {Promise} a {@link https://www.promisejs.org/|Promise}, with an object containing data of type {@link module:model/WorkerSleepSchedule} and HTTP response
|
||||
@ -264,6 +443,56 @@ export default class WorkerMgtApi {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {String} workerId
|
||||
* @param {module:model/WorkerClusterChangeRequest} workerClusterChangeRequest The list of cluster IDs this worker should be a member of.
|
||||
* @return {Promise} a {@link https://www.promisejs.org/|Promise}, with an object containing HTTP response
|
||||
*/
|
||||
setWorkerClustersWithHttpInfo(workerId, workerClusterChangeRequest) {
|
||||
let postBody = workerClusterChangeRequest;
|
||||
// verify the required parameter 'workerId' is set
|
||||
if (workerId === undefined || workerId === null) {
|
||||
throw new Error("Missing the required parameter 'workerId' when calling setWorkerClusters");
|
||||
}
|
||||
// verify the required parameter 'workerClusterChangeRequest' is set
|
||||
if (workerClusterChangeRequest === undefined || workerClusterChangeRequest === null) {
|
||||
throw new Error("Missing the required parameter 'workerClusterChangeRequest' when calling setWorkerClusters");
|
||||
}
|
||||
|
||||
let pathParams = {
|
||||
'worker_id': workerId
|
||||
};
|
||||
let queryParams = {
|
||||
};
|
||||
let headerParams = {
|
||||
};
|
||||
let formParams = {
|
||||
};
|
||||
|
||||
let authNames = [];
|
||||
let contentTypes = ['application/json'];
|
||||
let accepts = ['application/json'];
|
||||
let returnType = null;
|
||||
return this.apiClient.callApi(
|
||||
'/api/v3/worker-mgt/workers/{worker_id}/setclusters', 'POST',
|
||||
pathParams, queryParams, headerParams, formParams, postBody,
|
||||
authNames, contentTypes, accepts, returnType, null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} workerId
|
||||
* @param {module:model/WorkerClusterChangeRequest} workerClusterChangeRequest The list of cluster IDs this worker should be a member of.
|
||||
* @return {Promise} a {@link https://www.promisejs.org/|Promise}
|
||||
*/
|
||||
setWorkerClusters(workerId, workerClusterChangeRequest) {
|
||||
return this.setWorkerClustersWithHttpInfo(workerId, workerClusterChangeRequest)
|
||||
.then(function(response_and_data) {
|
||||
return response_and_data.data;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {String} workerId
|
||||
* @param {module:model/WorkerSleepSchedule} workerSleepSchedule The new sleep schedule.
|
||||
@ -314,4 +543,56 @@ export default class WorkerMgtApi {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update an existing worker cluster.
|
||||
* @param {String} clusterId
|
||||
* @param {module:model/WorkerCluster} workerCluster The updated worker cluster.
|
||||
* @return {Promise} a {@link https://www.promisejs.org/|Promise}, with an object containing HTTP response
|
||||
*/
|
||||
updateWorkerClusterWithHttpInfo(clusterId, workerCluster) {
|
||||
let postBody = workerCluster;
|
||||
// verify the required parameter 'clusterId' is set
|
||||
if (clusterId === undefined || clusterId === null) {
|
||||
throw new Error("Missing the required parameter 'clusterId' when calling updateWorkerCluster");
|
||||
}
|
||||
// verify the required parameter 'workerCluster' is set
|
||||
if (workerCluster === undefined || workerCluster === null) {
|
||||
throw new Error("Missing the required parameter 'workerCluster' when calling updateWorkerCluster");
|
||||
}
|
||||
|
||||
let pathParams = {
|
||||
'cluster_id': clusterId
|
||||
};
|
||||
let queryParams = {
|
||||
};
|
||||
let headerParams = {
|
||||
};
|
||||
let formParams = {
|
||||
};
|
||||
|
||||
let authNames = [];
|
||||
let contentTypes = ['application/json'];
|
||||
let accepts = ['application/json'];
|
||||
let returnType = null;
|
||||
return this.apiClient.callApi(
|
||||
'/api/v3/worker-mgt/cluster/{cluster_id}', 'PUT',
|
||||
pathParams, queryParams, headerParams, formParams, postBody,
|
||||
authNames, contentTypes, accepts, returnType, null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing worker cluster.
|
||||
* @param {String} clusterId
|
||||
* @param {module:model/WorkerCluster} workerCluster The updated worker cluster.
|
||||
* @return {Promise} a {@link https://www.promisejs.org/|Promise}
|
||||
*/
|
||||
updateWorkerCluster(clusterId, workerCluster) {
|
||||
return this.updateWorkerClusterWithHttpInfo(clusterId, workerCluster)
|
||||
.then(function(response_and_data) {
|
||||
return response_and_data.data;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
14
web/app/src/manager-api/model/Job.js
generated
14
web/app/src/manager-api/model/Job.js
generated
@ -97,6 +97,9 @@ class Job {
|
||||
if (data.hasOwnProperty('storage')) {
|
||||
obj['storage'] = JobStorageInfo.constructFromObject(data['storage']);
|
||||
}
|
||||
if (data.hasOwnProperty('worker_cluster')) {
|
||||
obj['worker_cluster'] = ApiClient.convertToType(data['worker_cluster'], 'String');
|
||||
}
|
||||
if (data.hasOwnProperty('id')) {
|
||||
obj['id'] = ApiClient.convertToType(data['id'], 'String');
|
||||
}
|
||||
@ -166,6 +169,12 @@ Job.prototype['submitter_platform'] = undefined;
|
||||
*/
|
||||
Job.prototype['storage'] = undefined;
|
||||
|
||||
/**
|
||||
* Worker Cluster that should execute this job. When a cluster ID is given, only Workers in that cluster will be scheduled to work on it. If empty or ommitted, all workers can work on this job.
|
||||
* @member {String} worker_cluster
|
||||
*/
|
||||
Job.prototype['worker_cluster'] = undefined;
|
||||
|
||||
/**
|
||||
* UUID of the Job
|
||||
* @member {String} id
|
||||
@ -239,6 +248,11 @@ SubmittedJob.prototype['submitter_platform'] = undefined;
|
||||
* @member {module:model/JobStorageInfo} storage
|
||||
*/
|
||||
SubmittedJob.prototype['storage'] = undefined;
|
||||
/**
|
||||
* Worker Cluster that should execute this job. When a cluster ID is given, only Workers in that cluster will be scheduled to work on it. If empty or ommitted, all workers can work on this job.
|
||||
* @member {String} worker_cluster
|
||||
*/
|
||||
SubmittedJob.prototype['worker_cluster'] = undefined;
|
||||
// Implement JobAllOf interface:
|
||||
/**
|
||||
* UUID of the Job
|
||||
|
9
web/app/src/manager-api/model/SubmittedJob.js
generated
9
web/app/src/manager-api/model/SubmittedJob.js
generated
@ -81,6 +81,9 @@ class SubmittedJob {
|
||||
if (data.hasOwnProperty('storage')) {
|
||||
obj['storage'] = JobStorageInfo.constructFromObject(data['storage']);
|
||||
}
|
||||
if (data.hasOwnProperty('worker_cluster')) {
|
||||
obj['worker_cluster'] = ApiClient.convertToType(data['worker_cluster'], 'String');
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
@ -132,6 +135,12 @@ SubmittedJob.prototype['submitter_platform'] = undefined;
|
||||
*/
|
||||
SubmittedJob.prototype['storage'] = undefined;
|
||||
|
||||
/**
|
||||
* Worker Cluster that should execute this job. When a cluster ID is given, only Workers in that cluster will be scheduled to work on it. If empty or ommitted, all workers can work on this job.
|
||||
* @member {String} worker_cluster
|
||||
*/
|
||||
SubmittedJob.prototype['worker_cluster'] = undefined;
|
||||
|
||||
|
||||
|
||||
|
||||
|
15
web/app/src/manager-api/model/Worker.js
generated
15
web/app/src/manager-api/model/Worker.js
generated
@ -13,6 +13,7 @@
|
||||
|
||||
import ApiClient from '../ApiClient';
|
||||
import WorkerAllOf from './WorkerAllOf';
|
||||
import WorkerCluster from './WorkerCluster';
|
||||
import WorkerStatus from './WorkerStatus';
|
||||
import WorkerStatusChangeRequest from './WorkerStatusChangeRequest';
|
||||
import WorkerSummary from './WorkerSummary';
|
||||
@ -101,6 +102,9 @@ class Worker {
|
||||
if (data.hasOwnProperty('task')) {
|
||||
obj['task'] = WorkerTask.constructFromObject(data['task']);
|
||||
}
|
||||
if (data.hasOwnProperty('clusters')) {
|
||||
obj['clusters'] = ApiClient.convertToType(data['clusters'], [WorkerCluster]);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
@ -162,6 +166,12 @@ Worker.prototype['supported_task_types'] = undefined;
|
||||
*/
|
||||
Worker.prototype['task'] = undefined;
|
||||
|
||||
/**
|
||||
* Clusters of which this Worker is a member.
|
||||
* @member {Array.<module:model/WorkerCluster>} clusters
|
||||
*/
|
||||
Worker.prototype['clusters'] = undefined;
|
||||
|
||||
|
||||
// Implement WorkerSummary interface:
|
||||
/**
|
||||
@ -209,6 +219,11 @@ WorkerAllOf.prototype['supported_task_types'] = undefined;
|
||||
* @member {module:model/WorkerTask} task
|
||||
*/
|
||||
WorkerAllOf.prototype['task'] = undefined;
|
||||
/**
|
||||
* Clusters of which this Worker is a member.
|
||||
* @member {Array.<module:model/WorkerCluster>} clusters
|
||||
*/
|
||||
WorkerAllOf.prototype['clusters'] = undefined;
|
||||
|
||||
|
||||
|
||||
|
10
web/app/src/manager-api/model/WorkerAllOf.js
generated
10
web/app/src/manager-api/model/WorkerAllOf.js
generated
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import ApiClient from '../ApiClient';
|
||||
import WorkerCluster from './WorkerCluster';
|
||||
import WorkerTask from './WorkerTask';
|
||||
|
||||
/**
|
||||
@ -66,6 +67,9 @@ class WorkerAllOf {
|
||||
if (data.hasOwnProperty('task')) {
|
||||
obj['task'] = WorkerTask.constructFromObject(data['task']);
|
||||
}
|
||||
if (data.hasOwnProperty('clusters')) {
|
||||
obj['clusters'] = ApiClient.convertToType(data['clusters'], [WorkerCluster]);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
@ -95,6 +99,12 @@ WorkerAllOf.prototype['supported_task_types'] = undefined;
|
||||
*/
|
||||
WorkerAllOf.prototype['task'] = undefined;
|
||||
|
||||
/**
|
||||
* Clusters of which this Worker is a member.
|
||||
* @member {Array.<module:model/WorkerCluster>} clusters
|
||||
*/
|
||||
WorkerAllOf.prototype['clusters'] = undefined;
|
||||
|
||||
|
||||
|
||||
|
||||
|
91
web/app/src/manager-api/model/WorkerCluster.js
generated
Normal file
91
web/app/src/manager-api/model/WorkerCluster.js
generated
Normal file
@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Flamenco manager
|
||||
* Render Farm manager API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*
|
||||
*/
|
||||
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
/**
|
||||
* The WorkerCluster model module.
|
||||
* @module model/WorkerCluster
|
||||
* @version 0.0.0
|
||||
*/
|
||||
class WorkerCluster {
|
||||
/**
|
||||
* Constructs a new <code>WorkerCluster</code>.
|
||||
* Cluster of workers. A job can optionally specify which cluster it should be limited to. Workers can be part of multiple clusters simultaneously.
|
||||
* @alias module:model/WorkerCluster
|
||||
* @param name {String}
|
||||
*/
|
||||
constructor(name) {
|
||||
|
||||
WorkerCluster.initialize(this, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the fields of this object.
|
||||
* This method is used by the constructors of any subclasses, in order to implement multiple inheritance (mix-ins).
|
||||
* Only for internal use.
|
||||
*/
|
||||
static initialize(obj, name) {
|
||||
obj['name'] = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a <code>WorkerCluster</code> from a plain JavaScript object, optionally creating a new instance.
|
||||
* Copies all relevant properties from <code>data</code> to <code>obj</code> if supplied or a new instance if not.
|
||||
* @param {Object} data The plain JavaScript object bearing properties of interest.
|
||||
* @param {module:model/WorkerCluster} obj Optional instance to populate.
|
||||
* @return {module:model/WorkerCluster} The populated <code>WorkerCluster</code> instance.
|
||||
*/
|
||||
static constructFromObject(data, obj) {
|
||||
if (data) {
|
||||
obj = obj || new WorkerCluster();
|
||||
|
||||
if (data.hasOwnProperty('id')) {
|
||||
obj['id'] = ApiClient.convertToType(data['id'], 'String');
|
||||
}
|
||||
if (data.hasOwnProperty('name')) {
|
||||
obj['name'] = ApiClient.convertToType(data['name'], 'String');
|
||||
}
|
||||
if (data.hasOwnProperty('description')) {
|
||||
obj['description'] = ApiClient.convertToType(data['description'], 'String');
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* UUID of the cluster. Can be ommitted when creating a new cluster, in which case a random UUID will be assigned.
|
||||
* @member {String} id
|
||||
*/
|
||||
WorkerCluster.prototype['id'] = undefined;
|
||||
|
||||
/**
|
||||
* @member {String} name
|
||||
*/
|
||||
WorkerCluster.prototype['name'] = undefined;
|
||||
|
||||
/**
|
||||
* @member {String} description
|
||||
*/
|
||||
WorkerCluster.prototype['description'] = undefined;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export default WorkerCluster;
|
||||
|
74
web/app/src/manager-api/model/WorkerClusterChangeRequest.js
generated
Normal file
74
web/app/src/manager-api/model/WorkerClusterChangeRequest.js
generated
Normal file
@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Flamenco manager
|
||||
* Render Farm manager API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*
|
||||
*/
|
||||
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
/**
|
||||
* The WorkerClusterChangeRequest model module.
|
||||
* @module model/WorkerClusterChangeRequest
|
||||
* @version 0.0.0
|
||||
*/
|
||||
class WorkerClusterChangeRequest {
|
||||
/**
|
||||
* Constructs a new <code>WorkerClusterChangeRequest</code>.
|
||||
* Request to change which clusters this Worker is assigned to.
|
||||
* @alias module:model/WorkerClusterChangeRequest
|
||||
* @param clusterIds {Array.<String>}
|
||||
*/
|
||||
constructor(clusterIds) {
|
||||
|
||||
WorkerClusterChangeRequest.initialize(this, clusterIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the fields of this object.
|
||||
* This method is used by the constructors of any subclasses, in order to implement multiple inheritance (mix-ins).
|
||||
* Only for internal use.
|
||||
*/
|
||||
static initialize(obj, clusterIds) {
|
||||
obj['cluster_ids'] = clusterIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a <code>WorkerClusterChangeRequest</code> from a plain JavaScript object, optionally creating a new instance.
|
||||
* Copies all relevant properties from <code>data</code> to <code>obj</code> if supplied or a new instance if not.
|
||||
* @param {Object} data The plain JavaScript object bearing properties of interest.
|
||||
* @param {module:model/WorkerClusterChangeRequest} obj Optional instance to populate.
|
||||
* @return {module:model/WorkerClusterChangeRequest} The populated <code>WorkerClusterChangeRequest</code> instance.
|
||||
*/
|
||||
static constructFromObject(data, obj) {
|
||||
if (data) {
|
||||
obj = obj || new WorkerClusterChangeRequest();
|
||||
|
||||
if (data.hasOwnProperty('cluster_ids')) {
|
||||
obj['cluster_ids'] = ApiClient.convertToType(data['cluster_ids'], ['String']);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @member {Array.<String>} cluster_ids
|
||||
*/
|
||||
WorkerClusterChangeRequest.prototype['cluster_ids'] = undefined;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export default WorkerClusterChangeRequest;
|
||||
|
72
web/app/src/manager-api/model/WorkerClusterList.js
generated
Normal file
72
web/app/src/manager-api/model/WorkerClusterList.js
generated
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Flamenco manager
|
||||
* Render Farm manager API
|
||||
*
|
||||
* The version of the OpenAPI document: 1.0.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*
|
||||
*/
|
||||
|
||||
import ApiClient from '../ApiClient';
|
||||
import WorkerCluster from './WorkerCluster';
|
||||
|
||||
/**
|
||||
* The WorkerClusterList model module.
|
||||
* @module model/WorkerClusterList
|
||||
* @version 0.0.0
|
||||
*/
|
||||
class WorkerClusterList {
|
||||
/**
|
||||
* Constructs a new <code>WorkerClusterList</code>.
|
||||
* @alias module:model/WorkerClusterList
|
||||
*/
|
||||
constructor() {
|
||||
|
||||
WorkerClusterList.initialize(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the fields of this object.
|
||||
* This method is used by the constructors of any subclasses, in order to implement multiple inheritance (mix-ins).
|
||||
* Only for internal use.
|
||||
*/
|
||||
static initialize(obj) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a <code>WorkerClusterList</code> from a plain JavaScript object, optionally creating a new instance.
|
||||
* Copies all relevant properties from <code>data</code> to <code>obj</code> if supplied or a new instance if not.
|
||||
* @param {Object} data The plain JavaScript object bearing properties of interest.
|
||||
* @param {module:model/WorkerClusterList} obj Optional instance to populate.
|
||||
* @return {module:model/WorkerClusterList} The populated <code>WorkerClusterList</code> instance.
|
||||
*/
|
||||
static constructFromObject(data, obj) {
|
||||
if (data) {
|
||||
obj = obj || new WorkerClusterList();
|
||||
|
||||
if (data.hasOwnProperty('clusters')) {
|
||||
obj['clusters'] = ApiClient.convertToType(data['clusters'], [WorkerCluster]);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @member {Array.<module:model/WorkerCluster>} clusters
|
||||
*/
|
||||
WorkerClusterList.prototype['clusters'] = undefined;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export default WorkerClusterList;
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
import { WorkerMgtApi } from '@/manager-api';
|
||||
import { getAPIClient } from "@/api-client";
|
||||
|
||||
// 'use' prefix is idiomatic for Pinia stores.
|
||||
// See https://pinia.vuejs.org/core-concepts/
|
||||
export const useWorkers = defineStore('workers', {
|
||||
@ -11,6 +14,12 @@ export const useWorkers = defineStore('workers', {
|
||||
* @type {string}
|
||||
*/
|
||||
activeWorkerID: "",
|
||||
|
||||
/** @type {API.WorkerCluster[]} */
|
||||
clusters: [],
|
||||
|
||||
/* Mapping from cluster UUID to API.WorkerCluster. */
|
||||
clustersByID: {},
|
||||
}),
|
||||
actions: {
|
||||
setActiveWorkerID(workerID) {
|
||||
@ -37,5 +46,23 @@ export const useWorkers = defineStore('workers', {
|
||||
activeWorkerID: "",
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Fetch the available worker clusters from the Manager.
|
||||
*
|
||||
* @returns a promise.
|
||||
*/
|
||||
refreshClusters() {
|
||||
const api = new WorkerMgtApi(getAPIClient());
|
||||
return api.fetchWorkerClusters()
|
||||
.then((resp) => {
|
||||
this.clusters = resp.clusters;
|
||||
|
||||
let clustersByID = {};
|
||||
for (let cluster of this.clusters) {
|
||||
clustersByID[cluster.id] = cluster;
|
||||
}
|
||||
this.clustersByID = clustersByID;
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@ -115,3 +115,6 @@ enableRobotsTXT = true
|
||||
[params.geekdocContentLicense]
|
||||
name = "CC BY 4.0 - Blender Foundation"
|
||||
link = "https://creativecommons.org/licenses/by/4.0/"
|
||||
|
||||
[params.flamenco]
|
||||
bugreportURL = "https://projects.blender.org/studio/flamenco/issues/new?template=.gitea%2fissue_template%2fbug.yaml"
|
||||
|
@ -9,7 +9,7 @@ Join the community on the [#flamenco channel][chat] of Blender Chat do discuss
|
||||
development topics. New faces are always welcome!
|
||||
|
||||
{{< button size="large" relref="/development/getting-started" >}}Get Started Developing Flamenco{{< /button >}}
|
||||
{{< button size="large" href="https://projects.blender.org/studio/flamenco/issues/new?template=.gitea%2fissue_template%2fbug.yaml" >}}Report a Bug{{< /button >}}
|
||||
{{< flamenco/reportBugButton size="large" >}}
|
||||
|
||||
If you want to know what kind of work can be done, take a look at the
|
||||
[workboard][workboard].
|
||||
|
@ -40,21 +40,30 @@ file][workercfg].
|
||||
## Can I change the paths/names of the rendered files?
|
||||
|
||||
Where Flamenco places the rendered files is determined by the job type. You can
|
||||
create [your own custom job type][jobtypes] to change this. With that, you can
|
||||
create [your own custom job type][jobtypes] or check the existing
|
||||
[third-party job types][thirdpartyjobs] to change this. With that, you can
|
||||
even add your own custom job settings like a sequence identifier and use that to
|
||||
determine the location of rendered files.
|
||||
|
||||
|
||||
## Can I use the Compositor to output multiple EXR files?
|
||||
## Can I use the Compositor to output multiple EXR files or Passes?
|
||||
|
||||
This is possible with Flamenco, but it takes a bit of work. It's not managed by
|
||||
Flamenco's default job types. You can create [your own custom job
|
||||
type][jobtypes] for this, though. With that, you have control over the arguments
|
||||
that get used before and/or after the filename on the CLI.
|
||||
This is possible with Flamenco, but it takes a bit of work. Although it's not
|
||||
managed by Flamenco's default job types, you can use a [custom job type][jobtypes]
|
||||
for this.
|
||||
|
||||
If you have this working, please [share your job compiler script with us][getinvolved]!
|
||||
With that, you have control over the arguments that get used before and/or after
|
||||
the filename on the CLI.
|
||||
|
||||
There are Flamenco jobs out there that support compositor nodes,
|
||||
multi-platform, and multiple pass outputs. You can check our [third-party jobs
|
||||
section][thirdpartyjobs].
|
||||
|
||||
If you wish to contribute to the project, you're invited to
|
||||
[get involved with Flamenco][getinvolved]!
|
||||
|
||||
[jobtypes]: {{< ref "usage/job-types" >}}
|
||||
[thirdpartyjobs]: {{< ref "third-party-jobs" >}}
|
||||
[getinvolved]: {{< ref "development/get-involved" >}}
|
||||
|
||||
|
||||
@ -98,3 +107,22 @@ complex project, and relies on a lot of components
|
||||
([source](https://www.opencue.io/docs/getting-started/)), whereas Flamenco is
|
||||
made for simplicity and use in small studios or at home, running on your own
|
||||
hardware.
|
||||
|
||||
## Why do I get an Error Performing BAT Pack Message?
|
||||
|
||||
As of yet, we've only encountered the issue below on Windows installations. If
|
||||
you get this issue, please {{< flamenco/reportBugLink size="small" >}}let us
|
||||
know{{< /flamenco/reportBugLink >}} so that it can be properly investigated.
|
||||
|
||||
```
|
||||
Error performing BAT pack: [WinError 267] The directory name is invalid:
|
||||
'C:\\The\\Path\\To\\Your\\Project.blend'
|
||||
```
|
||||
|
||||
This is most likely some sort of incompatibility that occurs in some cases where
|
||||
you might be using linked assets from an asset library in your project.
|
||||
|
||||
To work around this issue, try the following:
|
||||
|
||||
* In Blender, use File → External Data → Make Paths Relative.
|
||||
* Submit your job again.
|
||||
|
4
web/project-website/content/third-party-jobs/_index.md
Normal file
4
web/project-website/content/third-party-jobs/_index.md
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: Third-Party Jobs
|
||||
weight: 30
|
||||
---
|
@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Available Third-Party Jobs
|
||||
weight: 1
|
||||
---
|
||||
|
||||
This section contains third-party job types for Flamenco. These have been
|
||||
submitted by the community. If you wish to contribute, consider joining the
|
||||
[Blender Chat channel][flamencochannel] and chime-in there.
|
||||
|
||||
## How can I create my own Job Type?
|
||||
|
||||
This is described [Job Types][jobtypes]. It is recommended to use the
|
||||
[built-in scripts][built-in-scripts] as examples and adjust them from there.
|
||||
|
||||
## Third-Party Job Types
|
||||
|
||||
{{< flamenco/toc-children >}}
|
||||
|
||||
[jobtypes]: {{< ref "usage/job-types" >}}
|
||||
[built-in-scripts]: https://projects.blender.org/studio/flamenco/src/branch/main/internal/manager/job_compilers/scripts
|
||||
[flamencochannel]: https://blender.chat/channel/flamenco
|
@ -0,0 +1,103 @@
|
||||
---
|
||||
title: Compositor Nodes
|
||||
weight: 10
|
||||
---
|
||||
|
||||
*Job type documented and maintained by: [Dylan Blanqué][author].*
|
||||
|
||||
[author]: https://projects.blender.org/Dylan-Blanque
|
||||
|
||||
{{< hint >}}
|
||||
|
||||
This is a community-made job type. It may not reflect the same design as the
|
||||
rest of Flamenco, as it was made for a specific person to solve a specific need.
|
||||
|
||||
{{< /hint >}}
|
||||
|
||||
|
||||
This job type updates Blender's compositor nodes to work with Flamenco.
|
||||
|
||||
You'll need to do the following changes to support this workflow:
|
||||
|
||||
1. Download the [Flamenco Compositor Script ZIP file][compositorrepo] and extract it somewhere.
|
||||
2. Copy `startup_script.py` to the configured Blender File Folder in your shared storage.
|
||||
3. Copy `multi_pass_render.js` to the `scripts` folder in your Flamenco Manager installation folder (create it if it doesn't exist).
|
||||
4. Add these variables to your `flamenco-manager.yaml` file:
|
||||
- `storagePath`: Your NAS path, multi-platform variable.
|
||||
- `jobSubPath`: Where the jobs are stored inside `storagePath`.
|
||||
- `renderSubpath`: Where the render output is stored inside `storagePath`.
|
||||
- `deviceType`: Compute Device Type to force. Do not set the variable if you wish to use whatever is available.
|
||||
5. Submit your job from Blender with the corresponding Multi-Pass Job, it should
|
||||
whatever compositor nodes you have set and correct the paths where necessary.
|
||||
|
||||
[compositorrepo]: https://github.com/dblanque/flamenco-compositor-script/archive/refs/heads/main.zip
|
||||
|
||||
{{< hint type=warning >}}
|
||||
This has only been tested in an environment with [Shaman][shaman] enabled, but it should work without Shaman as well.
|
||||
|
||||
[shaman]: {{< ref "/usage/shared-storage/shaman" >}}
|
||||
{{< /hint >}}
|
||||
|
||||
|
||||
# Example Configuration Flamenco Manager YAML
|
||||
|
||||
```yaml
|
||||
# Configuration file for Flamenco.
|
||||
#
|
||||
# For an explanation of the fields,
|
||||
# refer to the original flamenco-manager-example.yaml
|
||||
|
||||
_meta:
|
||||
version: 3
|
||||
manager_name: Flamenco Manager
|
||||
database: flamenco-manager.sqlite
|
||||
listen: :8080
|
||||
autodiscoverable: true
|
||||
local_manager_storage_path: ./flamenco-manager-storage
|
||||
shared_storage_path: /mnt/storage/project_files
|
||||
shaman:
|
||||
enabled: true
|
||||
garbageCollect:
|
||||
period: 24h0m0s
|
||||
maxAge: 744h0m0s
|
||||
extraCheckoutPaths: []
|
||||
task_timeout: 10m0s
|
||||
worker_timeout: 1m0s
|
||||
blocklist_threshold: 3
|
||||
task_fail_after_softfail_count: 3
|
||||
variables:
|
||||
blender:
|
||||
values:
|
||||
- platform: all
|
||||
value: blender
|
||||
- platform: linux
|
||||
value: /usr/local/blender/blender
|
||||
- platform: windows
|
||||
value: C:/Program Files/Blender Foundation/Blender 3.4/blender.exe
|
||||
- platform: darwin
|
||||
value: /usr/bin/blender
|
||||
blenderArgs:
|
||||
values:
|
||||
- platform: all
|
||||
value: -b -y
|
||||
storagePath:
|
||||
values:
|
||||
- platform: linux
|
||||
value: /mnt/storage
|
||||
- platform: windows
|
||||
value: "Z:\\"
|
||||
jobSubPath:
|
||||
values:
|
||||
- platform: all
|
||||
value: project_files
|
||||
renderSubPath:
|
||||
values:
|
||||
- platform: all
|
||||
value: project_render
|
||||
deviceType:
|
||||
values:
|
||||
- platform: all
|
||||
value: "CUDA"
|
||||
# Set the device type to FIRST or remove the variable definition
|
||||
# to use whatever device type is detected first.
|
||||
```
|
@ -25,9 +25,13 @@ The difference with regular variables is that regular variables are one-way:
|
||||
|
||||
Two-way variables go both ways, as follows:
|
||||
|
||||
- When submitting a job, values are replaced with variables.
|
||||
- When submitting a **job**, values **in the javascript jobs' command** are replaced
|
||||
with the corresponding variables as it's executed on the client.
|
||||
- When sending a task to a worker, variables are replaced with values again.
|
||||
|
||||
*(Do keep in mind that if you perform changes to a job, you'll need to re-submit*
|
||||
*it.)*
|
||||
|
||||
This may seem like a lot of unnecessary work. After all, why go through the
|
||||
trouble of replacing in one direction, when later the opposite is done? The
|
||||
power lies in the fact that each replacement step can target a different
|
||||
|
@ -0,0 +1,19 @@
|
||||
{{/* This is an adjusted copy of themes/hugo-geekdoc/layouts/shortcodes/button.html */}}
|
||||
{{- $ref := .Page.Site.Params.Flamenco.bugreportURL -}}
|
||||
{{- $class := "" }}
|
||||
{{- $size := default "regular" (.Get "size" | lower) }}
|
||||
|
||||
{{- if not (in (slice "regular" "large" "small") $size) }}
|
||||
{{- $size = "regular" }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Get "class" }}
|
||||
{{- $class = . }}
|
||||
{{- end }}
|
||||
|
||||
<span class="gdoc-button gdoc-button--{{ $size }}{{ with $class }}{{ printf " %s" . }}{{ end }}">
|
||||
<a
|
||||
class="gdoc-button__link"
|
||||
{{- with $ref }}{{ printf " href=\"%s\"" . | safeHTMLAttr }}{{ end }}
|
||||
>Report a Bug</a>
|
||||
</span>
|
@ -0,0 +1,11 @@
|
||||
{{/* This is an adjusted copy of themes/hugo-geekdoc/layouts/shortcodes/button.html */}}
|
||||
{{- $ref := .Page.Site.Params.Flamenco.bugreportURL -}}
|
||||
{{- $class := "" }}
|
||||
|
||||
{{- with .Get "class" }}
|
||||
{{- $class = . }}
|
||||
{{- end }}
|
||||
|
||||
<a class="{{ with $class }}{{ printf " %s" . }}{{ end }}"
|
||||
{{- with $ref }}{{ printf " href=\"%s\"" . | safeHTMLAttr }}{{ end }}
|
||||
>{{ $.Inner }}</a>
|
@ -0,0 +1,20 @@
|
||||
<!-- For more info check the links below -->
|
||||
<!-- Lists: https://gohugo.io/templates/lists/ -->
|
||||
<!-- Taxonomy Templates: https://gohugo.io/templates/taxonomy-templates/ -->
|
||||
<!-- Page Variables: https://gohugo.io/variables/pages/ -->
|
||||
<!-- Cheers, Dylan -->
|
||||
|
||||
<!-- This TOC Excludes the current section index and the current page -->
|
||||
|
||||
<div>
|
||||
{{ $current_page_title := .Page.Title }}
|
||||
<ul>
|
||||
{{ range .Page.CurrentSection.Data.Pages.ByWeight }}
|
||||
{{ if ne .Page.Title $current_page_title }}
|
||||
<li>
|
||||
<a href="{{ .Permalink }}">{{ .LinkTitle }}</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user