2424
2525import requests
2626from dask .distributed import CancelledError , Client , LocalCluster , as_completed
27- from dask_gateway import Gateway , JupyterHubAuth
27+ from dask_gateway import Gateway
28+ from dask_gateway .auth import BasicAuth , JupyterHubAuth
2829from fastapi import HTTPException
2930from pygeoapi .process .base import BaseProcessor
3031from pygeoapi .process .manager .postgresql import PostgreSQLManager
3637 load_external_auth_config_by_domain ,
3738)
3839from rs_server_common .s3_storage_handler .s3_storage_handler import S3StorageHandler
40+ from rs_server_common .settings import LOCAL_MODE
3941from rs_server_common .utils .logging import Logging
4042from starlette .datastructures import Headers
4143from starlette .requests import Request
@@ -615,7 +617,7 @@ def manage_dask_tasks_results(self, client: Client, catalog_collection: str):
615617 self .log_job_execution (JobStatus .successful , 100 , "Finished" )
616618 self .logger .info ("Tasks monitoring finished" )
617619
618- def dask_cluster_connect (self ) -> Client :
620+ def dask_cluster_connect (self ) -> Client : # pylint: disable=too-many-branches,too-many-statements
619621 """Connects a dask cluster scheduler
620622 Establishes a connection to a Dask cluster, either in a local environment or via a Dask Gateway in
621623 a Kubernetes cluster. This method checks if the cluster is already created (for local mode) or connects
@@ -672,42 +674,62 @@ def dask_cluster_connect(self) -> Client:
672674 # If self.cluster is already initialized, it means the application is running in local mode, and
673675 # the cluster was created when the application started.
674676 if not self .cluster :
675- # in kubernetes cluster mode, we have to connect to the gateway and get the list of the clusters
677+ # Connect to the gateway and get the list of the clusters
676678 try :
677679 # get the name of the cluster
678680 cluster_name = os .environ ["RSPY_DASK_STAGING_CLUSTER_NAME" ]
679- # check the auth type, only jupyterhub type supported for now
680- auth_type = os .environ ["DASK_GATEWAY__AUTH__TYPE" ]
681- # Handle JupyterHub authentication
682- if auth_type == "jupyterhub" :
683- gateway_auth = JupyterHubAuth (api_token = os .environ ["JUPYTERHUB_API_TOKEN" ])
681+
682+ # In local mode, authenticate to the dask cluster with username/password
683+ if LOCAL_MODE :
684+ gateway_auth = BasicAuth (
685+ os .environ ["LOCAL_DASK_USERNAME" ],
686+ os .environ ["LOCAL_DASK_PASSWORD" ],
687+ )
688+
689+ # Cluster mode
684690 else :
685- self .logger .error (f"Unsupported authentication type: { auth_type } " )
686- raise RuntimeError (f"Unsupported authentication type: { auth_type } " )
691+ # check the auth type, only jupyterhub type supported for now
692+ auth_type = os .environ ["DASK_GATEWAY__AUTH__TYPE" ]
693+ # Handle JupyterHub authentication
694+ if auth_type == "jupyterhub" :
695+ gateway_auth = JupyterHubAuth (api_token = os .environ ["JUPYTERHUB_API_TOKEN" ])
696+ else :
697+ self .logger .error (f"Unsupported authentication type: { auth_type } " )
698+ raise RuntimeError (f"Unsupported authentication type: { auth_type } " )
699+
687700 gateway = Gateway (
688701 address = os .environ ["DASK_GATEWAY__ADDRESS" ],
689702 auth = gateway_auth ,
690703 )
691- clusters = gateway .list_clusters ()
692- self .logger .debug (f"The list of clusters: { clusters } " )
693704
694- # Get the identifier of the cluster whose name is equal to the cluster_name variable
705+ # Sort the clusters by newest first
706+ clusters = sorted (gateway .list_clusters (), key = lambda cluster : cluster .start_time , reverse = True )
707+ self .logger .debug (f"Cluster list for gateway { os .environ ['DASK_GATEWAY__ADDRESS' ]!r} : { clusters } " )
708+
709+ # In local mode, get the first cluster from the gateway.
710+ cluster_id = None
711+ if LOCAL_MODE :
712+ if clusters :
713+ cluster_id = clusters [0 ].name
714+
715+ # In cluster mode, get the identifier of the cluster whose name is equal to the cluster_name variable.
695716 # Protection for the case when this cluster does not exit
696- cluster_id = next (
697- (
698- cluster .name
699- for cluster in clusters
700- if isinstance (cluster .options , dict ) and cluster .options .get ("cluster_name" ) == cluster_name
701- ),
702- None ,
703- )
717+ else :
718+ cluster_id = next (
719+ (
720+ cluster .name
721+ for cluster in clusters
722+ if isinstance (cluster .options , dict ) and cluster .options .get ("cluster_name" ) == cluster_name
723+ ),
724+ None ,
725+ )
704726
705727 if not cluster_id :
706- raise IndexError (f"No dask cluster named ' { cluster_name } ' was found." )
728+ raise IndexError (f"Dask cluster with 'cluster_name'= { cluster_name !r } was not found." )
707729
708730 self .cluster = gateway .connect (cluster_id )
709-
710731 self .logger .info (f"Successfully connected to the { cluster_name } dask cluster" )
732+
711733 except KeyError as e :
712734 self .logger .exception (
713735 "Failed to retrieve the required connection details for "
@@ -725,6 +747,22 @@ def dask_cluster_connect(self) -> Client:
725747 # create the client as well
726748 client = Client (self .cluster )
727749
750+ # Forward logging from dask workers to the caller
751+ client .forward_logging ()
752+
753+ def set_dask_env (host_env : dict ):
754+ """Pass environment variables to the dask workers."""
755+ for name in ["S3_ACCESSKEY" , "S3_SECRETKEY" , "S3_ENDPOINT" , "S3_REGION" ]:
756+ os .environ [name ] = host_env [name ]
757+
758+ # Some kind of workaround for boto3 to avoid checksum being added inside
759+ # the file contents uploaded to the s3 bucket e.g. x-amz-checksum-crc32:xxx
760+ # See: https://github.com/boto/boto3/issues/4435
761+ os .environ ["AWS_REQUEST_CHECKSUM_CALCULATION" ] = "when_required"
762+ os .environ ["AWS_RESPONSE_CHECKSUM_VALIDATION" ] = "when_required"
763+
764+ client .run (set_dask_env , os .environ )
765+
728766 # This is a temporary fix for the dask cluster settings which does not create a scheduler by default
729767 # This code should be removed as soon as this is fixed in the kubernetes cluster
730768 try :
0 commit comments