diff --git a/wedpr-builder/db/wedpr_admin_dml.sql b/wedpr-builder/db/wedpr_admin_dml.sql deleted file mode 100644 index 939fdea7..00000000 --- a/wedpr-builder/db/wedpr_admin_dml.sql +++ /dev/null @@ -1,6 +0,0 @@ --- 管理端初始化 -insert into wedpr_user (username, password, status) values('admin', '{bcrypt}$2a$10$XuiuKLg23kxtC/ldvYN0/evt0Y3aoBC9iV29srhIBMMDORzCQiYA.', 0); -insert into wedpr_user_role(username, role_id) values ('admin', '10'); -insert into wedpr_role_permission (role_id, role_name, permission_id) values ('10', 'admin_user', '1'); - -insert into `wedpr_config_table`(`config_key`, `config_value`) values("wedpr_algorithm_templates", '{"version":"1.0","templates":[{"name":"PSI","title":"数据对齐","detail":"","version":"1.0"},{"name":"XGB_TRAINING","title":"SecureLGBM训练","detail":"","version":"1.0"},{"name":"XGB_PREDICTING","title":"SecureLGBM预测","detail":"","version":"1.0"}]}'); diff --git a/wedpr-builder/db/wedpr_dml.sql b/wedpr-builder/db/wedpr_dml.sql index 3a697b3a..438010b5 100644 --- a/wedpr-builder/db/wedpr_dml.sql +++ b/wedpr-builder/db/wedpr_dml.sql @@ -1,10 +1,6 @@ -- the Wizard algorithm template insert into `wedpr_config_table`(`config_key`, `config_value`) values("wedpr_algorithm_templates", '{"version":"1.0","templates":[{"name":"PSI","title":"数据对齐","supportable":true,"enable":true,"participateNumber":"2+","detail":"","version":"1.0"},{"name":"PIR","title":"匿踪查询","supportable":true,"enable":true,"participateNumber":"1","detail":"","version":"1.0"},{"name":"SQL","title":"联表分析","supportable":true,"enable":true,"participateNumber":"1+","detail":"","version":"1.0"},{"name":"MPC","title":"自定义计算","supportable":true,"enable":true,"participateNumber":"1+","detail":"","version":"1.0"},{"name":"XGB_TRAINING","title":"SecureLGBM训练","supportable":true,"enable":true,"participateNumber":"1+","needTagsProvider":true,"detail":"","version":"1.0"},{"name":"XGB_PREDICTING","title":"SecureLGBM预测","supportable":true,"enable":true,"participateNumber":"1+","needTagsProvider":true,"detail":"","version":"1.0"},{"name":"LR_TRAINING","title":"SecureLR建模","supportable":true,"enable":true,"participateNumber":"1+","needTagsProvider":true,"detail":"","version":"1.0"},{"name":"LR_PREDICTING","title":"SecureLR预测","supportable":true,"enable":true,"participateNumber":"1+","needTagsProvider":true,"detail":"","version":"1.0"},{"name":"PREPROCESSING","title":"数据预处理","supportable":true,"detail":"","version":"1.0"},{"name":"FEATURE_ENGINEERING","title":"特征工程","supportable":true,"detail":"","version":"1.0"}]}'); - --- the jupyter related host settings --- insert into `wedpr_config_table`(`config_key`, `config_value`) values("jupyter_entrypoints", '{"hostSettings":[{"entryPoint":"192.168.0.238:14000","maxJupyterCount":5,"jupyterStartPort":14000}]}'); - -- the jupyter related code template -- command to create user insert into `wedpr_config_table`(`config_key`, `config_value`) values("wedpr_create_user", 'useradd -m ${user_name}'); @@ -41,4 +37,7 @@ insert into wedpr_group_detail (group_id, username, status) values('100000000000 insert into wedpr_user (username, password, status) values('admin', '{bcrypt}$2a$10$9ZhDOBp.sRKat4l14ygu/.LscxrMUcDAfeVOEPiYwbcRkoB09gCmi', 0); insert into wedpr_user_role(username, role_id) values ('admin', '1'); insert into wedpr_role_permission (role_id, role_name, permission_id) values ('1', 'admin_user', '1'); -insert into wedpr_role_permission (role_id, role_name, permission_id) values ('2', 'original_user', '2'); \ No newline at end of file +insert into wedpr_role_permission (role_id, role_name, permission_id) values ('2', 'original_user', '2'); + +-- the jupyter related host settings +-- insert into `wedpr_config_table`(`config_key`, `config_value`) values("jupyter_entrypoints", '{"hostSettings":[{"entryPoint":"192.168.0.238:14000","maxJupyterCount":5,"jupyterStartPort":14000}]}'); \ No newline at end of file diff --git a/wedpr-builder/wedpr_builder/common/constant.py b/wedpr-builder/wedpr_builder/common/constant.py index 54262fcd..a6cb2528 100644 --- a/wedpr-builder/wedpr_builder/common/constant.py +++ b/wedpr-builder/wedpr_builder/common/constant.py @@ -91,6 +91,15 @@ class ConfigInfo: nginx_tpl_path = get_abs_path("nginx/") nginx_config_file_list = ["nginx.conf"] + # the init db file path + pwd_path = os.getcwd() + db_dir_name = "db" + db_file_path = os.path.join(pwd_path, db_dir_name) + dml_sql_file = "wedpr_dml.sql" + # the site init conf + init_tpl_path = get_abs_path("init/") + init_file_path_list = ["site_init.sh"] + @staticmethod def get_docker_path(file_path: str): return os.path.join(ConfigInfo.default_docker_work_dir, file_path) @@ -113,6 +122,10 @@ class ConfigProperities: SQLALCHEMY_URL = "SQLALCHEMY_URL" MYSQL_USER = "MYSQL_USER" MYSQL_PASSWORD = "MYSQL_PASSWORD" + # the mysql var used for init script + MYSQL_HOST = "MYSQL_HOST" + MYSQL_PORT = "MYSQL_PORT" + MYSQL_DATABASE = "MYSQL_DATABASE" # the blockchain configuration BLOCKCHAIN_GROUP_ID = "BLOCKCHAIN_GROUP_ID" BLOCKCHAIN_PEERS = "BLOCKCHAIN_PEERS" diff --git a/wedpr-builder/wedpr_builder/config/wedpr_deploy_config.py b/wedpr-builder/wedpr_builder/config/wedpr_deploy_config.py index 2dd8195e..5a3c4e7b 100644 --- a/wedpr-builder/wedpr_builder/config/wedpr_deploy_config.py +++ b/wedpr-builder/wedpr_builder/config/wedpr_deploy_config.py @@ -5,6 +5,7 @@ from wedpr_builder.common import utilities from wedpr_builder.common import constant from wedpr_builder.config.wedpr_user_config import UserJWTConfig +import json class ComponentSwitch: @@ -300,9 +301,54 @@ def to_properties(self) -> {}: properties.update({constant.ConfigProperities.MYSQL_USER: self.user}) properties.update( {constant.ConfigProperities.MYSQL_PASSWORD: self.password}) + # mysql host + properties.update({constant.ConfigProperities.MYSQL_HOST: self.host}) + properties.update({constant.ConfigProperities.MYSQL_PORT: self.port}) + properties.update( + {constant.ConfigProperities.MYSQL_DATABASE: self.database}) return properties +class JupyterInfos: + def __init__(self): + self.jupyters = [] + + def to_string(self) -> str: + """ + convert to json string + :return: + """ + dict_result = {} + jupyter_dict_array = [] + for jupyter in self.jupyters: + jupyter_dict_array.append(jupyter.as_dict()) + dict_result.update({"hostSettings": jupyter_dict_array}) + return json.dumps(dict_result) + + +class SingleJupyterInfo: + def __init__( + self, + entry_point: str, + start_port: int, + max_jupyter_count: int = 10): + self.entryPoint = entry_point + self.jupyterStartPort = start_port + self.maxJupyterCount = max_jupyter_count + + def __repr__(self): + return f"entryPoint: {self.entryPoint}, " \ + f"jupyterStartPort: {self.jupyterStartPort}," \ + f" maxJupyterCount: {self.maxJupyterCount}" + + def as_dict(self): + result = {} + result.update({"\"entryPoint\"": f"\"{self.entryPoint}\""}) + result.update({"\"jupyterStartPort\"": f"{self.jupyterStartPort}"}) + result.update({"\"maxJupyterCount\"": f"{self.maxJupyterCount}"}) + return result + + class ServiceConfig: def __init__(self, config, env_config: EnvConfig, gateway_targets: str, @@ -325,10 +371,19 @@ def __init__(self, config, env_config: EnvConfig, self.config, "server_start_port", 0, must_exist, config_section)) self.server_backend_list = [] + self.jupyter_infos = JupyterInfos() + + def to_jupyter_sql(self): + if self.service_type != constant.ServiceInfo.wedpr_jupyter_worker_service: + return "" + jupyter_setting = "'%s'" % self.jupyter_infos.to_string() + utilities.log_info(f"* jupyter_setting: {jupyter_setting}") + sql = 'insert into \`wedpr_config_table\`(\`config_key\`, \`config_valule\`) values(\\\"jupyter_entrypoints\\\", %s);' % jupyter_setting + return sql def __repr__(self): return f"**ServiceConfig: deploy_ip: {self.deploy_ip_list}, " \ - f"agency: {self.agency}" \ + f"agency: {self.agency}, " \ f"server_start_port: {self.server_start_port}," \ f"service_type: {self.service_type} \n**" @@ -343,8 +398,8 @@ def to_nginx_properties(self): def to_properties(self, deploy_ip, node_index: int) -> {}: props = {} - start_port = self.server_start_port + 2 * node_index - self.server_backend_list.append(f"{deploy_ip}:{start_port}") + server_start_port = self.server_start_port + 2 * node_index + self.server_backend_list.append(f"{deploy_ip}:{server_start_port}") # nodeid node_id = f"{self.service_type}-{self.env_config.zone}-node{node_index}" props.update({constant.ConfigProperities.WEDPR_NODE_ID: node_id}) @@ -360,10 +415,10 @@ def to_properties(self, deploy_ip, node_index: int) -> {}: {constant.ConfigProperities.WEDPR_TRANSPORT_HOST_IP: host_ip}) # the server listen port props.update( - {constant.ConfigProperities.WEDPR_SERVER_LISTEN_PORT: start_port}) + {constant.ConfigProperities.WEDPR_SERVER_LISTEN_PORT: server_start_port}) # transport listen_port props.update( - {constant.ConfigProperities.WEDPR_TRANSPORT_LISTEN_PORT: start_port + 1}) + {constant.ConfigProperities.WEDPR_TRANSPORT_LISTEN_PORT: server_start_port + 1}) # set the image desc for docker mode image_desc = self.env_config.get_image_desc_by_service_name( self.service_type) @@ -371,15 +426,20 @@ def to_properties(self, deploy_ip, node_index: int) -> {}: props.update( {constant.ConfigProperities.WEDPR_IMAGE_DESC: image_desc}) # set the exposed port - exposed_port_list = f"-p {start_port}:{start_port} -p {start_port + 1}:{start_port + 1}" + exposed_port_list = f"-p {server_start_port}:{server_start_port} -p {server_start_port + 1}:{server_start_port + 1}" # default expose 20 ports for jupyter use # reserver 100 ports for jupyter use - jupyter_start_port = start_port + 100 + jupyter_start_port = server_start_port + 100 default_jupyter_max_num = 20 if self.service_type == constant.ServiceInfo.wedpr_jupyter_worker_service: - start_port = jupyter_start_port + default_jupyter_max_num * node_index - end_port = start_port + default_jupyter_max_num - exposed_port_list = f"{exposed_port_list} -p {start_port}-{end_port}:{start_port}-{end_port}" + begin_port = jupyter_start_port + default_jupyter_max_num * node_index + end_port = begin_port + default_jupyter_max_num + exposed_port_list = f"{exposed_port_list} -p {begin_port}-{end_port}:{begin_port}-{end_port}" + entry_point = f"http://{deploy_ip}:{server_start_port}" + # add the SingleJupyterInfo + self.jupyter_infos.jupyters.append(SingleJupyterInfo( + entry_point=entry_point, + start_port=begin_port,)) props.update( {constant.ConfigProperities.WEDPR_DOCKER_EXPORSE_PORT_LIST: exposed_port_list}) # set the docker name @@ -906,6 +966,36 @@ def get_pir_properties(self, deploy_ip: str, node_index: int): constant.ConfigInfo.wedpr_pir_docker_dir)) return props + def __update_dml__(self, sql, dml_file_path, use_double_quote=False): + command = "echo '%s' >> %s" % (sql, dml_file_path) + if use_double_quote: + command = "echo \"%s\" >> %s" % (sql, dml_file_path) + (ret, output) = utilities.execute_command_and_getoutput(command) + if ret is False: + raise Exception( + f"update dml file {dml_file_path} failed for {output}") + + def update_dml(self, agency_list, init_path): + """ + update the dml information + :param agency_list: + :param init_path: + :return: + """ + sql = "\n" + # set the agency information + for agency in agency_list: + sql = f'{sql}insert into `wedpr_agency_table`(`name`, `cnName`, `desc`, `meta`) ' \ + f'values("{agency}", "{agency}", "{agency}", "{agency}");\n' + dml_file_path = os.path.join( + init_path, constant.ConfigInfo.db_dir_name, constant.ConfigInfo.dml_sql_file) + self.__update_dml__(sql, dml_file_path) + # set the jupyter setting + if self.jupyter_worker_config is None: + raise Exception("Must set the jupyter worker configuration!") + sql = self.jupyter_worker_config.to_jupyter_sql() + self.__update_dml__(sql, dml_file_path, True) + def __repr__(self): return f"agency: {self.agency_name}, gateway_config: {self.gateway_config}, " \ f"node_config: {self.node_list}, hdfs_config: {self.hdfs_storage_config}" diff --git a/wedpr-builder/wedpr_builder/controller/commandline_helper.py b/wedpr-builder/wedpr_builder/controller/commandline_helper.py index 48d6d0ea..2133d066 100644 --- a/wedpr-builder/wedpr_builder/controller/commandline_helper.py +++ b/wedpr-builder/wedpr_builder/controller/commandline_helper.py @@ -24,7 +24,6 @@ def parse_command(): "* generate wedpr-site config:\t python3 build_wedpr.py -t wedpr-site\n " \ "* generate wedpr-pir config:\t python3 build_wedpr.py -t wedpr-pir\n" \ "* generate wedpr-model service config:\t python3 build_wedpr.py -t wedpr-model\n " \ - "* generate wedpr-jupyter-worker config:\t python3 build_wedpr.py -t wedpr-jupyter-worker\n " \ "* generate gateway config:\t python3 build_wedpr.py -o genconfig -c config.toml -t wedpr-gateway -d wedpr-generated\n " \ "* generate node config:\t python3 build_wedpr.py -o genconfig -c config.toml -t wedpr-node -d wedpr-generated" parser = argparse.ArgumentParser( @@ -85,8 +84,14 @@ def generate_node_config(args, toml_config): sys.exit(-1) # the site config generator if service_type == constant.ServiceInfo.wedpr_site_service: - component_switch = ComponentSwitch(site_must_exists=True) + # default generate the jupyter config + component_switch = ComponentSwitch( + site_must_exists=True, jupyter_must_exists=True) config = WeDPRDeployConfig(toml_config, component_switch) + jupyter_generator = WedprJupyterWorkerServiceGenerator( + config, args.output) + jupyter_generator.generate_config() + # Note: generate site config after jupyter config generate success site_generator = WedprSiteServiceGenerator(config, args.output) site_generator.generate_config() # the pir service generator @@ -102,6 +107,8 @@ def generate_node_config(args, toml_config): model_service_generator = WedprModelServiceGenerator( config, args.output) model_service_generator.generate_config() + # Note: the jupyter config is generated with the site config + """ # the jupyter worker config generator if service_type == constant.ServiceInfo.wedpr_jupyter_worker_service: component_switch = ComponentSwitch(jupyter_must_exists=True) @@ -109,6 +116,7 @@ def generate_node_config(args, toml_config): jupyter_generator = WedprJupyterWorkerServiceGenerator( config, args.output) jupyter_generator.generate_config() + """ def execute_command(args): diff --git a/wedpr-builder/wedpr_builder/generator/wedpr_service_generator.py b/wedpr-builder/wedpr_builder/generator/wedpr_service_generator.py index f9746ec3..b3c4f826 100644 --- a/wedpr-builder/wedpr_builder/generator/wedpr_service_generator.py +++ b/wedpr-builder/wedpr_builder/generator/wedpr_service_generator.py @@ -30,17 +30,29 @@ def get_service_config(self, agency_config: AgencyConfig) -> ServiceConfig: pass def generate_config(self): + agency_list = self.config.agency_list.keys() for agency in self.config.agency_list.keys(): agency_config = self.config.agency_list.get(agency) service_config = self.get_service_config(agency_config) - self.__generate_service_config__(agency_config, service_config) + self.__generate_service_config__( + agency_config, agency_list, service_config) @abstractmethod def generate_nginx_config(self, node_path: str, server_config: ServiceConfig): pass + @abstractmethod + def generate_init_scripts(self, init_dir, agency_list, agency_config: AgencyConfig): + """ + generate the init scripts + :param agency_config: + :return: + """ + pass + def __generate_service_config__( self, agency_config: AgencyConfig, + agency_list, service_config: ServiceConfig): utilities.print_badge(f"* generate {service_config.service_type} config, " f"agency: {agency_config.agency_name}, deploy_dir: " @@ -54,15 +66,19 @@ def __generate_service_config__( if len(ip_array) > 1: count = int(ip_array[1]) for i in range(count): - node_path = self.__generate_single_site_config__( + node_path = self.__generate_single_node_config__( agency_config=agency_config, service_config=service_config, agency_name=service_config.agency, deploy_ip=ip, node_index=i) node_path_list.append(node_path) # generate the ip shell scripts - self.__generate_docker_ip_shell_scripts__(self.__get_deploy_path__( - agency_config.agency_name, ip, None, service_config.service_type)) + output_path = self.__get_deploy_path__( + agency_config.agency_name, ip, None, service_config.service_type) + self.__generate_docker_ip_shell_scripts__(output_path) + # generate the init scripts + self.generate_init_scripts(os.path.join( + output_path, "init"), agency_list, agency_config) for node_path in node_path_list: self.generate_nginx_config(node_path, service_config) utilities.print_badge(f"* generate {service_config.service_type} config success, " @@ -75,7 +91,7 @@ def __generate_docker_ip_shell_scripts__(self, ip_dir): return DockerGenerator.generate_shell_scripts(ip_dir) - def __generate_single_site_config__( + def __generate_single_node_config__( self, agency_config: AgencyConfig, service_config: ServiceConfig, agency_name: str, @@ -113,6 +129,7 @@ def __generate_single_site_config__( # substitute the configuration config_properties = self.get_properties( deploy_ip, agency_config, node_index) + # the docker mode case self.__generate_docker_scripts__( node_path, config_properties, service_config) @@ -221,6 +238,32 @@ def __init__(self, deploy_path: str): super().__init__(config, deploy_path) + # generate the init scripts + def generate_init_scripts(self, init_dir, agency_list, agency_config: AgencyConfig): + utilities.log_info( + f"* Generate init scripts for wedpr-site, init_dir: {init_dir}") + utilities.mkdir(init_dir) + # copy the db scripts + command = f"cp -r {constant.ConfigInfo.db_file_path} {init_dir}" + (ret, output) = utilities.execute_command_and_getoutput(command) + if ret is False: + raise Exception( + f"Copy database init files failed, error: {output}") + # copy the init files + command = f"cp {constant.ConfigInfo.init_tpl_path}/* {init_dir}" + (ret, output) = utilities.execute_command_and_getoutput(command) + if ret is False: + raise Exception(f"Generate init file failed, error: {output}") + # substitute the content + storage_props = agency_config.sql_storage_config.to_properties() + for file in constant.ConfigInfo.init_file_path_list: + file_path = os.path.join(init_dir, file) + utilities.substitute_configurations(storage_props, file_path) + utilities.log_info(f"* Generate init script: {file_path} success") + # update the dml + agency_config.update_dml(agency_list, init_dir) + utilities.log_info("* Generate init scripts for wedpr-site success") + def __generate_shell_scripts__(self, dist_path, dst_path): utilities.log_info( f"* generate shell script, dist_path: {dist_path}, dst_path: {dst_path}") @@ -260,6 +303,9 @@ class WedprModelServiceGenerator(WedprServiceGenerator): def __init__(self, config: WeDPRDeployConfig, deploy_path: str): super().__init__(config, deploy_path) + def generate_init_scripts(self, init_dir, agency_list, agency_config: AgencyConfig): + return + def __copy_binary__(self, dist_path, dst_path): if self.config.env_config.docker_mode is True: utilities.log_info( @@ -312,6 +358,9 @@ def __init__(self, deploy_path: str): super().__init__(config, deploy_path) + def generate_init_scripts(self, init_dir, agency_list, agency_config: AgencyConfig): + return + def __copy_binary__(self, dist_path, dst_path): self.__copy_java_binary__(dist_path, dst_path) @@ -338,6 +387,9 @@ def __init__(self, deploy_path: str): super().__init__(config, deploy_path) + def generate_init_scripts(self, init_dir, agency_list, agency_config: AgencyConfig): + return + def __generate_shell_scripts__(self, dist_path, dst_path): return diff --git a/wedpr-builder/wedpr_builder/tpl/init/site_init.sh b/wedpr-builder/wedpr_builder/tpl/init/site_init.sh new file mode 100644 index 00000000..87d2d455 --- /dev/null +++ b/wedpr-builder/wedpr_builder/tpl/init/site_init.sh @@ -0,0 +1,17 @@ +#!/bin/bash +SHELL_FOLDER=$(cd $(dirname $0);pwd) + +LOG_INFO() { + content=${1} + echo -e "\033[32m[INFO] ${content}\033[0m" +} + +cd ${SHELL_FOLDER} + +db_init_script="db/scripts/init/db_init.sh" + +LOG_INFO "* Begin to init database: ${MYSQL_DATABASE}, MYSQL_HOST: ${MYSQL_HOST}, PORT: ${MYSQL_PORT}, USER: ${MYSQL_USER}" + +bash ${db_init_script} ${MYSQL_HOST} ${MYSQL_PORT} ${MYSQL_USER} ${MYSQL_PASSWORD} ${MYSQL_DATABASE} + +LOG_INFO "* Init database success: ${MYSQL_DATABASE}, MYSQL_HOST: ${MYSQL_HOST}, PORT: ${MYSQL_PORT}, USER: ${MYSQL_USER}" diff --git a/wedpr-builder/wedpr_builder/tpl/site/conf/wedpr.properties b/wedpr-builder/wedpr_builder/tpl/site/conf/wedpr.properties index ca685ea3..4df1ce2c 100644 --- a/wedpr-builder/wedpr_builder/tpl/site/conf/wedpr.properties +++ b/wedpr-builder/wedpr_builder/tpl/site/conf/wedpr.properties @@ -86,7 +86,7 @@ wedpr.executor.mpc.request.timeout.ms=60000 wedpr.executor.mpc.max.total.connection=5 # the jupyter config -wedpr.jupyter.max_count_per_host=8 +wedpr.jupyter.max_count_per_host=10 wedpr.jupyter.host_configuration_key=jupyter_entrypoints # the transport config diff --git a/wedpr-components/env-integration/jupyter/src/main/java/com/webank/wedpr/components/integration/jupyter/core/JupyterConfig.java b/wedpr-components/env-integration/jupyter/src/main/java/com/webank/wedpr/components/integration/jupyter/core/JupyterConfig.java index 3404b903..eece6952 100644 --- a/wedpr-components/env-integration/jupyter/src/main/java/com/webank/wedpr/components/integration/jupyter/core/JupyterConfig.java +++ b/wedpr-components/env-integration/jupyter/src/main/java/com/webank/wedpr/components/integration/jupyter/core/JupyterConfig.java @@ -22,7 +22,7 @@ public class JupyterConfig { private static Integer MAX_JUPYTER_PER_HOST = - WeDPRConfig.apply("wedpr.jupyter.max_count_per_host", 3); + WeDPRConfig.apply("wedpr.jupyter.max_count_per_host", 10); private static String JUPYTER_HOST_CONFIGURATION_KEY = WeDPRConfig.apply("wedpr.jupyter.host_configuration_key", "jupyter_entrypoints"); private static String JUPYTER_ENTRYPOINT_SPLITTER = ";"; diff --git a/wedpr-site/conf/wedpr.properties b/wedpr-site/conf/wedpr.properties index 9b92f1b5..4d781519 100644 --- a/wedpr-site/conf/wedpr.properties +++ b/wedpr-site/conf/wedpr.properties @@ -84,7 +84,7 @@ wedpr.executor.mpc.request.timeout.ms=60000 wedpr.executor.mpc.max.total.connection=5 # the jupyter config -wedpr.jupyter.max_count_per_host=8 +wedpr.jupyter.max_count_per_host=10 wedpr.jupyter.host_configuration_key=jupyter_entrypoints # the transport config