diff --git a/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py b/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py index c28042b6ae..ebd4b8f944 100644 --- a/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/aftereffects/plugins/publish/collect_render.py @@ -24,7 +24,7 @@ class AERenderInstance(RenderInstance): class CollectAERender(publish.AbstractCollectRender): - order = pyblish.api.CollectorOrder + 0.405 + order = pyblish.api.CollectorOrder + 0.100 label = "Collect After Effects Render Layers" hosts = ["aftereffects"] @@ -145,6 +145,7 @@ class CollectAERender(publish.AbstractCollectRender): if "review" in instance.families: # to skip ExtractReview locally instance.families.remove("review") + instance.deadline = inst.data.get("deadline") instances.append(instance) diff --git a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py index 7a2844d5db..95d029aad4 100644 --- a/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py +++ b/client/ayon_core/hosts/fusion/plugins/publish/collect_render.py @@ -115,6 +115,7 @@ class CollectFusionRender( if "review" in instance.families: # to skip ExtractReview locally instance.families.remove("review") + instance.deadline = inst.data.get("deadline") instances.append(instance) diff --git a/client/ayon_core/hosts/harmony/plugins/publish/collect_farm_render.py b/client/ayon_core/hosts/harmony/plugins/publish/collect_farm_render.py index 156e2ac6ba..c63eb114e5 100644 --- a/client/ayon_core/hosts/harmony/plugins/publish/collect_farm_render.py +++ b/client/ayon_core/hosts/harmony/plugins/publish/collect_farm_render.py @@ -177,7 +177,10 @@ class CollectFarmRender(publish.AbstractCollectRender): outputFormat=info[1], outputStartFrame=info[3], leadingZeros=info[2], - ignoreFrameHandleCheck=True + ignoreFrameHandleCheck=True, + #todo: inst is not available, must be determined, fix when + #reworking to Publisher + # deadline=inst.data.get("deadline") ) render_instance.context = context diff --git a/client/ayon_core/modules/deadline/__init__.py b/client/ayon_core/modules/deadline/__init__.py index 5631e501d8..683d8dbe4a 100644 --- a/client/ayon_core/modules/deadline/__init__.py +++ b/client/ayon_core/modules/deadline/__init__.py @@ -1,6 +1,8 @@ from .deadline_module import DeadlineModule +from .version import __version__ __all__ = ( "DeadlineModule", + "__version__" ) diff --git a/client/ayon_core/modules/deadline/abstract_submit_deadline.py b/client/ayon_core/modules/deadline/abstract_submit_deadline.py index 2e0518ae20..00e51100bc 100644 --- a/client/ayon_core/modules/deadline/abstract_submit_deadline.py +++ b/client/ayon_core/modules/deadline/abstract_submit_deadline.py @@ -49,6 +49,10 @@ def requests_post(*args, **kwargs): if 'verify' not in kwargs: kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa + + auth = kwargs.get("auth") + if auth: + kwargs["auth"] = tuple(auth) # explicit cast to tuple # add 10sec timeout before bailing out kwargs['timeout'] = 10 return requests.post(*args, **kwargs) @@ -70,6 +74,9 @@ def requests_get(*args, **kwargs): if 'verify' not in kwargs: kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa + auth = kwargs.get("auth") + if auth: + kwargs["auth"] = tuple(auth) # add 10sec timeout before bailing out kwargs['timeout'] = 10 return requests.get(*args, **kwargs) @@ -434,9 +441,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, """Plugin entry point.""" self._instance = instance context = instance.context - self._deadline_url = context.data.get("defaultDeadline") - self._deadline_url = instance.data.get( - "deadlineUrl", self._deadline_url) + self._deadline_url = instance.data["deadline"]["url"] assert self._deadline_url, "Requires Deadline Webservice URL" @@ -460,7 +465,8 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, self.plugin_info = self.get_plugin_info() self.aux_files = self.get_aux_files() - job_id = self.process_submission() + auth = instance.data["deadline"]["auth"] + job_id = self.process_submission(auth) self.log.info("Submitted job to Deadline: {}.".format(job_id)) # TODO: Find a way that's more generic and not render type specific @@ -473,10 +479,10 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, job_info=render_job_info, plugin_info=render_plugin_info ) - render_job_id = self.submit(payload) + render_job_id = self.submit(payload, auth) self.log.info("Render job id: %s", render_job_id) - def process_submission(self): + def process_submission(self, auth=None): """Process data for submission. This takes Deadline JobInfo, PluginInfo, AuxFile, creates payload @@ -487,7 +493,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, """ payload = self.assemble_payload() - return self.submit(payload) + return self.submit(payload, auth) @abstractmethod def get_job_info(self): @@ -577,7 +583,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, "AuxFiles": aux_files or self.aux_files } - def submit(self, payload): + def submit(self, payload, auth): """Submit payload to Deadline API end-point. This takes payload in the form of JSON file and POST it to @@ -585,6 +591,7 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, Args: payload (dict): dict to become json in deadline submission. + auth (tuple): (username, password) Returns: str: resulting Deadline job id. @@ -594,7 +601,8 @@ class AbstractSubmitDeadline(pyblish.api.InstancePlugin, """ url = "{}/api/jobs".format(self._deadline_url) - response = requests_post(url, json=payload) + response = requests_post(url, json=payload, + auth=auth) if not response.ok: self.log.error("Submission failed!") self.log.error(response.status_code) diff --git a/client/ayon_core/modules/deadline/deadline_module.py b/client/ayon_core/modules/deadline/deadline_module.py index c0ba83477e..b1089bbfe2 100644 --- a/client/ayon_core/modules/deadline/deadline_module.py +++ b/client/ayon_core/modules/deadline/deadline_module.py @@ -19,23 +19,23 @@ class DeadlineModule(AYONAddon, IPluginPaths): def initialize(self, studio_settings): # This module is always enabled - deadline_urls = {} + deadline_servers_info = {} enabled = self.name in studio_settings if enabled: deadline_settings = studio_settings[self.name] - deadline_urls = { - url_item["name"]: url_item["value"] + deadline_servers_info = { + url_item["name"]: url_item for url_item in deadline_settings["deadline_urls"] } - if enabled and not deadline_urls: + if enabled and not deadline_servers_info: enabled = False self.log.warning(( "Deadline Webservice URLs are not specified. Disabling addon." )) self.enabled = enabled - self.deadline_urls = deadline_urls + self.deadline_servers_info = deadline_servers_info def get_plugin_paths(self): """Deadline plugin paths.""" @@ -45,13 +45,15 @@ class DeadlineModule(AYONAddon, IPluginPaths): } @staticmethod - def get_deadline_pools(webservice, log=None): + def get_deadline_pools(webservice, auth=None, log=None): """Get pools from Deadline. Args: webservice (str): Server url. - log (Logger) + auth (Optional[Tuple[str, str]]): Tuple containing username, + password + log (Optional[Logger]): Logger to log errors to, if provided. Returns: - list: Pools. + List[str]: Pools. Throws: RuntimeError: If deadline webservice is unreachable. @@ -63,7 +65,10 @@ class DeadlineModule(AYONAddon, IPluginPaths): argument = "{}/api/pools?NamesOnly=true".format(webservice) try: - response = requests_get(argument) + kwargs = {} + if auth: + kwargs["auth"] = auth + response = requests_get(argument, **kwargs) except requests.exceptions.ConnectionError as exc: msg = 'Cannot connect to DL web service {}'.format(webservice) log.error(msg) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py index ea4b7a213e..22022831a0 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_deadline_server_from_instance.py @@ -13,17 +13,45 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): """Collect Deadline Webservice URL from instance.""" # Run before collect_render. - order = pyblish.api.CollectorOrder + 0.005 + order = pyblish.api.CollectorOrder + 0.225 label = "Deadline Webservice from the Instance" - families = ["rendering", "renderlayer"] - hosts = ["maya"] + targets = ["local"] + families = ["render", + "rendering", + "render.farm", + "renderFarm", + "renderlayer", + "maxrender", + "usdrender", + "redshift_rop", + "arnold_rop", + "mantra_rop", + "karma_rop", + "vray_rop", + "publish.hou", + "image"] # for Fusion def process(self, instance): - instance.data["deadlineUrl"] = self._collect_deadline_url(instance) - instance.data["deadlineUrl"] = \ - instance.data["deadlineUrl"].strip().rstrip("/") + if not instance.data.get("farm"): + self.log.debug("Should not be processed on farm, skipping.") + return + + if not instance.data.get("deadline"): + instance.data["deadline"] = {} + + # todo: separate logic should be removed, all hosts should have same + host_name = instance.context.data["hostName"] + if host_name == "maya": + deadline_url = self._collect_deadline_url(instance) + else: + deadline_url = (instance.data.get("deadlineUrl") or # backwards + instance.data.get("deadline", {}).get("url")) + if deadline_url: + instance.data["deadline"]["url"] = deadline_url.strip().rstrip("/") + else: + instance.data["deadline"]["url"] = instance.context.data["deadline"]["defaultUrl"] # noqa self.log.debug( - "Using {} for submission.".format(instance.data["deadlineUrl"])) + "Using {} for submission".format(instance.data["deadline"]["url"])) def _collect_deadline_url(self, render_instance): # type: (pyblish.api.Instance) -> str @@ -49,13 +77,13 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): ["project_settings"] ["deadline"] ) - - default_server = render_instance.context.data["defaultDeadline"] + default_server_url = (render_instance.context.data["deadline"] + ["defaultUrl"]) # QUESTION How and where is this is set? Should be removed? instance_server = render_instance.data.get("deadlineServers") if not instance_server: self.log.debug("Using default server.") - return default_server + return default_server_url # Get instance server as sting. if isinstance(instance_server, int): @@ -66,7 +94,7 @@ class CollectDeadlineServerFromInstance(pyblish.api.InstancePlugin): default_servers = { url_item["name"]: url_item["value"] - for url_item in deadline_settings["deadline_urls"] + for url_item in deadline_settings["deadline_servers_info"] } project_servers = ( render_instance.context.data diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py index b7ca227b01..9238e0ed95 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_default_deadline_server.py @@ -18,10 +18,9 @@ class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin): """ # Run before collect_deadline_server_instance. - order = pyblish.api.CollectorOrder + 0.0025 + order = pyblish.api.CollectorOrder + 0.200 label = "Default Deadline Webservice" - - pass_mongo_url = False + targets = ["local"] def process(self, context): try: @@ -33,15 +32,17 @@ class CollectDefaultDeadlineServer(pyblish.api.ContextPlugin): deadline_settings = context.data["project_settings"]["deadline"] deadline_server_name = deadline_settings["deadline_server"] - deadline_webservice = None + dl_server_info = None if deadline_server_name: - deadline_webservice = deadline_module.deadline_urls.get( + dl_server_info = deadline_module.deadline_servers_info.get( deadline_server_name) - default_deadline_webservice = deadline_module.deadline_urls["default"] - deadline_webservice = ( - deadline_webservice - or default_deadline_webservice - ) + if dl_server_info: + deadline_url = dl_server_info["value"] + else: + default_dl_server_info = deadline_module.deadline_servers_info[0] + deadline_url = default_dl_server_info["value"] - context.data["defaultDeadline"] = deadline_webservice.strip().rstrip("/") # noqa + context.data["deadline"] = {} + context.data["deadline"]["defaultUrl"] = ( + deadline_url.strip().rstrip("/")) diff --git a/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py new file mode 100644 index 0000000000..5d03523c89 --- /dev/null +++ b/client/ayon_core/modules/deadline/plugins/publish/collect_user_credentials.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +"""Collect user credentials + +Requires: + context -> project_settings + instance.data["deadline"]["url"] + +Provides: + instance.data["deadline"] -> require_authentication (bool) + instance.data["deadline"] -> auth (tuple (str, str)) - + (username, password) or None +""" +import pyblish.api + +from ayon_api import get_server_api_connection +from ayon_core.modules.deadline.deadline_module import DeadlineModule +from ayon_core.modules.deadline import __version__ + + +class CollectDeadlineUserCredentials(pyblish.api.InstancePlugin): + """Collects user name and password for artist if DL requires authentication + """ + order = pyblish.api.CollectorOrder + 0.250 + label = "Collect Deadline User Credentials" + + targets = ["local"] + hosts = ["aftereffects", + "blender", + "fusion", + "harmony", + "nuke", + "maya", + "max", + "houdini"] + + families = ["render", + "rendering", + "render.farm", + "renderFarm", + "renderlayer", + "maxrender", + "usdrender", + "redshift_rop", + "arnold_rop", + "mantra_rop", + "karma_rop", + "vray_rop", + "publish.hou"] + + def process(self, instance): + if not instance.data.get("farm"): + self.log.debug("Should not be processed on farm, skipping.") + return + + collected_deadline_url = instance.data["deadline"]["url"] + if not collected_deadline_url: + raise ValueError("Instance doesn't have '[deadline][url]'.") + context_data = instance.context.data + deadline_settings = context_data["project_settings"]["deadline"] + + deadline_server_name = None + # deadline url might be set directly from instance, need to find + # metadata for it + for deadline_info in deadline_settings["deadline_urls"]: + dl_settings_url = deadline_info["value"].strip().rstrip("/") + if dl_settings_url == collected_deadline_url: + deadline_server_name = deadline_info["name"] + break + + if not deadline_server_name: + raise ValueError(f"Collected {collected_deadline_url} doesn't " + "match any site configured in Studio Settings") + + instance.data["deadline"]["require_authentication"] = ( + deadline_info["require_authentication"] + ) + instance.data["deadline"]["auth"] = None + + if not deadline_info["require_authentication"]: + return + # TODO import 'get_addon_site_settings' when available + # in public 'ayon_api' + local_settings = get_server_api_connection().get_addon_site_settings( + DeadlineModule.name, __version__) + local_settings = local_settings["local_settings"] + for server_info in local_settings: + if deadline_server_name == server_info["server_name"]: + instance.data["deadline"]["auth"] = (server_info["username"], + server_info["password"]) diff --git a/client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml b/client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml new file mode 100644 index 0000000000..eec05df08a --- /dev/null +++ b/client/ayon_core/modules/deadline/plugins/publish/help/validate_deadline_connection.xml @@ -0,0 +1,17 @@ + + + + Deadline Authentication + +## Deadline authentication is required + +This project has set in Settings that Deadline requires authentication. + +### How to repair? + +Please go to Ayon Server > Site Settings and provide your Deadline username and password. +In some cases the password may be empty if Deadline is configured to allow that. Ask your administrator. + + + + \ No newline at end of file diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py index ab342c1a9d..f5805beb5c 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_blender_deadline.py @@ -174,7 +174,8 @@ class BlenderSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, instance.data["toBeRenderedOn"] = "deadline" payload = self.assemble_payload() - return self.submit(payload) + return self.submit(payload, + auth=instance.data["deadline"]["auth"]) def from_published_scene(self): """ diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py index 1fae23c9b2..2220442dac 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_celaction_deadline.py @@ -2,9 +2,10 @@ import os import re import json import getpass -import requests import pyblish.api +from openpype_modules.deadline.abstract_submit_deadline import requests_post + class CelactionSubmitDeadline(pyblish.api.InstancePlugin): """Submit CelAction2D scene to Deadline @@ -30,11 +31,7 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): context = instance.context - # get default deadline webservice url from deadline module - deadline_url = instance.context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - deadline_url = instance.data.get("deadlineUrl") + deadline_url = instance.data["deadline"]["url"] assert deadline_url, "Requires Deadline Webservice URL" self.deadline_url = "{}/api/jobs".format(deadline_url) @@ -197,7 +194,8 @@ class CelactionSubmitDeadline(pyblish.api.InstancePlugin): self.log.debug("__ expectedFiles: `{}`".format( instance.data["expectedFiles"])) - response = requests.post(self.deadline_url, json=payload) + response = requests_post(self.deadline_url, json=payload, + auth=instance.data["deadline"]["require_authentication"]) if not response.ok: self.log.error( diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py index 1eba00a17f..e9b93a47cd 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_fusion_deadline.py @@ -2,10 +2,9 @@ import os import json import getpass -import requests - import pyblish.api +from openpype_modules.deadline.abstract_submit_deadline import requests_post from ayon_core.pipeline.publish import ( AYONPyblishPluginMixin ) @@ -82,11 +81,7 @@ class FusionSubmitDeadline( from ayon_core.hosts.fusion.api.lib import get_frame_path - # get default deadline webservice url from deadline module - deadline_url = instance.context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - deadline_url = instance.data.get("deadlineUrl") + deadline_url = instance.data["deadline"]["url"] assert deadline_url, "Requires Deadline Webservice URL" # Collect all saver instances in context that are to be rendered @@ -246,7 +241,8 @@ class FusionSubmitDeadline( # E.g. http://192.168.0.1:8082/api/jobs url = "{}/api/jobs".format(deadline_url) - response = requests.post(url, json=payload) + auth = instance.data["deadline"]["auth"] + response = requests_post(url, json=payload, auth=auth) if not response.ok: raise Exception(response.text) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py index cba05f6948..e9f6c382c5 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_max_deadline.py @@ -187,11 +187,13 @@ class MaxSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, payload_data, project_settings) job_infos, plugin_infos = payload for job_info, plugin_info in zip(job_infos, plugin_infos): - self.submit(self.assemble_payload(job_info, plugin_info)) + self.submit(self.assemble_payload(job_info, plugin_info), + instance.data["deadline"]["auth"]) else: payload = self._use_published_name(payload_data, project_settings) job_info, plugin_info = payload - self.submit(self.assemble_payload(job_info, plugin_info)) + self.submit(self.assemble_payload(job_info, plugin_info), + instance.data["deadline"]["auth"]) def _use_published_name(self, data, project_settings): # Not all hosts can import these modules. diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py index 0300b12104..250dc8b7ea 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -292,7 +292,7 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, return plugin_payload - def process_submission(self): + def process_submission(self, auth=None): from maya import cmds instance = self._instance @@ -332,7 +332,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, if "vrayscene" in instance.data["families"]: self.log.debug("Submitting V-Ray scene render..") vray_export_payload = self._get_vray_export_payload(payload_data) - export_job = self.submit(vray_export_payload) + export_job = self.submit(vray_export_payload, + instance.data["deadline"]["auth"]) payload = self._get_vray_render_payload(payload_data) @@ -351,7 +352,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, else: # Submit main render job job_info, plugin_info = payload - self.submit(self.assemble_payload(job_info, plugin_info)) + self.submit(self.assemble_payload(job_info, plugin_info), + instance.data["deadline"]["auth"]) def _tile_render(self, payload): """Submit as tile render per frame with dependent assembly jobs.""" @@ -451,7 +453,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, # Submit frame tile jobs frame_tile_job_id = {} for frame, tile_job_payload in frame_payloads.items(): - job_id = self.submit(tile_job_payload) + job_id = self.submit(tile_job_payload, + instance.data["deadline"]["auth"]) frame_tile_job_id[frame] = job_id # Define assembly payloads @@ -559,7 +562,8 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline, "submitting assembly job {} of {}".format(i + 1, num_assemblies) ) - assembly_job_id = self.submit(payload) + assembly_job_id = self.submit(payload, + instance.data["deadline"]["auth"]) assembly_job_ids.append(assembly_job_id) instance.data["assemblySubmissionJobs"] = assembly_job_ids diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py index 52a2ad122b..ef744ae1e1 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -4,9 +4,9 @@ import json import getpass from datetime import datetime -import requests import pyblish.api +from openpype_modules.deadline.abstract_submit_deadline import requests_post from ayon_core.pipeline.publish import ( AYONPyblishPluginMixin ) @@ -100,11 +100,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, node = instance.data["transientData"]["node"] context = instance.context - # get default deadline webservice url from deadline module - deadline_url = instance.context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - deadline_url = instance.data.get("deadlineUrl") + deadline_url = instance.data["deadline"]["url"] assert deadline_url, "Requires Deadline Webservice URL" self.deadline_url = "{}/api/jobs".format(deadline_url) @@ -427,7 +423,9 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin, self.log.debug("__ expectedFiles: `{}`".format( instance.data["expectedFiles"])) - response = requests.post(self.deadline_url, json=payload, timeout=10) + auth = instance.data["deadline"]["auth"] + response = requests_post(self.deadline_url, json=payload, timeout=10, + auth=auth) if not response.ok: raise Exception(response.text) diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py index ad7e5b8ed1..ce15eda9a0 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_cache_job.py @@ -5,10 +5,10 @@ import json import re from copy import deepcopy -import requests import ayon_api import pyblish.api +from openpype_modules.deadline.abstract_submit_deadline import requests_post from ayon_core.pipeline import publish from ayon_core.lib import EnumDef, is_in_tests from ayon_core.pipeline.version_start import get_versioning_start @@ -209,7 +209,9 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, self.log.debug("Submitting Deadline publish job ...") url = "{}/api/jobs".format(self.deadline_url) - response = requests.post(url, json=payload, timeout=10) + auth = instance.data["deadline"]["auth"] + response = requests_post(url, json=payload, timeout=10, + auth=auth) if not response.ok: raise Exception(response.text) @@ -341,11 +343,7 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, deadline_publish_job_id = None if submission_type == "deadline": - # get default deadline webservice url from deadline module - self.deadline_url = instance.context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - self.deadline_url = instance.data.get("deadlineUrl") + self.deadline_url = instance.data["deadline"]["url"] assert self.deadline_url, "Requires Deadline Webservice URL" deadline_publish_job_id = \ @@ -353,7 +351,9 @@ class ProcessSubmittedCacheJobOnFarm(pyblish.api.InstancePlugin, # Inject deadline url to instances. for inst in instances: - inst["deadlineUrl"] = self.deadline_url + if "deadline" not in inst: + inst["deadline"] = {} + inst["deadline"] = instance.data["deadline"] # publish job file publish_job = { diff --git a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py index 99a5f94cf1..06dd62e18b 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py +++ b/client/ayon_core/modules/deadline/plugins/publish/submit_publish_job.py @@ -5,11 +5,11 @@ import json import re from copy import deepcopy -import requests import clique import ayon_api import pyblish.api +from openpype_modules.deadline.abstract_submit_deadline import requests_post from ayon_core.pipeline import publish from ayon_core.lib import EnumDef, is_in_tests from ayon_core.pipeline.version_start import get_versioning_start @@ -303,7 +303,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, self.log.debug("Submitting Deadline publish job ...") url = "{}/api/jobs".format(self.deadline_url) - response = requests.post(url, json=payload, timeout=10) + auth = instance.data["deadline"]["auth"] + response = requests_post(url, json=payload, timeout=10, + auth=auth) if not response.ok: raise Exception(response.text) @@ -457,18 +459,17 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin, } # get default deadline webservice url from deadline module - self.deadline_url = instance.context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - self.deadline_url = instance.data.get("deadlineUrl") + self.deadline_url = instance.data["deadline"]["url"] assert self.deadline_url, "Requires Deadline Webservice URL" deadline_publish_job_id = \ self._submit_deadline_post_job(instance, render_job, instances) - # Inject deadline url to instances. + # Inject deadline url to instances to query DL for job id for overrides for inst in instances: - inst["deadlineUrl"] = self.deadline_url + if not "deadline" in inst: + inst["deadline"] = {} + inst["deadline"] = instance.data["deadline"] # publish job file publish_job = { diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py index a7b300beff..8fffd47786 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_connection.py @@ -1,5 +1,7 @@ import pyblish.api +from ayon_core.pipeline import PublishXmlValidationError + from openpype_modules.deadline.abstract_submit_deadline import requests_get @@ -8,27 +10,42 @@ class ValidateDeadlineConnection(pyblish.api.InstancePlugin): label = "Validate Deadline Web Service" order = pyblish.api.ValidatorOrder - hosts = ["maya", "nuke"] - families = ["renderlayer", "render"] + hosts = ["maya", "nuke", "aftereffects", "harmony", "fusion"] + families = ["renderlayer", "render", "render.farm"] # cache responses = {} def process(self, instance): - # get default deadline webservice url from deadline module - deadline_url = instance.context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - deadline_url = instance.data.get("deadlineUrl") - self.log.debug( - "We have deadline URL on instance {}".format(deadline_url) - ) + if not instance.data.get("farm"): + self.log.debug("Should not be processed on farm, skipping.") + return + + deadline_url = instance.data["deadline"]["url"] assert deadline_url, "Requires Deadline Webservice URL" + kwargs = {} + if instance.data["deadline"]["require_authentication"]: + auth = instance.data["deadline"]["auth"] + kwargs["auth"] = auth + + if not auth[0]: + raise PublishXmlValidationError( + self, + "Deadline requires authentication. " + "At least username is required to be set in " + "Site Settings.") + if deadline_url not in self.responses: - self.responses[deadline_url] = requests_get(deadline_url) + self.responses[deadline_url] = requests_get(deadline_url, **kwargs) response = self.responses[deadline_url] + if response.status_code == 401: + raise PublishXmlValidationError( + self, + "Deadline requires authentication. " + "Provided credentials are not working. " + "Please change them in Site Settings") assert response.ok, "Response must be ok" assert response.text.startswith("Deadline Web Service "), ( "Web service did not respond with 'Deadline Web Service'" diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py index 2feb044cf1..5094b3deaf 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_deadline_pools.py @@ -37,8 +37,9 @@ class ValidateDeadlinePools(OptionalPyblishPluginMixin, self.log.debug("Skipping local instance.") return - deadline_url = self.get_deadline_url(instance) - pools = self.get_pools(deadline_url) + deadline_url = instance.data["deadline"]["url"] + pools = self.get_pools(deadline_url, + instance.data["deadline"].get("auth")) invalid_pools = {} primary_pool = instance.data.get("primaryPool") @@ -61,22 +62,18 @@ class ValidateDeadlinePools(OptionalPyblishPluginMixin, formatting_data={"pools_str": ", ".join(pools)} ) - def get_deadline_url(self, instance): - # get default deadline webservice url from deadline module - deadline_url = instance.context.data["defaultDeadline"] - if instance.data.get("deadlineUrl"): - # if custom one is set in instance, use that - deadline_url = instance.data.get("deadlineUrl") - return deadline_url - - def get_pools(self, deadline_url): + def get_pools(self, deadline_url, auth): if deadline_url not in self.pools_per_url: self.log.debug( "Querying available pools for Deadline url: {}".format( deadline_url) ) pools = DeadlineModule.get_deadline_pools(deadline_url, + auth=auth, log=self.log) + # some DL return "none" as a pool name + if not "none" in pools: + pools.append("none") self.log.info("Available pools: {}".format(pools)) self.pools_per_url[deadline_url] = pools diff --git a/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py b/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py index 6263526d5c..83e867408c 100644 --- a/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py +++ b/client/ayon_core/modules/deadline/plugins/publish/validate_expected_and_rendered_files.py @@ -199,16 +199,16 @@ class ValidateExpectedFiles(pyblish.api.InstancePlugin): (dict): Job info from Deadline """ - # get default deadline webservice url from deadline module - deadline_url = instance.context.data["defaultDeadline"] - # if custom one is set in instance, use that - if instance.data.get("deadlineUrl"): - deadline_url = instance.data.get("deadlineUrl") + deadline_url = instance.data["deadline"]["url"] assert deadline_url, "Requires Deadline Webservice URL" url = "{}/api/jobs?JobID={}".format(deadline_url, job_id) try: - response = requests_get(url) + kwargs = {} + auth = instance.data["deadline"]["auth"] + if auth: + kwargs["auth"] = auth + response = requests_get(url, **kwargs) except requests.exceptions.ConnectionError: self.log.error("Deadline is not accessible at " "{}".format(deadline_url)) diff --git a/client/ayon_core/modules/deadline/version.py b/client/ayon_core/modules/deadline/version.py new file mode 100644 index 0000000000..569b1212f7 --- /dev/null +++ b/client/ayon_core/modules/deadline/version.py @@ -0,0 +1 @@ +__version__ = "0.1.10" diff --git a/client/ayon_core/pipeline/publish/abstract_collect_render.py b/client/ayon_core/pipeline/publish/abstract_collect_render.py index c50dc16380..17cab876b6 100644 --- a/client/ayon_core/pipeline/publish/abstract_collect_render.py +++ b/client/ayon_core/pipeline/publish/abstract_collect_render.py @@ -80,6 +80,7 @@ class RenderInstance(object): anatomyData = attr.ib(default=None) outputDir = attr.ib(default=None) context = attr.ib(default=None) + deadline = attr.ib(default=None) # The source instance the data of this render instance should merge into source_instance = attr.ib(default=None, type=pyblish.api.Instance) @@ -215,13 +216,12 @@ class AbstractCollectRender(pyblish.api.ContextPlugin): # add additional data data = self.add_additional_data(data) - render_instance_dict = attr.asdict(render_instance) - # Merge into source instance if provided, otherwise create instance - instance = render_instance_dict.pop("source_instance", None) + instance = render_instance.source_instance if instance is None: instance = context.create_instance(render_instance.name) + render_instance_dict = attr.asdict(render_instance) instance.data.update(render_instance_dict) instance.data.update(data) diff --git a/server_addon/deadline/server/__init__.py b/server_addon/deadline/server/__init__.py index e7dcb7d347..8d2dc152cd 100644 --- a/server_addon/deadline/server/__init__.py +++ b/server_addon/deadline/server/__init__.py @@ -2,11 +2,13 @@ from typing import Type from ayon_server.addons import BaseServerAddon -from .settings import DeadlineSettings, DEFAULT_VALUES +from .settings import DeadlineSettings, DEFAULT_VALUES, DeadlineSiteSettings class Deadline(BaseServerAddon): settings_model: Type[DeadlineSettings] = DeadlineSettings + site_settings_model: Type[DeadlineSiteSettings] = DeadlineSiteSettings + async def get_default_settings(self): settings_model_cls = self.get_settings_model() diff --git a/server_addon/deadline/server/settings/__init__.py b/server_addon/deadline/server/settings/__init__.py index 0307862afa..d25c0fb330 100644 --- a/server_addon/deadline/server/settings/__init__.py +++ b/server_addon/deadline/server/settings/__init__.py @@ -2,9 +2,11 @@ from .main import ( DeadlineSettings, DEFAULT_VALUES, ) +from .site_settings import DeadlineSiteSettings __all__ = ( "DeadlineSettings", + "DeadlineSiteSettings", "DEFAULT_VALUES", ) diff --git a/server_addon/deadline/server/settings/main.py b/server_addon/deadline/server/settings/main.py index 21a314cd2f..5d42b9b1ef 100644 --- a/server_addon/deadline/server/settings/main.py +++ b/server_addon/deadline/server/settings/main.py @@ -15,12 +15,6 @@ from .publish_plugins import ( ) -class ServerListSubmodel(BaseSettingsModel): - _layout = "compact" - name: str = SettingsField(title="Name") - value: str = SettingsField(title="Value") - - async def defined_deadline_ws_name_enum_resolver( addon: "BaseServerAddon", settings_variant: str = "production", @@ -32,25 +26,40 @@ async def defined_deadline_ws_name_enum_resolver( settings = await addon.get_studio_settings(variant=settings_variant) - ws_urls = [] + ws_server_name = [] for deadline_url_item in settings.deadline_urls: - ws_urls.append(deadline_url_item.name) + ws_server_name.append(deadline_url_item.name) - return ws_urls + return ws_server_name + +class ServerItemSubmodel(BaseSettingsModel): + """Connection info about configured DL servers.""" + _layout = "compact" + name: str = SettingsField(title="Name") + value: str = SettingsField(title="Url") + require_authentication: bool = SettingsField( + False, + title="Require authentication") + ssl: bool = SettingsField(False, + title="SSL") class DeadlineSettings(BaseSettingsModel): - deadline_urls: list[ServerListSubmodel] = SettingsField( + # configured DL servers + deadline_urls: list[ServerItemSubmodel] = SettingsField( default_factory=list, - title="System Deadline Webservice URLs", + title="System Deadline Webservice Info", scope=["studio"], ) + + # name(key) of selected server for project deadline_server: str = SettingsField( - title="Project deadline server", + title="Project Deadline server name", section="---", scope=["project"], enum_resolver=defined_deadline_ws_name_enum_resolver ) + publish: PublishPluginsModel = SettingsField( default_factory=PublishPluginsModel, title="Publish Plugins", @@ -62,11 +71,14 @@ class DeadlineSettings(BaseSettingsModel): return value + DEFAULT_VALUES = { "deadline_urls": [ { "name": "default", - "value": "http://127.0.0.1:8082" + "value": "http://127.0.0.1:8082", + "require_authentication": False, + "ssl": False } ], "deadline_server": "default", diff --git a/server_addon/deadline/server/settings/site_settings.py b/server_addon/deadline/server/settings/site_settings.py new file mode 100644 index 0000000000..a77a6edc7e --- /dev/null +++ b/server_addon/deadline/server/settings/site_settings.py @@ -0,0 +1,26 @@ +from ayon_server.settings import ( + BaseSettingsModel, + SettingsField, +) +from .main import defined_deadline_ws_name_enum_resolver + + +class CredentialPerServerModel(BaseSettingsModel): + """Provide credentials for configured DL servers""" + _layout = "expanded" + server_name: str = SettingsField("", + title="DL server name", + enum_resolver=defined_deadline_ws_name_enum_resolver) + username: str = SettingsField("", + title="Username") + password: str = SettingsField("", + title="Password") + + +class DeadlineSiteSettings(BaseSettingsModel): + local_settings: list[CredentialPerServerModel] = SettingsField( + default_factory=list, + title="Local setting", + description="Please provide credentials for configured Deadline servers", + ) +