WIP: 103268-job-task-progress #104185

Draft
Nitin-Rawat-1 wants to merge 19 commits from Nitin-Rawat-1/flamenco:103268-job-task-progress into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
30 changed files with 1729 additions and 219 deletions

View File

@ -26,6 +26,7 @@ from flamenco.manager.model.error import Error
from flamenco.manager.model.may_keep_running import MayKeepRunning from flamenco.manager.model.may_keep_running import MayKeepRunning
from flamenco.manager.model.registered_worker import RegisteredWorker from flamenco.manager.model.registered_worker import RegisteredWorker
from flamenco.manager.model.security_error import SecurityError from flamenco.manager.model.security_error import SecurityError
from flamenco.manager.model.task_progress_update import TaskProgressUpdate
from flamenco.manager.model.task_update import TaskUpdate from flamenco.manager.model.task_update import TaskUpdate
from flamenco.manager.model.worker_registration import WorkerRegistration from flamenco.manager.model.worker_registration import WorkerRegistration
from flamenco.manager.model.worker_sign_on import WorkerSignOn from flamenco.manager.model.worker_sign_on import WorkerSignOn
@ -344,6 +345,64 @@ class WorkerApi(object):
}, },
api_client=api_client api_client=api_client
) )
self.task_progress_update_endpoint = _Endpoint(
settings={
'response_type': None,
'auth': [
'worker_auth'
],
'endpoint_path': '/api/v3/worker/task/{task_id}/progress',
'operation_id': 'task_progress_update',
'http_method': 'POST',
'servers': None,
},
params_map={
'all': [
'task_id',
'task_progress_update',
],
'required': [
'task_id',
'task_progress_update',
],
'nullable': [
],
'enum': [
],
'validation': [
]
},
root_map={
'validations': {
},
'allowed_values': {
},
'openapi_types': {
'task_id':
(str,),
'task_progress_update':
(TaskProgressUpdate,),
},
'attribute_map': {
'task_id': 'task_id',
},
'location_map': {
'task_id': 'path',
'task_progress_update': 'body',
},
'collection_format_map': {
}
},
headers_map={
'accept': [
'application/json'
],
'content_type': [
'application/json'
]
},
api_client=api_client
)
self.task_update_endpoint = _Endpoint( self.task_update_endpoint = _Endpoint(
settings={ settings={
'response_type': None, 'response_type': None,
@ -955,6 +1014,87 @@ class WorkerApi(object):
body body
return self.task_output_produced_endpoint.call_with_http_info(**kwargs) return self.task_output_produced_endpoint.call_with_http_info(**kwargs)
def task_progress_update(
self,
task_id,
task_progress_update,
**kwargs
):
"""Update the progress of the task. # noqa: E501
This method makes a synchronous HTTP request by default. To make an
asynchronous HTTP request, please pass async_req=True
>>> thread = api.task_progress_update(task_id, task_progress_update, async_req=True)
>>> result = thread.get()
Args:
task_id (str):
task_progress_update (TaskProgressUpdate): Task progress information
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['task_id'] = \
task_id
kwargs['task_progress_update'] = \
task_progress_update
return self.task_progress_update_endpoint.call_with_http_info(**kwargs)
def task_update( def task_update(
self, self,
task_id, task_id,

View File

@ -44,7 +44,7 @@ class WorkerMgtApi(object):
self.api_client = api_client self.api_client = api_client
self.create_worker_cluster_endpoint = _Endpoint( self.create_worker_cluster_endpoint = _Endpoint(
settings={ settings={
'response_type': None, 'response_type': (WorkerCluster,),
'auth': [], 'auth': [],
'endpoint_path': '/api/v3/worker-mgt/clusters', 'endpoint_path': '/api/v3/worker-mgt/clusters',
'operation_id': 'create_worker_cluster', 'operation_id': 'create_worker_cluster',
@ -691,7 +691,7 @@ class WorkerMgtApi(object):
async_req (bool): execute request asynchronously async_req (bool): execute request asynchronously
Returns: Returns:
None WorkerCluster
If the method is called asynchronously, returns the request If the method is called asynchronously, returns the request
thread. thread.
""" """

View File

@ -0,0 +1,15 @@
# SocketIOTaskProgressUpdate
Task progress, sent to a SocketIO room when progress of a task changes.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **str** | UUID of the Task |
**job_id** | **str** | |
**progress** | **int** | Indicates the percentage of the task that's been completed. |
**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)

View File

@ -0,0 +1,13 @@
# TaskProgressUpdate
TaskProgressUpdate is sent by a Worker to update the progress of a task it's executing.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**progress** | **int** | Indicates the percentage of the task that's been completed. |
**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)

View File

@ -10,6 +10,7 @@ Method | HTTP request | Description
[**sign_off**](WorkerApi.md#sign_off) | **POST** /api/v3/worker/sign-off | Mark the worker as offline [**sign_off**](WorkerApi.md#sign_off) | **POST** /api/v3/worker/sign-off | Mark the worker as offline
[**sign_on**](WorkerApi.md#sign_on) | **POST** /api/v3/worker/sign-on | Authenticate & sign in the worker. [**sign_on**](WorkerApi.md#sign_on) | **POST** /api/v3/worker/sign-on | Authenticate & sign in the worker.
[**task_output_produced**](WorkerApi.md#task_output_produced) | **POST** /api/v3/worker/task/{task_id}/output-produced | Store the most recently rendered frame here. Note that it is up to the Worker to ensure this is in a format that's digestable by the Manager. Currently only PNG and JPEG support is planned. [**task_output_produced**](WorkerApi.md#task_output_produced) | **POST** /api/v3/worker/task/{task_id}/output-produced | Store the most recently rendered frame here. Note that it is up to the Worker to ensure this is in a format that's digestable by the Manager. Currently only PNG and JPEG support is planned.
[**task_progress_update**](WorkerApi.md#task_progress_update) | **POST** /api/v3/worker/task/{task_id}/progress | Update the progress of the task.
[**task_update**](WorkerApi.md#task_update) | **POST** /api/v3/worker/task/{task_id} | Update the task, typically to indicate progress, completion, or failure. [**task_update**](WorkerApi.md#task_update) | **POST** /api/v3/worker/task/{task_id} | Update the task, typically to indicate progress, completion, or failure.
[**worker_state**](WorkerApi.md#worker_state) | **GET** /api/v3/worker/state | [**worker_state**](WorkerApi.md#worker_state) | **GET** /api/v3/worker/state |
[**worker_state_changed**](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. [**worker_state_changed**](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.
@ -485,6 +486,88 @@ void (empty response body)
[[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) [[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)
# **task_progress_update**
> task_progress_update(task_id, task_progress_update)
Update the progress of the task.
### Example
* Basic Authentication (worker_auth):
```python
import time
import flamenco.manager
from flamenco.manager.api import worker_api
from flamenco.manager.model.error import Error
from flamenco.manager.model.task_progress_update import TaskProgressUpdate
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"
)
# The client must configure the authentication and authorization parameters
# in accordance with the API server security policy.
# Examples for each auth method are provided below, use the example that
# satisfies your auth use case.
# Configure HTTP basic authorization: worker_auth
configuration = flamenco.manager.Configuration(
username = 'YOUR_USERNAME',
password = 'YOUR_PASSWORD'
)
# Enter a context with an instance of the API client
with flamenco.manager.ApiClient(configuration) as api_client:
# Create an instance of the API class
api_instance = worker_api.WorkerApi(api_client)
task_id = "task_id_example" # str |
task_progress_update = TaskProgressUpdate(
progress=0,
) # TaskProgressUpdate | Task progress information
# example passing only required values which don't have defaults set
try:
# Update the progress of the task.
api_instance.task_progress_update(task_id, task_progress_update)
except flamenco.manager.ApiException as e:
print("Exception when calling WorkerApi->task_progress_update: %s\n" % e)
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**task_id** | **str**| |
**task_progress_update** | [**TaskProgressUpdate**](TaskProgressUpdate.md)| Task progress information |
### Return type
void (empty response body)
### Authorization
[worker_auth](../README.md#worker_auth)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
### HTTP response details
| Status code | Description | Response headers |
|-------------|-------------|------------------|
**204** | The progress update was accepted | - |
**409** | The task is assigned to another worker, so the progress update was not 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)
# **task_update** # **task_update**
> task_update(task_id, task_update) > task_update(task_id, task_update)

View File

@ -19,7 +19,7 @@ Method | HTTP request | Description
# **create_worker_cluster** # **create_worker_cluster**
> create_worker_cluster(worker_cluster) > WorkerCluster create_worker_cluster(worker_cluster)
Create a new worker cluster. Create a new worker cluster.
@ -53,7 +53,8 @@ with flamenco.manager.ApiClient() as api_client:
# example passing only required values which don't have defaults set # example passing only required values which don't have defaults set
try: try:
# Create a new worker cluster. # Create a new worker cluster.
api_instance.create_worker_cluster(worker_cluster) api_response = api_instance.create_worker_cluster(worker_cluster)
pprint(api_response)
except flamenco.manager.ApiException as e: except flamenco.manager.ApiException as e:
print("Exception when calling WorkerMgtApi->create_worker_cluster: %s\n" % e) print("Exception when calling WorkerMgtApi->create_worker_cluster: %s\n" % e)
``` ```
@ -67,7 +68,7 @@ Name | Type | Description | Notes
### Return type ### Return type
void (empty response body) [**WorkerCluster**](WorkerCluster.md)
### Authorization ### Authorization
@ -83,7 +84,7 @@ No authorization required
| Status code | Description | Response headers | | Status code | Description | Response headers |
|-------------|-------------|------------------| |-------------|-------------|------------------|
**204** | The cluster was created. | - | **200** | The cluster was created. The created cluster is returned, so that the caller can know its UUID. | - |
**0** | Error message | - | **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) [[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)

View File

@ -0,0 +1,277 @@
"""
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 SocketIOTaskProgressUpdate(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 = {
('progress',): {
'inclusive_maximum': 100,
'inclusive_minimum': 0,
},
}
@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 {
'id': (str,), # noqa: E501
'job_id': (str,), # noqa: E501
'progress': (int,), # noqa: E501
}
@cached_property
def discriminator():
return None
attribute_map = {
'id': 'id', # noqa: E501
'job_id': 'job_id', # noqa: E501
'progress': 'progress', # noqa: E501
}
read_only_vars = {
}
_composed_schemas = {}
@classmethod
@convert_js_args_to_python_args
def _from_openapi_data(cls, id, job_id, progress, *args, **kwargs): # noqa: E501
"""SocketIOTaskProgressUpdate - a model defined in OpenAPI
Args:
id (str): UUID of the Task
job_id (str):
progress (int): Indicates the percentage of the task that's been completed.
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.id = id
self.job_id = job_id
self.progress = progress
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, id, job_id, progress, *args, **kwargs): # noqa: E501
"""SocketIOTaskProgressUpdate - a model defined in OpenAPI
Args:
id (str): UUID of the Task
job_id (str):
progress (int): Indicates the percentage of the task that's been completed.
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.id = id
self.job_id = job_id
self.progress = progress
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.")

View File

@ -0,0 +1,265 @@
"""
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 TaskProgressUpdate(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 = {
('progress',): {
'inclusive_maximum': 100,
'inclusive_minimum': 0,
},
}
@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 {
'progress': (int,), # noqa: E501
}
@cached_property
def discriminator():
return None
attribute_map = {
'progress': 'progress', # noqa: E501
}
read_only_vars = {
}
_composed_schemas = {}
@classmethod
@convert_js_args_to_python_args
def _from_openapi_data(cls, progress, *args, **kwargs): # noqa: E501
"""TaskProgressUpdate - a model defined in OpenAPI
Args:
progress (int): Indicates the percentage of the task that's been completed.
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.progress = progress
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, progress, *args, **kwargs): # noqa: E501
"""TaskProgressUpdate - a model defined in OpenAPI
Args:
progress (int): Indicates the percentage of the task that's been completed.
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.progress = progress
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.")

View File

@ -62,11 +62,13 @@ from flamenco.manager.model.socket_io_subscription import SocketIOSubscription
from flamenco.manager.model.socket_io_subscription_operation import SocketIOSubscriptionOperation from flamenco.manager.model.socket_io_subscription_operation import SocketIOSubscriptionOperation
from flamenco.manager.model.socket_io_subscription_type import SocketIOSubscriptionType from flamenco.manager.model.socket_io_subscription_type import SocketIOSubscriptionType
from flamenco.manager.model.socket_io_task_log_update import SocketIOTaskLogUpdate from flamenco.manager.model.socket_io_task_log_update import SocketIOTaskLogUpdate
from flamenco.manager.model.socket_io_task_progress_update import SocketIOTaskProgressUpdate
from flamenco.manager.model.socket_io_task_update import SocketIOTaskUpdate from flamenco.manager.model.socket_io_task_update import SocketIOTaskUpdate
from flamenco.manager.model.socket_io_worker_update import SocketIOWorkerUpdate from flamenco.manager.model.socket_io_worker_update import SocketIOWorkerUpdate
from flamenco.manager.model.submitted_job import SubmittedJob from flamenco.manager.model.submitted_job import SubmittedJob
from flamenco.manager.model.task import Task from flamenco.manager.model.task import Task
from flamenco.manager.model.task_log_info import TaskLogInfo from flamenco.manager.model.task_log_info import TaskLogInfo
from flamenco.manager.model.task_progress_update import TaskProgressUpdate
from flamenco.manager.model.task_status import TaskStatus from flamenco.manager.model.task_status import TaskStatus
from flamenco.manager.model.task_status_change import TaskStatusChange from flamenco.manager.model.task_status_change import TaskStatusChange
from flamenco.manager.model.task_summary import TaskSummary from flamenco.manager.model.task_summary import TaskSummary

View File

@ -113,6 +113,7 @@ Class | Method | HTTP request | Description
*WorkerApi* | [**sign_off**](flamenco/manager/docs/WorkerApi.md#sign_off) | **POST** /api/v3/worker/sign-off | Mark the worker as offline *WorkerApi* | [**sign_off**](flamenco/manager/docs/WorkerApi.md#sign_off) | **POST** /api/v3/worker/sign-off | Mark the worker as offline
*WorkerApi* | [**sign_on**](flamenco/manager/docs/WorkerApi.md#sign_on) | **POST** /api/v3/worker/sign-on | Authenticate & sign in the worker. *WorkerApi* | [**sign_on**](flamenco/manager/docs/WorkerApi.md#sign_on) | **POST** /api/v3/worker/sign-on | Authenticate & sign in the worker.
*WorkerApi* | [**task_output_produced**](flamenco/manager/docs/WorkerApi.md#task_output_produced) | **POST** /api/v3/worker/task/{task_id}/output-produced | Store the most recently rendered frame here. Note that it is up to the Worker to ensure this is in a format that's digestable by the Manager. Currently only PNG and JPEG support is planned. *WorkerApi* | [**task_output_produced**](flamenco/manager/docs/WorkerApi.md#task_output_produced) | **POST** /api/v3/worker/task/{task_id}/output-produced | Store the most recently rendered frame here. Note that it is up to the Worker to ensure this is in a format that's digestable by the Manager. Currently only PNG and JPEG support is planned.
*WorkerApi* | [**task_progress_update**](flamenco/manager/docs/WorkerApi.md#task_progress_update) | **POST** /api/v3/worker/task/{task_id}/progress | Update the progress of the task.
*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* | [**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**](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. *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.
@ -185,11 +186,13 @@ Class | Method | HTTP request | Description
- [SocketIOSubscriptionOperation](flamenco/manager/docs/SocketIOSubscriptionOperation.md) - [SocketIOSubscriptionOperation](flamenco/manager/docs/SocketIOSubscriptionOperation.md)
- [SocketIOSubscriptionType](flamenco/manager/docs/SocketIOSubscriptionType.md) - [SocketIOSubscriptionType](flamenco/manager/docs/SocketIOSubscriptionType.md)
- [SocketIOTaskLogUpdate](flamenco/manager/docs/SocketIOTaskLogUpdate.md) - [SocketIOTaskLogUpdate](flamenco/manager/docs/SocketIOTaskLogUpdate.md)
- [SocketIOTaskProgressUpdate](flamenco/manager/docs/SocketIOTaskProgressUpdate.md)
- [SocketIOTaskUpdate](flamenco/manager/docs/SocketIOTaskUpdate.md) - [SocketIOTaskUpdate](flamenco/manager/docs/SocketIOTaskUpdate.md)
- [SocketIOWorkerUpdate](flamenco/manager/docs/SocketIOWorkerUpdate.md) - [SocketIOWorkerUpdate](flamenco/manager/docs/SocketIOWorkerUpdate.md)
- [SubmittedJob](flamenco/manager/docs/SubmittedJob.md) - [SubmittedJob](flamenco/manager/docs/SubmittedJob.md)
- [Task](flamenco/manager/docs/Task.md) - [Task](flamenco/manager/docs/Task.md)
- [TaskLogInfo](flamenco/manager/docs/TaskLogInfo.md) - [TaskLogInfo](flamenco/manager/docs/TaskLogInfo.md)
- [TaskProgressUpdate](flamenco/manager/docs/TaskProgressUpdate.md)
- [TaskStatus](flamenco/manager/docs/TaskStatus.md) - [TaskStatus](flamenco/manager/docs/TaskStatus.md)
- [TaskStatusChange](flamenco/manager/docs/TaskStatusChange.md) - [TaskStatusChange](flamenco/manager/docs/TaskStatusChange.md)
- [TaskSummary](flamenco/manager/docs/TaskSummary.md) - [TaskSummary](flamenco/manager/docs/TaskSummary.md)

View File

@ -373,6 +373,58 @@ func (f *Flamenco) ScheduleTask(e echo.Context) error {
return e.JSON(http.StatusOK, customisedTask) return e.JSON(http.StatusOK, customisedTask)
} }
// TODO: 1) Broadcast the udpates to the frontend client
// TODO: 2) Write tests for the following function
func (f *Flamenco) TaskProgressUpdate(e echo.Context, taskID string) error {
logger := requestLogger(e)
worker := requestWorkerOrPanic(e)
if !uuid.IsValid(taskID) {
logger.Debug().Msg("Invalid task ID received")
return sendAPIError(e, http.StatusBadRequest, "Task ID not valid")
}
logger = logger.With().Str("taskID", taskID).Logger()
// Fetch the task, to see if this worker is even allowed to send udpates.
ctx := e.Request().Context()
dbTask, err := f.persist.FetchTask(ctx, taskID)
if err != nil {
logger.Warn().Err(err).Msg("cannot fetch task")
if errors.Is(err, persistence.ErrTaskNotFound) {
return sendAPIError(e, http.StatusNotFound, "task %+v not found", taskID)
}
return sendAPIError(e, http.StatusInternalServerError, "error fetching task")
}
if dbTask == nil {
panic("task could not be fetched, but database gave no error either")
}
// Decode the request body.
var taskProgressUpdate api.TaskProgressUpdate
if err := e.Bind(&taskProgressUpdate); err != nil {
logger.Warn().Err(err).Msg("bad request received")
return sendAPIError(e, http.StatusBadRequest, "invalid format")
}
if dbTask.WorkerID == nil {
logger.Warn().Msg("worker trying to update task that's not assigned to any worker")
return sendAPIError(e, http.StatusConflict, "task %+v is not assigned to any worker, so also not to you", taskID)
}
if *dbTask.WorkerID != worker.ID {
logger.Warn().Msg("worker trying to update task that's assigned to another worker")
return sendAPIError(e, http.StatusConflict, "task %+v is not assigned to you", taskID)
}
// for testing ..............................
fmt.Println("----------------------------")
fmt.Print("Progress: ")
fmt.Println(taskProgressUpdate.Progress)
fmt.Println("----------------------------")
//...........................................
return e.NoContent(http.StatusNoContent)
}
func (f *Flamenco) TaskOutputProduced(e echo.Context, taskID string) error { func (f *Flamenco) TaskOutputProduced(e echo.Context, taskID string) error {
ctx := e.Request().Context() ctx := e.Request().Context()
filesize := e.Request().ContentLength filesize := e.Request().ContentLength

View File

@ -111,6 +111,11 @@ func jsFrameChunker(frameRange string, chunkSize int) ([]string, error) {
return chunks, nil return chunks, nil
} }
func jsCountChunkSize(chunk string) (int, error) {
frames, err := frameRangeExplode(chunk)
return len(frames), err
}
// Given a range of frames, return an array containing each frame number. // Given a range of frames, return an array containing each frame number.
func frameRangeExplode(frameRange string) ([]int, error) { func frameRangeExplode(frameRange string) ([]int, error) {
// Store as map to avoid duplicate frames. // Store as map to avoid duplicate frames.

View File

@ -56,3 +56,38 @@ func TestFrameRangeExplode(t *testing.T) {
20, 21, 22, 23, 24, 25, 40, 20, 21, 22, 23, 24, 25, 40,
}, frames) }, frames)
} }
func TestJSCountChunkSize(t *testing.T) {
tests := []struct {
name string
chunk string
want int
wantErr bool
}{
// Bad cases.
{"empty", "", 0, true},
{"negative", "-5", 0, true},
{"space", " ", 0, true},
{"no-comma", "5 10", 0, true},
{"no-numbers", "start-end", 0, true},
// Good cases.
{"single", "4", 1, false},
{"multiple", "4,5,10", 3, false},
{"onerange", "1-4", 4, false},
{"tworange", "1-4,10-12", 7, false},
{"overlap", "1-6,3-12", 12, false},
{"space-around", " 1-5\t", 5, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := jsCountChunkSize(tt.chunk)
if (err != nil) != tt.wantErr {
t.Errorf("jsCountChunkSize() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("jsCountChunkSize() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -140,6 +140,7 @@ func newGojaVM(registry *require.Registry) *goja.Runtime {
mustSet("alert", jsAlert) mustSet("alert", jsAlert)
mustSet("frameChunker", jsFrameChunker) mustSet("frameChunker", jsFrameChunker)
mustSet("formatTimestampLocal", jsFormatTimestampLocal) mustSet("formatTimestampLocal", jsFormatTimestampLocal)
mustSet("countChunkSize", jsCountChunkSize)
// Pre-import some useful modules. // Pre-import some useful modules.
registry.Enable(vm) registry.Enable(vm)

View File

@ -95,12 +95,14 @@ function authorRenderTasks(settings, renderDir, renderOutput) {
let renderTasks = []; let renderTasks = [];
let chunks = frameChunker(settings.frames, settings.chunk_size); let chunks = frameChunker(settings.frames, settings.chunk_size);
for (let chunk of chunks) { for (let chunk of chunks) {
const numFrames = countChunkSize(chunk)
const task = author.Task(`render-${chunk}`, "blender"); const task = author.Task(`render-${chunk}`, "blender");
const command = author.Command("blender-render", { const command = author.Command("blender-render", {
exe: "{blender}", exe: "{blender}",
exeArgs: "{blenderArgs}", exeArgs: "{blenderArgs}",
argsBefore: [], argsBefore: [],
blendfile: settings.blendfile, blendfile: settings.blendfile,
numFrames: numFrames,
args: [ args: [
"--render-output", path.join(renderDir, path.basename(renderOutput)), "--render-output", path.join(renderDir, path.basename(renderOutput)),
"--render-format", settings.format, "--render-format", settings.format,

View File

@ -7,6 +7,7 @@ package worker
import ( import (
"context" "context"
"fmt" "fmt"
"math"
"os/exec" "os/exec"
"regexp" "regexp"
"sync" "sync"
@ -20,6 +21,10 @@ import (
) )
var regexpFileSaved = regexp.MustCompile("Saved: '(.*)'") var regexpFileSaved = regexp.MustCompile("Saved: '(.*)'")
var regexpFrameNumber = regexp.MustCompile("Fra:[0-9]+")
var prevFrame string

I don't see the need to declare these variables here.

I don't see the need to declare these variables here.
var renderedNumFrames int
var totalNumFrames float64
type BlenderParameters struct { type BlenderParameters struct {
exe string // Expansion of `{blender}`: the executable path defined by the Manager. exe string // Expansion of `{blender}`: the executable path defined by the Manager.
@ -27,6 +32,7 @@ type BlenderParameters struct {
argsBefore []string // Additional CLI arguments defined by the job compiler script, to go before the blend file name. argsBefore []string // Additional CLI arguments defined by the job compiler script, to go before the blend file name.
blendfile string // Path of the file to open. blendfile string // Path of the file to open.
args []string // Additional CLI arguments defined by the job compiler script, to go after the blend file name. args []string // Additional CLI arguments defined by the job compiler script, to go after the blend file name.
numFrames float64 // Additional CLI argument defined by the job compiler script, to define the total number of frame that are needed to be rendered.

This is not a CLI argument. It's just for Flamenco's bookkeeping, and won't be passed to Blender in any way.

This is not a CLI argument. It's just for Flamenco's bookkeeping, and won't be passed to Blender in any way.
} }
// cmdBlender executes the "blender-render" command. // cmdBlender executes the "blender-render" command.
@ -46,6 +52,8 @@ func (ce *CommandExecutor) cmdBlenderRender(ctx context.Context, logger zerolog.
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
wg.Add(1) wg.Add(1)
go func() { go func() {
prevFrame = ""
renderedNumFrames = 0

Why are these set here? They're not used in this goroutine. Also Go initializes variables to the zero value, so it's likely to be a no-op. It can cause a race condition, though, because the goroutine runs asynchronously from the rest of the code.

Why are these set here? They're not used in this goroutine. Also Go initializes variables to the zero value, so it's likely to be a no-op. It can cause a race condition, though, because the goroutine runs asynchronously from the rest of the code.
defer wg.Done() defer wg.Done()
for line := range lineChannel { for line := range lineChannel {
ce.processLineBlender(ctx, logger, taskID, line) ce.processLineBlender(ctx, logger, taskID, line)
@ -138,6 +146,12 @@ func cmdBlenderRenderParams(logger zerolog.Logger, cmd api.Command) (BlenderPara
// Ignore the `ok` return value, as a missing exeArgs key is fine: // Ignore the `ok` return value, as a missing exeArgs key is fine:
parameters.exeArgs, _ = cmdParameter[string](cmd, "exeArgs") parameters.exeArgs, _ = cmdParameter[string](cmd, "exeArgs")
// Ignore the `ok` return value, as a missing numFrames key is fine:
parameters.numFrames, _ = cmdParameter[float64](cmd, "numFrames")
if parameters.numFrames != 0 {

This if is not necessary. totalNumFrames is initialized to zero anyway, so if paramters.numFrames == 0, the assignment can safely be done as it just reassigns zero.

This `if` is not necessary. `totalNumFrames` is initialized to zero anyway, so if `paramters.numFrames == 0`, the assignment can safely be done as it just reassigns zero.
totalNumFrames = parameters.numFrames
}
if parameters.argsBefore, ok = cmdParameterAsStrings(cmd, "argsBefore"); !ok { if parameters.argsBefore, ok = cmdParameterAsStrings(cmd, "argsBefore"); !ok {
logger.Warn().Interface("command", cmd).Msg("invalid 'argsBefore' parameter") logger.Warn().Interface("command", cmd).Msg("invalid 'argsBefore' parameter")
return parameters, NewParameterInvalidError("argsBefore", cmd, "cannot convert to list of strings") return parameters, NewParameterInvalidError("argsBefore", cmd, "cannot convert to list of strings")
@ -170,6 +184,22 @@ func cmdBlenderRenderParams(logger zerolog.Logger, cmd api.Command) (BlenderPara
func (ce *CommandExecutor) processLineBlender(ctx context.Context, logger zerolog.Logger, taskID string, line string) { func (ce *CommandExecutor) processLineBlender(ctx context.Context, logger zerolog.Logger, taskID string, line string) {
// TODO: check for "Warning: Unable to open" and other indicators of missing // TODO: check for "Warning: Unable to open" and other indicators of missing
// files. Flamenco v2 updated the task.Activity field for such situations. // files. Flamenco v2 updated the task.Activity field for such situations.
renderedFrameNumber := regexpFrameNumber.FindString(line)
if renderedFrameNumber != "" && renderedFrameNumber != prevFrame {
renderedNumFrames++
prevFrame = renderedFrameNumber
var progress = int(math.Ceil(float64(renderedNumFrames) / totalNumFrames * 100))
// for checking the out of progress
fmt.Println("---------------------------")
fmt.Println(prevFrame)
fmt.Println(progress)
fmt.Println("---------------------------")
err := ce.listener.UpdateTaskProgress(ctx, taskID, progress)
if err != nil {
logger.Warn().Err(err).Msg("error send progress udpate to manager.")
}
}
match := regexpFileSaved.FindStringSubmatch(line) match := regexpFileSaved.FindStringSubmatch(line)
if len(match) < 2 { if len(match) < 2 {

View File

@ -39,6 +39,8 @@ type CommandListener interface {
LogProduced(ctx context.Context, taskID string, logLines ...string) error LogProduced(ctx context.Context, taskID string, logLines ...string) error
// OutputProduced tells the Manager there has been some output (most commonly a rendered frame or video). // OutputProduced tells the Manager there has been some output (most commonly a rendered frame or video).
OutputProduced(ctx context.Context, taskID string, outputLocation string) error OutputProduced(ctx context.Context, taskID string, outputLocation string) error
// UpdateTaskProgress sends the progress update of the task to manager
UpdateTaskProgress(ctx context.Context, taskID string, progress int) error
} }
// TimeService is a service that operates on time. // TimeService is a service that operates on time.

View File

@ -6,6 +6,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net/http"
"strings" "strings"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -91,9 +92,35 @@ func (l *Listener) OutputProduced(ctx context.Context, taskID string, outputLoca
return nil return nil
} }
func (l *Listener) UpdateTaskProgress(ctx context.Context, taskID string, progress int) error {
return l.sendProgressUpdate(ctx, taskID, api.TaskProgressUpdateJSONRequestBody{
Progress: progress,
})
}
func (l *Listener) sendTaskUpdate(ctx context.Context, taskID string, update api.TaskUpdateJSONRequestBody) error { func (l *Listener) sendTaskUpdate(ctx context.Context, taskID string, update api.TaskUpdateJSONRequestBody) error {
if ctx.Err() != nil { if ctx.Err() != nil {
return ctx.Err() return ctx.Err()
} }
return l.buffer.SendTaskUpdate(ctx, taskID, update) return l.buffer.SendTaskUpdate(ctx, taskID, update)
} }
func (l *Listener) sendProgressUpdate(ctx context.Context, taskID string, progress api.TaskProgressUpdateJSONRequestBody) error {
if ctx.Err() != nil {
return ctx.Err()
}
resp, err := l.client.TaskProgressUpdateWithResponse(ctx, taskID, progress)

This should not use the API client directly. If the Manager cannot be reached, the progress updates should be queued in the buffer, just like regular task updates.

This should not use the API client directly. If the Manager cannot be reached, the progress updates should be queued in the buffer, just like regular task updates.
if err != nil {
log.Warn().Err(err).Str("task", taskID).Msg("Error communicating with the Manager, unable to send progress update")
return fmt.Errorf("%v", err)
}
switch resp.StatusCode() {
case http.StatusNoContent:
return nil
default:
return fmt.Errorf("unknown error from Manager, code %d: %v",
resp.StatusCode(), resp.JSONDefault)
}
}

View File

@ -1376,6 +1376,46 @@ func (mr *MockFlamencoClientMockRecorder) TaskOutputProducedWithBodyWithResponse
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TaskOutputProducedWithBodyWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).TaskOutputProducedWithBodyWithResponse), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TaskOutputProducedWithBodyWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).TaskOutputProducedWithBodyWithResponse), varargs...)
} }
// TaskProgressUpdateWithBodyWithResponse mocks base method.
func (m *MockFlamencoClient) TaskProgressUpdateWithBodyWithResponse(arg0 context.Context, arg1, arg2 string, arg3 io.Reader, arg4 ...api.RequestEditorFn) (*api.TaskProgressUpdateResponse, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1, arg2, arg3}
for _, a := range arg4 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "TaskProgressUpdateWithBodyWithResponse", varargs...)
ret0, _ := ret[0].(*api.TaskProgressUpdateResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// TaskProgressUpdateWithBodyWithResponse indicates an expected call of TaskProgressUpdateWithBodyWithResponse.
func (mr *MockFlamencoClientMockRecorder) TaskProgressUpdateWithBodyWithResponse(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, "TaskProgressUpdateWithBodyWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).TaskProgressUpdateWithBodyWithResponse), varargs...)
}
// TaskProgressUpdateWithResponse mocks base method.
func (m *MockFlamencoClient) TaskProgressUpdateWithResponse(arg0 context.Context, arg1 string, arg2 api.TaskProgressUpdateJSONRequestBody, arg3 ...api.RequestEditorFn) (*api.TaskProgressUpdateResponse, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1, arg2}
for _, a := range arg3 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "TaskProgressUpdateWithResponse", varargs...)
ret0, _ := ret[0].(*api.TaskProgressUpdateResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// TaskProgressUpdateWithResponse indicates an expected call of TaskProgressUpdateWithResponse.
func (mr *MockFlamencoClientMockRecorder) TaskProgressUpdateWithResponse(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, "TaskProgressUpdateWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).TaskProgressUpdateWithResponse), varargs...)
}
// TaskUpdateWithBodyWithResponse mocks base method. // TaskUpdateWithBodyWithResponse mocks base method.
func (m *MockFlamencoClient) TaskUpdateWithBodyWithResponse(arg0 context.Context, arg1, arg2 string, arg3 io.Reader, arg4 ...api.RequestEditorFn) (*api.TaskUpdateResponse, error) { func (m *MockFlamencoClient) TaskUpdateWithBodyWithResponse(arg0 context.Context, arg1, arg2 string, arg3 io.Reader, arg4 ...api.RequestEditorFn) (*api.TaskUpdateResponse, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@ -66,3 +66,17 @@ func (mr *MockCommandListenerMockRecorder) OutputProduced(arg0, arg1, arg2 inter
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OutputProduced", reflect.TypeOf((*MockCommandListener)(nil).OutputProduced), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OutputProduced", reflect.TypeOf((*MockCommandListener)(nil).OutputProduced), arg0, arg1, arg2)
} }
// UpdateTaskProgress mocks base method.
func (m *MockCommandListener) UpdateTaskProgress(arg0 context.Context, arg1 string, arg2 int) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateTaskProgress", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateTaskProgress indicates an expected call of UpdateTaskProgress.
func (mr *MockCommandListenerMockRecorder) UpdateTaskProgress(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTaskProgress", reflect.TypeOf((*MockCommandListener)(nil).UpdateTaskProgress), arg0, arg1, arg2)
}

View File

@ -454,6 +454,37 @@ paths:
schema: schema:
$ref: "#/components/schemas/Error" $ref: "#/components/schemas/Error"
/api/v3/worker/task/{task_id}/progress:
summary: Workers send the progress of the task, in the form of integer percentage value here.
post:
operationId: taskProgressUpdate
summary: Update the progress of the task.
security: [{ worker_auth: [] }]
tags: [worker]
parameters:
- name: task_id
in: path
required: true
schema: { type: string, format: uuid }
requestBody:
description: Task progress information
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TaskProgressUpdate"
responses:
"204":
description: The progress update was accepted
"409":
description: The task is assigned to another worker, so the progress update was not accepted.
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
# Worker Management # Worker Management
/api/v3/worker-mgt/workers: /api/v3/worker-mgt/workers:
summary: Obtain list of Workers known to the Manager. summary: Obtain list of Workers known to the Manager.
@ -1581,6 +1612,20 @@ components:
type: string type: string
description: Log lines for this task, will be appended to logs sent earlier. description: Log lines for this task, will be appended to logs sent earlier.
TaskProgressUpdate:
type: object
description: >
TaskProgressUpdate is sent by a Worker to update the progress of a task
it's executing.
properties:
"progress":
type: integer
minimum: 0
maximum: 100
description: >
Indicates the percentage of the task that's been completed.
required: ["progress"]
MayKeepRunning: MayKeepRunning:
type: object type: object
description: Indicates whether the worker may keep running the task. description: Indicates whether the worker may keep running the task.
@ -2233,6 +2278,24 @@ components:
"activity": { type: string } "activity": { type: string }
required: [id, job_id, name, updated, status, activity] required: [id, job_id, name, updated, status, activity]
SocketIOTaskProgressUpdate:
type: object
description: >
Task progress, sent to a SocketIO room when progress of a task changes.
properties:
"id":
type: string
format: uuid
description: UUID of the Task
"job_id": { type: string, format: uuid }
"progress":
type: integer
minimum: 0
maximum: 100
description: >
Indicates the percentage of the task that's been completed.
required: [id, job_id, progress]
SocketIOTaskLogUpdate: SocketIOTaskLogUpdate:
type: object type: object
description: > description: >

View File

@ -292,6 +292,11 @@ type ClientInterface interface {
// TaskOutputProduced request with any body // TaskOutputProduced request with any body
TaskOutputProducedWithBody(ctx context.Context, taskId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) TaskOutputProducedWithBody(ctx context.Context, taskId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
// TaskProgressUpdate request with any body
TaskProgressUpdateWithBody(ctx context.Context, taskId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
TaskProgressUpdate(ctx context.Context, taskId string, body TaskProgressUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
} }
func (c *Client) GetConfiguration(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { func (c *Client) GetConfiguration(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
@ -1182,6 +1187,30 @@ func (c *Client) TaskOutputProducedWithBody(ctx context.Context, taskId string,
return c.Client.Do(req) return c.Client.Do(req)
} }
func (c *Client) TaskProgressUpdateWithBody(ctx context.Context, taskId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewTaskProgressUpdateRequestWithBody(c.Server, taskId, 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) TaskProgressUpdate(ctx context.Context, taskId string, body TaskProgressUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewTaskProgressUpdateRequest(c.Server, taskId, 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)
}
// NewGetConfigurationRequest generates requests for GetConfiguration // NewGetConfigurationRequest generates requests for GetConfiguration
func NewGetConfigurationRequest(server string) (*http.Request, error) { func NewGetConfigurationRequest(server string) (*http.Request, error) {
var err error var err error
@ -3150,6 +3179,53 @@ func NewTaskOutputProducedRequestWithBody(server string, taskId string, contentT
return req, nil return req, nil
} }
// NewTaskProgressUpdateRequest calls the generic TaskProgressUpdate builder with application/json body
func NewTaskProgressUpdateRequest(server string, taskId string, body TaskProgressUpdateJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
buf, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewReader(buf)
return NewTaskProgressUpdateRequestWithBody(server, taskId, "application/json", bodyReader)
}
// NewTaskProgressUpdateRequestWithBody generates requests for TaskProgressUpdate with any type of body
func NewTaskProgressUpdateRequestWithBody(server string, taskId string, contentType string, body io.Reader) (*http.Request, error) {
var err error
var pathParam0 string
pathParam0, err = runtime.StyleParamWithLocation("simple", false, "task_id", runtime.ParamLocationPath, taskId)
if err != nil {
return nil, err
}
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/api/v3/worker/task/%s/progress", 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
}
func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error {
for _, r := range c.RequestEditors { for _, r := range c.RequestEditors {
if err := r(ctx, req); err != nil { if err := r(ctx, req); err != nil {
@ -3393,6 +3469,11 @@ type ClientWithResponsesInterface interface {
// TaskOutputProduced request with any body // TaskOutputProduced request with any body
TaskOutputProducedWithBodyWithResponse(ctx context.Context, taskId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TaskOutputProducedResponse, error) TaskOutputProducedWithBodyWithResponse(ctx context.Context, taskId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TaskOutputProducedResponse, error)
// TaskProgressUpdate request with any body
TaskProgressUpdateWithBodyWithResponse(ctx context.Context, taskId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TaskProgressUpdateResponse, error)
TaskProgressUpdateWithResponse(ctx context.Context, taskId string, body TaskProgressUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*TaskProgressUpdateResponse, error)
} }
type GetConfigurationResponse struct { type GetConfigurationResponse struct {
@ -4209,6 +4290,7 @@ func (r FetchWorkerClustersResponse) StatusCode() int {
type CreateWorkerClusterResponse struct { type CreateWorkerClusterResponse struct {
Body []byte Body []byte
HTTPResponse *http.Response HTTPResponse *http.Response
JSON200 *WorkerCluster
JSONDefault *Error JSONDefault *Error
} }
@ -4591,6 +4673,28 @@ func (r TaskOutputProducedResponse) StatusCode() int {
return 0 return 0
} }
type TaskProgressUpdateResponse struct {
Body []byte
HTTPResponse *http.Response
JSONDefault *Error
}
// Status returns HTTPResponse.Status
func (r TaskProgressUpdateResponse) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r TaskProgressUpdateResponse) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
// GetConfigurationWithResponse request returning *GetConfigurationResponse // GetConfigurationWithResponse request returning *GetConfigurationResponse
func (c *ClientWithResponses) GetConfigurationWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetConfigurationResponse, error) { func (c *ClientWithResponses) GetConfigurationWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetConfigurationResponse, error) {
rsp, err := c.GetConfiguration(ctx, reqEditors...) rsp, err := c.GetConfiguration(ctx, reqEditors...)
@ -5236,6 +5340,23 @@ func (c *ClientWithResponses) TaskOutputProducedWithBodyWithResponse(ctx context
return ParseTaskOutputProducedResponse(rsp) return ParseTaskOutputProducedResponse(rsp)
} }
// TaskProgressUpdateWithBodyWithResponse request with arbitrary body returning *TaskProgressUpdateResponse
func (c *ClientWithResponses) TaskProgressUpdateWithBodyWithResponse(ctx context.Context, taskId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*TaskProgressUpdateResponse, error) {
rsp, err := c.TaskProgressUpdateWithBody(ctx, taskId, contentType, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseTaskProgressUpdateResponse(rsp)
}
func (c *ClientWithResponses) TaskProgressUpdateWithResponse(ctx context.Context, taskId string, body TaskProgressUpdateJSONRequestBody, reqEditors ...RequestEditorFn) (*TaskProgressUpdateResponse, error) {
rsp, err := c.TaskProgressUpdate(ctx, taskId, body, reqEditors...)
if err != nil {
return nil, err
}
return ParseTaskProgressUpdateResponse(rsp)
}
// ParseGetConfigurationResponse parses an HTTP response from a GetConfigurationWithResponse call // ParseGetConfigurationResponse parses an HTTP response from a GetConfigurationWithResponse call
func ParseGetConfigurationResponse(rsp *http.Response) (*GetConfigurationResponse, error) { func ParseGetConfigurationResponse(rsp *http.Response) (*GetConfigurationResponse, error) {
bodyBytes, err := ioutil.ReadAll(rsp.Body) bodyBytes, err := ioutil.ReadAll(rsp.Body)
@ -6307,6 +6428,13 @@ func ParseCreateWorkerClusterResponse(rsp *http.Response) (*CreateWorkerClusterR
} }
switch { 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: case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true:
var dest Error var dest Error
if err := json.Unmarshal(bodyBytes, &dest); err != nil { if err := json.Unmarshal(bodyBytes, &dest); err != nil {
@ -6811,3 +6939,29 @@ func ParseTaskOutputProducedResponse(rsp *http.Response) (*TaskOutputProducedRes
return response, nil return response, nil
} }
// ParseTaskProgressUpdateResponse parses an HTTP response from a TaskProgressUpdateWithResponse call
func ParseTaskProgressUpdateResponse(rsp *http.Response) (*TaskProgressUpdateResponse, error) {
bodyBytes, err := ioutil.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &TaskProgressUpdateResponse{
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
}

View File

@ -173,6 +173,9 @@ type ServerInterface interface {
// Store the most recently rendered frame here. Note that it is up to the Worker to ensure this is in a format that's digestable by the Manager. Currently only PNG and JPEG support is planned. // Store the most recently rendered frame here. Note that it is up to the Worker to ensure this is in a format that's digestable by the Manager. Currently only PNG and JPEG support is planned.
// (POST /api/v3/worker/task/{task_id}/output-produced) // (POST /api/v3/worker/task/{task_id}/output-produced)
TaskOutputProduced(ctx echo.Context, taskId string) error TaskOutputProduced(ctx echo.Context, taskId string) error
// Update the progress of the task.
// (POST /api/v3/worker/task/{task_id}/progress)
TaskProgressUpdate(ctx echo.Context, taskId string) error
} }
// ServerInterfaceWrapper converts echo contexts to parameters. // ServerInterfaceWrapper converts echo contexts to parameters.
@ -950,6 +953,24 @@ func (w *ServerInterfaceWrapper) TaskOutputProduced(ctx echo.Context) error {
return err return err
} }
// TaskProgressUpdate converts echo context to params.
func (w *ServerInterfaceWrapper) TaskProgressUpdate(ctx echo.Context) error {
var err error
// ------------- Path parameter "task_id" -------------
var taskId string
err = runtime.BindStyledParameterWithLocation("simple", false, "task_id", runtime.ParamLocationPath, ctx.Param("task_id"), &taskId)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter task_id: %s", err))
}
ctx.Set(Worker_authScopes, []string{""})
// Invoke the callback with all the unmarshalled arguments
err = w.Handler.TaskProgressUpdate(ctx, taskId)
return err
}
// This is a simple interface which specifies echo.Route addition functions which // This is a simple interface which specifies echo.Route addition functions which
// are present on both echo.Echo and echo.Group, since we want to allow using // are present on both echo.Echo and echo.Group, since we want to allow using
// either of them for path registration // either of them for path registration
@ -1031,5 +1052,6 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
router.POST(baseURL+"/api/v3/worker/task/:task_id", wrapper.TaskUpdate) router.POST(baseURL+"/api/v3/worker/task/:task_id", wrapper.TaskUpdate)
router.GET(baseURL+"/api/v3/worker/task/:task_id/may-i-run", wrapper.MayWorkerRun) router.GET(baseURL+"/api/v3/worker/task/:task_id/may-i-run", wrapper.MayWorkerRun)
router.POST(baseURL+"/api/v3/worker/task/:task_id/output-produced", wrapper.TaskOutputProduced) router.POST(baseURL+"/api/v3/worker/task/:task_id/output-produced", wrapper.TaskOutputProduced)
router.POST(baseURL+"/api/v3/worker/task/:task_id/progress", wrapper.TaskProgressUpdate)
} }

View File

@ -18,216 +18,220 @@ import (
// Base64 encoded, gzipped, json marshaled Swagger object // Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{ var swaggerSpec = []string{
"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",
} }
// GetSwagger returns the content of the embedded swagger specification file // GetSwagger returns the content of the embedded swagger specification file

View File

@ -578,6 +578,16 @@ type SocketIOTaskLogUpdate struct {
TaskId string `json:"task_id"` TaskId string `json:"task_id"`
} }
// Task progress, sent to a SocketIO room when progress of a task changes.
type SocketIOTaskProgressUpdate struct {
// UUID of the Task
Id string `json:"id"`
JobId string `json:"job_id"`
// Indicates the percentage of the task that's been completed.
Progress int `json:"progress"`
}
// Subset of a Task, sent over SocketIO when a task changes. For new tasks, `previous_status` will be excluded. // Subset of a Task, sent over SocketIO when a task changes. For new tasks, `previous_status` will be excluded.
type SocketIOTaskUpdate struct { type SocketIOTaskUpdate struct {
Activity string `json:"activity"` Activity string `json:"activity"`
@ -680,6 +690,12 @@ type TaskLogInfo struct {
Url string `json:"url"` Url string `json:"url"`
} }
// TaskProgressUpdate is sent by a Worker to update the progress of a task it's executing.
type TaskProgressUpdate struct {
// Indicates the percentage of the task that's been completed.
Progress int `json:"progress"`
}
// TaskStatus defines model for TaskStatus. // TaskStatus defines model for TaskStatus.
type TaskStatus string type TaskStatus string
@ -901,6 +917,9 @@ type WorkerStateChangedJSONBody WorkerStateChanged
// TaskUpdateJSONBody defines parameters for TaskUpdate. // TaskUpdateJSONBody defines parameters for TaskUpdate.
type TaskUpdateJSONBody TaskUpdate type TaskUpdateJSONBody TaskUpdate
// TaskProgressUpdateJSONBody defines parameters for TaskProgressUpdate.
type TaskProgressUpdateJSONBody TaskProgressUpdate
// CheckBlenderExePathJSONRequestBody defines body for CheckBlenderExePath for application/json ContentType. // CheckBlenderExePathJSONRequestBody defines body for CheckBlenderExePath for application/json ContentType.
type CheckBlenderExePathJSONRequestBody CheckBlenderExePathJSONBody type CheckBlenderExePathJSONRequestBody CheckBlenderExePathJSONBody
@ -964,6 +983,9 @@ type WorkerStateChangedJSONRequestBody WorkerStateChangedJSONBody
// TaskUpdateJSONRequestBody defines body for TaskUpdate for application/json ContentType. // TaskUpdateJSONRequestBody defines body for TaskUpdate for application/json ContentType.
type TaskUpdateJSONRequestBody TaskUpdateJSONBody type TaskUpdateJSONRequestBody TaskUpdateJSONBody
// TaskProgressUpdateJSONRequestBody defines body for TaskProgressUpdate for application/json ContentType.
type TaskProgressUpdateJSONRequestBody TaskProgressUpdateJSONBody
// Getter for additional properties for JobMetadata. Returns the specified // Getter for additional properties for JobMetadata. Returns the specified
// element and whether it was found // element and whether it was found
func (a JobMetadata) Get(fieldName string) (value string, found bool) { func (a JobMetadata) Get(fieldName string) (value string, found bool) {

View File

@ -61,11 +61,13 @@ import SocketIOSubscription from './model/SocketIOSubscription';
import SocketIOSubscriptionOperation from './model/SocketIOSubscriptionOperation'; import SocketIOSubscriptionOperation from './model/SocketIOSubscriptionOperation';
import SocketIOSubscriptionType from './model/SocketIOSubscriptionType'; import SocketIOSubscriptionType from './model/SocketIOSubscriptionType';
import SocketIOTaskLogUpdate from './model/SocketIOTaskLogUpdate'; import SocketIOTaskLogUpdate from './model/SocketIOTaskLogUpdate';
import SocketIOTaskProgressUpdate from './model/SocketIOTaskProgressUpdate';
import SocketIOTaskUpdate from './model/SocketIOTaskUpdate'; import SocketIOTaskUpdate from './model/SocketIOTaskUpdate';
import SocketIOWorkerUpdate from './model/SocketIOWorkerUpdate'; import SocketIOWorkerUpdate from './model/SocketIOWorkerUpdate';
import SubmittedJob from './model/SubmittedJob'; import SubmittedJob from './model/SubmittedJob';
import Task from './model/Task'; import Task from './model/Task';
import TaskLogInfo from './model/TaskLogInfo'; import TaskLogInfo from './model/TaskLogInfo';
import TaskProgressUpdate from './model/TaskProgressUpdate';
import TaskStatus from './model/TaskStatus'; import TaskStatus from './model/TaskStatus';
import TaskStatusChange from './model/TaskStatusChange'; import TaskStatusChange from './model/TaskStatusChange';
import TaskSummary from './model/TaskSummary'; import TaskSummary from './model/TaskSummary';
@ -420,6 +422,12 @@ export {
*/ */
SocketIOTaskLogUpdate, SocketIOTaskLogUpdate,
/**
* The SocketIOTaskProgressUpdate model constructor.
* @property {module:model/SocketIOTaskProgressUpdate}
*/
SocketIOTaskProgressUpdate,
/** /**
* The SocketIOTaskUpdate model constructor. * The SocketIOTaskUpdate model constructor.
* @property {module:model/SocketIOTaskUpdate} * @property {module:model/SocketIOTaskUpdate}
@ -450,6 +458,12 @@ export {
*/ */
TaskLogInfo, TaskLogInfo,
/**
* The TaskProgressUpdate model constructor.
* @property {module:model/TaskProgressUpdate}
*/
TaskProgressUpdate,
/** /**
* The TaskStatus model constructor. * The TaskStatus model constructor.
* @property {module:model/TaskStatus} * @property {module:model/TaskStatus}

View File

@ -18,6 +18,7 @@ import Error from '../model/Error';
import MayKeepRunning from '../model/MayKeepRunning'; import MayKeepRunning from '../model/MayKeepRunning';
import RegisteredWorker from '../model/RegisteredWorker'; import RegisteredWorker from '../model/RegisteredWorker';
import SecurityError from '../model/SecurityError'; import SecurityError from '../model/SecurityError';
import TaskProgressUpdate from '../model/TaskProgressUpdate';
import TaskUpdate from '../model/TaskUpdate'; import TaskUpdate from '../model/TaskUpdate';
import WorkerRegistration from '../model/WorkerRegistration'; import WorkerRegistration from '../model/WorkerRegistration';
import WorkerSignOn from '../model/WorkerSignOn'; import WorkerSignOn from '../model/WorkerSignOn';
@ -310,6 +311,58 @@ export default class WorkerApi {
} }
/**
* Update the progress of the task.
* @param {String} taskId
* @param {module:model/TaskProgressUpdate} taskProgressUpdate Task progress information
* @return {Promise} a {@link https://www.promisejs.org/|Promise}, with an object containing HTTP response
*/
taskProgressUpdateWithHttpInfo(taskId, taskProgressUpdate) {
let postBody = taskProgressUpdate;
// verify the required parameter 'taskId' is set
if (taskId === undefined || taskId === null) {
throw new Error("Missing the required parameter 'taskId' when calling taskProgressUpdate");
}
// verify the required parameter 'taskProgressUpdate' is set
if (taskProgressUpdate === undefined || taskProgressUpdate === null) {
throw new Error("Missing the required parameter 'taskProgressUpdate' when calling taskProgressUpdate");
}
let pathParams = {
'task_id': taskId
};
let queryParams = {
};
let headerParams = {
};
let formParams = {
};
let authNames = ['worker_auth'];
let contentTypes = ['application/json'];
let accepts = ['application/json'];
let returnType = null;
return this.apiClient.callApi(
'/api/v3/worker/task/{task_id}/progress', 'POST',
pathParams, queryParams, headerParams, formParams, postBody,
authNames, contentTypes, accepts, returnType, null
);
}
/**
* Update the progress of the task.
* @param {String} taskId
* @param {module:model/TaskProgressUpdate} taskProgressUpdate Task progress information
* @return {Promise} a {@link https://www.promisejs.org/|Promise}
*/
taskProgressUpdate(taskId, taskProgressUpdate) {
return this.taskProgressUpdateWithHttpInfo(taskId, taskProgressUpdate)
.then(function(response_and_data) {
return response_and_data.data;
});
}
/** /**
* Update the task, typically to indicate progress, completion, or failure. * Update the task, typically to indicate progress, completion, or failure.
* @param {String} taskId * @param {String} taskId

View File

@ -45,7 +45,7 @@ export default class WorkerMgtApi {
/** /**
* Create a new worker cluster. * Create a new worker cluster.
* @param {module:model/WorkerCluster} workerCluster The worker cluster. * @param {module:model/WorkerCluster} workerCluster The worker cluster.
* @return {Promise} a {@link https://www.promisejs.org/|Promise}, with an object containing HTTP response * @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) { createWorkerClusterWithHttpInfo(workerCluster) {
let postBody = workerCluster; let postBody = workerCluster;
@ -66,7 +66,7 @@ export default class WorkerMgtApi {
let authNames = []; let authNames = [];
let contentTypes = ['application/json']; let contentTypes = ['application/json'];
let accepts = ['application/json']; let accepts = ['application/json'];
let returnType = null; let returnType = WorkerCluster;
return this.apiClient.callApi( return this.apiClient.callApi(
'/api/v3/worker-mgt/clusters', 'POST', '/api/v3/worker-mgt/clusters', 'POST',
pathParams, queryParams, headerParams, formParams, postBody, pathParams, queryParams, headerParams, formParams, postBody,
@ -77,7 +77,7 @@ export default class WorkerMgtApi {
/** /**
* Create a new worker cluster. * Create a new worker cluster.
* @param {module:model/WorkerCluster} workerCluster The worker cluster. * @param {module:model/WorkerCluster} workerCluster The worker cluster.
* @return {Promise} a {@link https://www.promisejs.org/|Promise} * @return {Promise} a {@link https://www.promisejs.org/|Promise}, with data of type {@link module:model/WorkerCluster}
*/ */
createWorkerCluster(workerCluster) { createWorkerCluster(workerCluster) {
return this.createWorkerClusterWithHttpInfo(workerCluster) return this.createWorkerClusterWithHttpInfo(workerCluster)

View File

@ -0,0 +1,96 @@
/**
* 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 SocketIOTaskProgressUpdate model module.
* @module model/SocketIOTaskProgressUpdate
* @version 0.0.0
*/
class SocketIOTaskProgressUpdate {
/**
* Constructs a new <code>SocketIOTaskProgressUpdate</code>.
* Task progress, sent to a SocketIO room when progress of a task changes.
* @alias module:model/SocketIOTaskProgressUpdate
* @param id {String} UUID of the Task
* @param jobId {String}
* @param progress {Number} Indicates the percentage of the task that's been completed.
*/
constructor(id, jobId, progress) {
SocketIOTaskProgressUpdate.initialize(this, id, jobId, progress);
}
/**
* 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, id, jobId, progress) {
obj['id'] = id;
obj['job_id'] = jobId;
obj['progress'] = progress;
}
/**
* Constructs a <code>SocketIOTaskProgressUpdate</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/SocketIOTaskProgressUpdate} obj Optional instance to populate.
* @return {module:model/SocketIOTaskProgressUpdate} The populated <code>SocketIOTaskProgressUpdate</code> instance.
*/
static constructFromObject(data, obj) {
if (data) {
obj = obj || new SocketIOTaskProgressUpdate();
if (data.hasOwnProperty('id')) {
obj['id'] = ApiClient.convertToType(data['id'], 'String');
}
if (data.hasOwnProperty('job_id')) {
obj['job_id'] = ApiClient.convertToType(data['job_id'], 'String');
}
if (data.hasOwnProperty('progress')) {
obj['progress'] = ApiClient.convertToType(data['progress'], 'Number');
}
}
return obj;
}
}
/**
* UUID of the Task
* @member {String} id
*/
SocketIOTaskProgressUpdate.prototype['id'] = undefined;
/**
* @member {String} job_id
*/
SocketIOTaskProgressUpdate.prototype['job_id'] = undefined;
/**
* Indicates the percentage of the task that's been completed.
* @member {Number} progress
*/
SocketIOTaskProgressUpdate.prototype['progress'] = undefined;
export default SocketIOTaskProgressUpdate;

View File

@ -0,0 +1,75 @@
/**
* 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 TaskProgressUpdate model module.
* @module model/TaskProgressUpdate
* @version 0.0.0
*/
class TaskProgressUpdate {
/**
* Constructs a new <code>TaskProgressUpdate</code>.
* TaskProgressUpdate is sent by a Worker to update the progress of a task it&#39;s executing.
* @alias module:model/TaskProgressUpdate
* @param progress {Number} Indicates the percentage of the task that's been completed.
*/
constructor(progress) {
TaskProgressUpdate.initialize(this, progress);
}
/**
* 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, progress) {
obj['progress'] = progress;
}
/**
* Constructs a <code>TaskProgressUpdate</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/TaskProgressUpdate} obj Optional instance to populate.
* @return {module:model/TaskProgressUpdate} The populated <code>TaskProgressUpdate</code> instance.
*/
static constructFromObject(data, obj) {
if (data) {
obj = obj || new TaskProgressUpdate();
if (data.hasOwnProperty('progress')) {
obj['progress'] = ApiClient.convertToType(data['progress'], 'Number');
}
}
return obj;
}
}
/**
* Indicates the percentage of the task that's been completed.
* @member {Number} progress
*/
TaskProgressUpdate.prototype['progress'] = undefined;
export default TaskProgressUpdate;