diff --git a/bin/start-server.sh b/bin/start-server.sh new file mode 100755 index 0000000000..3a140df204 --- /dev/null +++ b/bin/start-server.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +#set -x + +export CHUNJUN_HOME="$(cd "`dirname "$0"`"/..; pwd)" +echo "CHUNJUN_HOME:"$CHUNJUN_HOME >&2 + +cd $CHUNJUN_HOME +HO_HEAP_SIZE="${HO_HEAP_SIZE:=1024m}" + +JAVA_OPTS="$JAVA_OPTS -Xmx${HO_HEAP_SIZE}" +#JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=10006" + +JAVA_OPTS="$JAVA_OPTS -Xms${HO_HEAP_SIZE}" + +JAVA_OPTS="$JAVA_OPTS -server" + +JAVA_OPTS="$JAVA_OPTS -Xloggc:../logs/node.gc" + +JAVA_OPTS="$JAVA_OPTS -XX:HeapDumpPath=../logs/heapdump.hprof" + +JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+HeapDumpOnOutOfMemoryError -XX:+DisableExplicitGC -Dfile.encoding=UTF-8 -Djna.nosys=true -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps" + +#JAVA_OPTS="$JAVA_OPTS -Djava.io.tmpdir=./tmpSave" +CHUNJUN_NICE=19 +CHUNJUN_LOG_DIR=$CHUNJUN_HOME/logs +pidfile=$CHUNJUN_HOME/run/chunjun.pid + +if [ ! -d "$CHUNJUN_LOG_DIR" ]; then + mkdir $CHUNJUN_LOG_DIR +fi + +if [ ! -e "$pidfile" ]; then + mkdir $CHUNJUN_HOME/run +fi + + + +# Find the java binary +if [ -n "${JAVA_HOME}" ]; then + JAVA_RUN="${JAVA_HOME}/bin/java" +else + if [ `command -v java` ]; then + JAVA_RUN="java" + else + echo "JAVA_HOME is not set" >&2 + exit 1 + fi +fi + +if [ -n "$HADOOP_USER_NAME" ]; then + echo "HADOOP_USER_NAME is "$HADOOP_USER_NAME +else + HADOOP_USER_NAME=yarn + echo "set HADOOP_USER_NAME as yarn" + export HADOOP_USER_NAME +fi + +JAR_DIR=$CHUNJUN_HOME/server/*:$CHUNJUN_HOME/*.jar:$CHUNJUN_HOME/conf/* +echo $JAR_DIR +CLASS_NAME=com.dtstack.chunjun.server.ServerLauncher + +start(){ + echo "ChunJun server starting ..." + nice -n ${CHUNJUN_NICE} $JAVA_RUN $JAVA_OPTS -cp $JAR_DIR $CLASS_NAME $@ 1> "${CHUNJUN_LOG_DIR}/chunjun.stdout" 2>&1 & + echo $! > $pidfile + ret=$? + return 0 +} + +status() { + if [ -f "$pidfile" ] ; then + pid=`cat "$pidfile"` + if kill -0 $pid > /dev/null 2> /dev/null ; then + # process by this pid is running. + # It may not be our pid, but that's what you get with just pidfiles. + # TODO(sissel): Check if this process seems to be the same as the one we + # expect. It'd be nice to use flock here, but flock uses fork, not exec, + # so it makes it quite awkward to use in this case. + return 0 + else + return 2 # program is dead but pid file exists + fi + else + return 3 + fi +} + + +stop() { + echo -n "Stoping chunjun server " + # Try a few times to kill TERM the program + if status ; then + pid=`cat "$pidfile"` + echo "Killing chunjun (pid $pid) with SIGTERM" + kill -TERM $pid + # Wait for it to exit. + for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30; do + echo "Waiting chunjun (pid $pid) to die..." + status || break + sleep 1 + done + if status ; then + if [ "$KILL_ON_STOP_TIMEOUT" == "1" ] ; then + echo "Timeout reached. Killing chunjun (pid $pid) with SIGKILL. This may result in data loss." + kill -KILL $pid + echo "chunjun killed with SIGKILL." + else + echo "chunjun stop failed; still running." + return 1 # stop timed out and not forced + fi + else + echo -n "chunjun stopped " + fi + fi + ret=$? + [ $ret -eq 0 ] ; echo "stop success" + +} + +case "$1" in + start) + echo "start" + status + code=$? + if [ $code -eq 0 ]; then + echo "chunjun server is already running" + else + start + code=$? + fi + exit $code + ;; + status) + status + code=$? + if [ $code -eq 0 ];then + echo "chunjun is already running" + else + echo "chunjun is not running" + fi + exit $code + ;; + stop) + stop + ;; + *) + echo "!!! only support 'start' 'status' 'stop' operator." +esac +exit $? diff --git a/chunjun-restore/chunjun-restore-mysql/pom.xml b/chunjun-restore/chunjun-restore-mysql/pom.xml index d4053eb199..d9910eb132 100644 --- a/chunjun-restore/chunjun-restore-mysql/pom.xml +++ b/chunjun-restore/chunjun-restore-mysql/pom.xml @@ -102,6 +102,13 @@ + + + + com.google.common + shade.core.com.google.common + + diff --git a/chunjun-server/pom.xml b/chunjun-server/pom.xml new file mode 100644 index 0000000000..a861ae137f --- /dev/null +++ b/chunjun-server/pom.xml @@ -0,0 +1,314 @@ + + + + chunjun + com.dtstack.chunjun + ${revision} + + 4.0.0 + + chunjun-server + + + 8 + 8 + 4.6.7 + + + + + javax.servlet + javax.servlet-api + 4.0.1 + + + + com.dtstack.chunjun + chunjun-core + ${project.version} + + + javax.servlet + javax.servlet-api + + + + + + org.apache.hadoop + hadoop-common + ${hadoop2.version} + + + commons-cli + commons-cli + + + guava + com.google.guava + + + slf4j-api + org.slf4j + + + log4j + log4j + + + slf4j-log4j12 + org.slf4j + + + commons-logging + commons-logging + + + javax.servlet + javax.servlet-api + + + + + + org.apache.hadoop + hadoop-yarn-common + ${hadoop2.version} + + + commons-cli + commons-cli + + + guava + com.google.guava + + + slf4j-api + org.slf4j + + + log4j + log4j + + + commons-logging + commons-logging + + + javax.servlet + javax.servlet-api + + + + + + org.apache.hadoop + hadoop-yarn-client + ${hadoop2.version} + + + commons-cli + commons-cli + + + log4j + log4j + + + javax.servlet + javax.servlet-api + + + + + + org.apache.hadoop + hadoop-hdfs-client + 2.8.5 + + + javax.servlet + javax.servlet-api + + + + + + org.apache.hadoop + hadoop-yarn-api + ${hadoop2.version} + + + commons-logging + commons-logging + + + javax.servlet + javax.servlet-api + + + + + + org.apache.flink + flink-clients + ${flink.version} + + + javax.servlet + javax.servlet-api + + + + + + org.apache.flink + flink-yarn + ${flink.version} + + + javax.servlet + javax.servlet-api + + + + + + org.apache.flink + flink-table-planner-loader + ${flink.version} + + + javax.servlet + javax.servlet-api + + + + + + io.javalin + javalin + ${javalin.version} + + + javax.servlet + javax.servlet-api + + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + + + package + + shade + + + + + + + ch.qos.logback:* + ch.qos.reload4j:* + commons-logging:* + + + + false + + + + + + + + reference.conf + + + + core-default.xml + + + + core-site.xml + + + + yarn-default.xml + + + + mapred-default.xml + + + + mapred-site.xml + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + copy-resources + + package + + run + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/client/IClient.java b/chunjun-server/src/main/java/com/dtstack/chunjun/client/IClient.java new file mode 100644 index 0000000000..2f8328d935 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/client/IClient.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.client; + +import com.dtstack.chunjun.entry.JobDescriptor; + +import java.io.IOException; + +/** + * 定义操作远程的接口 + * + * @author xuchao + * @date 2023-05-22 + */ +public interface IClient { + + /** 关联远程信息,获取 */ + void open(); + + /** 关闭连接 */ + void close() throws IOException; + + /** + * 获取任务日志 + * + * @return + */ + String getJobLog(String jobId); + + /** + * 获取任务状态 + * + * @return + */ + String getJobStatus(String jobId) throws Exception; + + /** + * 获取任务统计信息 + * + * @return + */ + String getJobStatics(String jobId); + + /** 提交任务 */ + String submitJob(JobDescriptor jobDescriptor) throws Exception; + + /** 取消任务 */ + void cancelJob(String jobId); +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/client/YarnSessionClient.java b/chunjun-server/src/main/java/com/dtstack/chunjun/client/YarnSessionClient.java new file mode 100644 index 0000000000..842c5534f7 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/client/YarnSessionClient.java @@ -0,0 +1,271 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.client; + +import com.dtstack.chunjun.config.SessionConfig; +import com.dtstack.chunjun.config.YarnAppConfig; +import com.dtstack.chunjun.entry.JobConverter; +import com.dtstack.chunjun.entry.JobDescriptor; +import com.dtstack.chunjun.server.util.JobGraphBuilder; + +import org.apache.flink.api.common.JobID; +import org.apache.flink.api.common.JobStatus; +import org.apache.flink.client.program.ClusterClient; +import org.apache.flink.client.program.ClusterClientProvider; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.runtime.jobgraph.JobGraph; +import org.apache.flink.util.Preconditions; +import org.apache.flink.yarn.CJYarnClusterClientFactory; +import org.apache.flink.yarn.YarnClusterDescriptor; + +import org.apache.commons.lang.StringUtils; +import org.apache.hadoop.yarn.api.records.ApplicationId; +import org.apache.hadoop.yarn.api.records.ApplicationReport; +import org.apache.hadoop.yarn.api.records.YarnApplicationState; +import org.apache.hadoop.yarn.client.api.YarnClient; +import org.apache.hadoop.yarn.conf.YarnConfiguration; +import org.apache.hadoop.yarn.exceptions.YarnException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * 基于yarn 构建client 支持yarn 客户端对flink 任务相关对操作 + * + * @author xuchao + * @date 2023-05-22 + */ +public class YarnSessionClient implements IClient { + + private static final Logger LOG = LoggerFactory.getLogger(YarnSessionClient.class); + + private ClusterClient client; + + private YarnClient yarnClient; + + private ApplicationId applicationId; + + /** flink-defined configuration */ + protected SessionConfig sessionConfig; + + private JobGraphBuilder jobGraphBuilder; + + public YarnSessionClient(SessionConfig sessionConfig) { + this.sessionConfig = sessionConfig; + } + + @Override + public void open() { + yarnClient = initYarnClient(); + client = initYarnClusterClient(); + jobGraphBuilder = new JobGraphBuilder(sessionConfig); + } + + @Override + public void close() throws IOException { + if (client != null) { + client.close(); + client = null; + LOG.info("close flink yarn session client"); + } + + if (applicationId != null) { + applicationId = null; + } + + if (yarnClient != null) { + yarnClient.close(); + yarnClient = null; + LOG.info("close yarn session client"); + } + } + + @Override + public String getJobLog(String jobId) { + // 找不到jobId 需要抛出异常 + return null; + } + + /** + * flink session 场景下通过restapi 直接获取任务日志 + * + * @param jobId + * @return + */ + @Override + public String getJobStatus(String jobId) throws Exception { + Preconditions.checkState(!StringUtils.isEmpty(jobId), "jobId can't be empty!"); + CompletableFuture jobStatusCompletableFuture = + client.getJobStatus(JobID.fromHexString(jobId)); + JobStatus jobStatus = jobStatusCompletableFuture.get(20, TimeUnit.SECONDS); + return jobStatus.toString(); + } + + @Override + public String getJobStatics(String jobId) { + // TODO 获取数据同步的同步指标 + // 同步数量 + return null; + } + + @Override + public String submitJob(JobDescriptor jobDescriptor) throws Exception { + + // TODO 提交部分需要控制classLoader 的新建,添加cache ,避免生成大量的class 导致metaspace oom. + JobGraph jobGraph = + jobGraphBuilder.buildJobGraph(JobConverter.convertJobToArgs(jobDescriptor)); + CompletableFuture jobIDCompletableFuture = client.submitJob(jobGraph); + JobID jobID = jobIDCompletableFuture.get(100, TimeUnit.SECONDS); + return jobID.toString(); + } + + @Override + public void cancelJob(String jobId) {} + + public ClusterClient initYarnClusterClient() { + Configuration newConf = new Configuration(sessionConfig.getFlinkConfig()); + applicationId = acquiredAppId(); + if (applicationId == null) { + // throw new EnginePluginsBaseException("No flink session found on yarn cluster."); + LOG.warn("No flink session found on yarn cluster, acquireAppId from yarn is null."); + return null; + } + // check is ha enabled + + YarnClusterDescriptor clusterDescriptor = + getClusterDescriptor( + newConf, sessionConfig.getHadoopConfig().getYarnConfiguration()); + ClusterClient clusterClient; + try { + ClusterClientProvider clusterClientProvider = + clusterDescriptor.retrieve(applicationId); + clusterClient = clusterClientProvider.getClusterClient(); + } catch (Exception e) { + LOG.error("FlinkSession appId {}, but couldn't retrieve Yarn cluster.", applicationId); + throw new RuntimeException(e); + } + + LOG.warn("---init flink client with yarn session success----"); + + return clusterClient; + } + + public YarnClusterDescriptor getClusterDescriptor( + Configuration configuration, YarnConfiguration yarnConfiguration) { + CJYarnClusterClientFactory yarnClusterClientFactory = new CJYarnClusterClientFactory(); + + return yarnClusterClientFactory.createClusterDescriptor(configuration, yarnConfiguration); + } + + public ApplicationId acquiredAppId() { + + // 根据名称和运行状态获取到指定的applicationId + Set set = new HashSet<>(); + set.add("Apache Flink"); + EnumSet enumSet = EnumSet.noneOf(YarnApplicationState.class); + enumSet.add(YarnApplicationState.RUNNING); + enumSet.add(YarnApplicationState.ACCEPTED); + + try { + List reportList = yarnClient.getApplications(set, enumSet); + for (ApplicationReport report : reportList) { + YarnApplicationState yarnApplicationState = report.getYarnApplicationState(); + // 注意queue 名称有一些hadoop 分发版本返回的是后缀 + boolean checkQueue = + StringUtils.equals( + report.getQueue(), sessionConfig.getAppConfig().getQueue()) + || StringUtils.startsWith( + sessionConfig.getAppConfig().getQueue(), + "." + report.getQueue()); + boolean checkState = yarnApplicationState.equals(YarnApplicationState.RUNNING); + boolean checkName = + report.getName().equals(sessionConfig.getAppConfig().getApplicationName()); + + if (!checkState || !checkQueue || !checkName) { + continue; + } + + return report.getApplicationId(); + } + + // 当前yarn 上没有启动session 的情况 + LOG.info("there are no session on yarn!"); + return null; + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private YarnClient initYarnClient() { + YarnClient yarnClient = YarnClient.createYarnClient(); + yarnClient.init(sessionConfig.getHadoopConfig().getYarnConfiguration()); + yarnClient.start(); + + return yarnClient; + } + + public ClusterClient getClient() { + return client; + } + + public void setYarnClient(YarnClient yarnClient) { + this.yarnClient = yarnClient; + } + + public YarnClient getYarnClient() { + return yarnClient; + } + + public YarnApplicationState getCurrentSessionStatus() throws IOException, YarnException { + if (yarnClient == null || applicationId == null) { + throw new RuntimeException("yarnClient or applicationId is null."); + } + + return yarnClient.getApplicationReport(applicationId).getYarnApplicationState(); + } + + public static void main(String[] args) throws IOException { + YarnAppConfig yarnAppConfig = new YarnAppConfig(); + yarnAppConfig.setApplicationName("dt_xc"); + yarnAppConfig.setQueue("default"); + + String flinkConfDir = "/Users/xuchao/conf/flinkconf/dev_conf_local/"; + String hadoopConfDir = "/Users/xuchao/conf/hadoopconf/dev_hadoop_flink01"; + String flinkLibDir = "/Users/xuchao/MyPrograms/Flink/flink-1.12.7/lib"; + String chunJunLibDir = "/Users/xuchao/IdeaProjects/chunjun/chunjun-dist"; + + SessionConfig sessionConfig = + new SessionConfig(flinkConfDir, hadoopConfDir, flinkLibDir, chunJunLibDir); + sessionConfig.setAppConfig(yarnAppConfig); + sessionConfig.loadFlinkConfiguration(); + sessionConfig.loadHadoopConfiguration(); + + YarnSessionClient yarnSessionClient = new YarnSessionClient(sessionConfig); + yarnSessionClient.open(); + System.out.println("-------"); + yarnSessionClient.close(); + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/config/ChunJunConfig.java b/chunjun-server/src/main/java/com/dtstack/chunjun/config/ChunJunConfig.java new file mode 100644 index 0000000000..2469fdd5a4 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/config/ChunJunConfig.java @@ -0,0 +1,243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.config; + +import com.dtstack.chunjun.options.ServerOptions; + +import java.io.File; +import java.util.Properties; + +/** + * chunjun server 配置 + * + * @author xuchao + * @date 2023-06-13 + */ +public class ChunJunConfig { + + private static final String DEFAULT_APP_NAME = "CHUNJUN-SESSION"; + + private static final String DEFAULT_QUEUE = "default"; + + /** 是否开启session check */ + private boolean enableSessionCheck = true; + + /** 是否开启自动部署session */ + private boolean enableSessionDeploy = true; + + /** 是否开启restful 服务,用于进行任务提交,日志查询,状态等查询 */ + private boolean enableRestful = true; + + private int serverPort = 18081; + + private String flinkConfDir; + + private String flinkLibDir; + + private String flinkPluginLibDir; + + private String chunJunLibDir; + + private String hadoopConfDir; + + private String applicationName = DEFAULT_APP_NAME; + + private String queue = DEFAULT_QUEUE; + + private String chunJunLogConfDir; + + /** 默认的日志级别 */ + private String logLevel = "INFO"; + + public boolean isEnableSessionCheck() { + return enableSessionCheck; + } + + public static ChunJunConfig fromProperties(Properties config) { + String flinkHome = config.getProperty(ServerOptions.FLINK_HOME_PATH_KEY); + String chunjunHome = config.getProperty(ServerOptions.CHUNJUN_HOME_PATH_KEY); + boolean sessionCheck = + Boolean.parseBoolean( + (String) + config.getOrDefault( + ServerOptions.CHUNJUN_SESSION_CHECK_KEY, "true")); + String hadoopConfDir = config.getProperty(ServerOptions.HADOOP_CONF_PATH); + boolean sessionDeploy = + Boolean.valueOf( + (String) + config.getOrDefault( + ServerOptions.CHUNJUN_SESSION_DEPLOY_KEY, "true")); + boolean restful = + Boolean.valueOf( + (String) + config.getOrDefault( + ServerOptions.CHUNJUN_RESTFUL_ENABLE_KEY, "true")); + Integer serverPort = + Integer.valueOf( + (String) + config.getOrDefault( + ServerOptions.CHUNJUN_SERVER_PORT_KEY, "18081")); + String yarnQueue = (String) config.getOrDefault(ServerOptions.YARN_QUEUE_KEY, "default"); + String logLevel = (String) config.getOrDefault(ServerOptions.LOG_LEVEL_KEY, "INFO"); + + if (flinkHome == null || flinkHome.isEmpty()) { + throw new RuntimeException("flink home is null"); + } + + if (chunjunHome == null || chunjunHome.isEmpty()) { + throw new RuntimeException("chunjun home is null"); + } + + if (hadoopConfDir == null || hadoopConfDir.isEmpty()) { + // 通过环境变量获取 + hadoopConfDir = System.getenv("HADOOP_CONF_DIR"); + } + + if (hadoopConfDir == null || hadoopConfDir.isEmpty()) { + throw new RuntimeException("hadoop conf dir is null"); + } + + String flinkConfDir = flinkHome + File.separator + "conf"; + String flinkLibDir = flinkHome + File.separator + "lib"; + String flinkPluginsDir = flinkHome + File.separator + "plugins"; + + String chunjunLibDir = chunjunHome + File.separator + "chunjun-dist"; + String chunjunLogConfDir = + chunjunHome + File.separator + "conf" + File.separator + "logconf"; + + ChunJunConfig chunJunConfig = new ChunJunConfig(); + chunJunConfig.setFlinkConfDir(flinkConfDir); + chunJunConfig.setFlinkLibDir(flinkLibDir); + chunJunConfig.setFlinkPluginLibDir(flinkPluginsDir); + + chunJunConfig.setChunJunLibDir(chunjunLibDir); + chunJunConfig.setChunJunLogConfDir(chunjunLogConfDir); + + chunJunConfig.setHadoopConfDir(hadoopConfDir); + + chunJunConfig.setEnableSessionCheck(sessionCheck); + chunJunConfig.setEnableSessionDeploy(sessionDeploy); + chunJunConfig.setEnableRestful(restful); + chunJunConfig.setServerPort(serverPort); + + chunJunConfig.setLogLevel(logLevel); + chunJunConfig.setQueue(yarnQueue); + + return chunJunConfig; + } + + public void setEnableSessionCheck(boolean enableSessionCheck) { + this.enableSessionCheck = enableSessionCheck; + } + + public String getFlinkConfDir() { + return flinkConfDir; + } + + public void setFlinkConfDir(String flinkConfDir) { + this.flinkConfDir = flinkConfDir; + } + + public String getHadoopConfDir() { + return hadoopConfDir; + } + + public void setHadoopConfDir(String hadoopConfDir) { + this.hadoopConfDir = hadoopConfDir; + } + + public String getApplicationName() { + return applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + public String getQueue() { + return queue; + } + + public void setQueue(String queue) { + this.queue = queue; + } + + public boolean isEnableSessionDeploy() { + return enableSessionDeploy; + } + + public void setEnableSessionDeploy(boolean enableSessionDeploy) { + this.enableSessionDeploy = enableSessionDeploy; + } + + public String getFlinkLibDir() { + return flinkLibDir; + } + + public void setFlinkLibDir(String flinkLibDir) { + this.flinkLibDir = flinkLibDir; + } + + public String getChunJunLibDir() { + return chunJunLibDir; + } + + public void setChunJunLibDir(String chunJunLibDir) { + this.chunJunLibDir = chunJunLibDir; + } + + public String getFlinkPluginLibDir() { + return flinkPluginLibDir; + } + + public void setFlinkPluginLibDir(String flinkPluginLibDir) { + this.flinkPluginLibDir = flinkPluginLibDir; + } + + public String getLogLevel() { + return logLevel; + } + + public void setLogLevel(String logLevel) { + this.logLevel = logLevel; + } + + public String getChunJunLogConfDir() { + return chunJunLogConfDir; + } + + public void setChunJunLogConfDir(String chunJunLogConfDir) { + this.chunJunLogConfDir = chunJunLogConfDir; + } + + public boolean isEnableRestful() { + return enableRestful; + } + + public void setEnableRestful(boolean enableRestful) { + this.enableRestful = enableRestful; + } + + public int getServerPort() { + return serverPort; + } + + public void setServerPort(int serverPort) { + this.serverPort = serverPort; + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/config/ChunJunServerOptions.java b/chunjun-server/src/main/java/com/dtstack/chunjun/config/ChunJunServerOptions.java new file mode 100644 index 0000000000..0b8eff181f --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/config/ChunJunServerOptions.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.config; + +import org.apache.flink.configuration.ConfigOption; + +import static org.apache.flink.configuration.ConfigOptions.key; + +/** + * @author xuchao + * @date 2023-09-13 + */ +public class ChunJunServerOptions { + + public static final ConfigOption CHUNJUN_DIST_PATH = + key("chunjun.dist.path") + .stringType() + .noDefaultValue() + .withDescription("config the path of chunjun root dir."); +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/config/HadoopConfig.java b/chunjun-server/src/main/java/com/dtstack/chunjun/config/HadoopConfig.java new file mode 100644 index 0000000000..002cb47edb --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/config/HadoopConfig.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.config; + +import com.dtstack.chunjun.yarn.HadoopConfTool; +import com.dtstack.chunjun.yarn.YarnConfLoader; + +import org.apache.commons.collections.MapUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.yarn.conf.YarnConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +/** + * @author xuchao + * @date 2023-05-22 + */ +public class HadoopConfig { + + private static final Logger LOG = LoggerFactory.getLogger(HadoopConfig.class); + + private Configuration hadoopConfiguration; + + private YarnConfiguration yarnConfiguration; + + private String hadoopConfDir; + + public HadoopConfig(String hadoopConfDir) { + this.hadoopConfDir = hadoopConfDir; + } + + public void initHadoopConf(Map conf) { + hadoopConfiguration = HadoopConfTool.loadConf(hadoopConfDir); + HadoopConfTool.setFsHdfsImplDisableCache(hadoopConfiguration); + + // replace param + if (MapUtils.isNotEmpty(conf)) {} + } + + public void initYarnConf(Map conf) { + yarnConfiguration = YarnConfLoader.loadConf(hadoopConfDir); + + // replace param + if (MapUtils.isNotEmpty(conf)) {} + + HadoopConfTool.replaceDefaultParam(yarnConfiguration, conf); + LOG.info("load yarn config success"); + } + + public String getDefaultFS() { + return hadoopConfiguration.get("fs.defaultFS"); + } + + public Configuration getHadoopConfiguration() { + return hadoopConfiguration; + } + + public YarnConfiguration getYarnConfiguration() { + return yarnConfiguration; + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/config/SessionConfig.java b/chunjun-server/src/main/java/com/dtstack/chunjun/config/SessionConfig.java new file mode 100644 index 0000000000..2eef39e1a0 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/config/SessionConfig.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.config; + +import org.apache.flink.configuration.ConfigConstants; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.configuration.GlobalConfiguration; +import org.apache.flink.yarn.configuration.YarnConfigOptions; + +import org.apache.commons.lang3.StringUtils; + +/** + * session 指定的相关配置 + * + * @author xuchao + * @date 2023-05-22 + */ +public class SessionConfig { + + private String flinkLibDir; + + private String chunJunLibDir; + + private String flinkConfDir; + + private String hadoopConfDir; + + protected YarnAppConfig appConfig; + + protected Configuration flinkConfig; + + protected HadoopConfig hadoopConfig; + + public SessionConfig( + String flinkConfDir, String hadoopConfDir, String flinkLibDir, String chunJunLibDir) { + this.flinkConfDir = flinkConfDir; + this.hadoopConfDir = hadoopConfDir; + this.flinkLibDir = flinkLibDir; + this.chunJunLibDir = chunJunLibDir; + } + + public YarnAppConfig getAppConfig() { + return appConfig; + } + + public void setAppConfig(YarnAppConfig appConfig) { + this.appConfig = appConfig; + } + + public Configuration getFlinkConfig() { + return flinkConfig; + } + + public void setFlinkConfig(Configuration flinkConfig) { + this.flinkConfig = flinkConfig; + } + + public HadoopConfig getHadoopConfig() { + return hadoopConfig; + } + + public void setHadoopConfig(HadoopConfig hadoopConfig) { + this.hadoopConfig = hadoopConfig; + } + + public String getFlinkLibDir() { + return flinkLibDir; + } + + public void setFlinkLibDir(String flinkLibDir) { + this.flinkLibDir = flinkLibDir; + } + + public String getChunJunLibDir() { + return chunJunLibDir; + } + + public void setChunJunLibDir(String chunJunLibDir) { + this.chunJunLibDir = chunJunLibDir; + } + + public String getFlinkConfDir() { + return flinkConfDir; + } + + public void setFlinkConfDir(String flinkConfDir) { + this.flinkConfDir = flinkConfDir; + } + + public String getHadoopConfDir() { + return hadoopConfDir; + } + + public void setHadoopConfDir(String hadoopConfDir) { + this.hadoopConfDir = hadoopConfDir; + } + + public void loadFlinkConfiguration() { + if (flinkConfig == null) { + flinkConfig = + StringUtils.isEmpty(flinkConfDir) + ? new Configuration() + : GlobalConfiguration.loadConfiguration(flinkConfDir); + if (StringUtils.isNotBlank(appConfig.getApplicationName())) { + flinkConfig.setString( + YarnConfigOptions.APPLICATION_NAME, appConfig.getApplicationName()); + } + + if (StringUtils.isNotBlank(hadoopConfDir)) { + flinkConfig.setString(ConfigConstants.PATH_HADOOP_CONFIG, hadoopConfDir); + } + + // if + // (ConstantValue.CLASS_PATH_PLUGIN_LOAD_MODE.equalsIgnoreCase(pluginLoadMode)) { + // flinkConfig.setString(CoreOptions.CLASSLOADER_RESOLVE_ORDER, + // "child-first"); + // } else { + // flinkConfig.setString(CoreOptions.CLASSLOADER_RESOLVE_ORDER, + // "parent-first"); + // } + // flinkConfig.setString(ConfigConstant.FLINK_PLUGIN_LOAD_MODE_KEY, + // pluginLoadMode); + } + } + + public void loadHadoopConfiguration() { + HadoopConfig hadoopConfig = new HadoopConfig(hadoopConfDir); + hadoopConfig.initHadoopConf(null); + hadoopConfig.initYarnConf(null); + this.hadoopConfig = hadoopConfig; + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/config/WebConfig.java b/chunjun-server/src/main/java/com/dtstack/chunjun/config/WebConfig.java new file mode 100644 index 0000000000..e25dfbc4fb --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/config/WebConfig.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.config; + +import lombok.Data; + +/** + * @author xuchao + * @date 2023-09-19 + */ +@Data +public class WebConfig { + + private int port; +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/config/YarnAppConfig.java b/chunjun-server/src/main/java/com/dtstack/chunjun/config/YarnAppConfig.java new file mode 100644 index 0000000000..9277b6bc64 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/config/YarnAppConfig.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.config; + +/** + * session 对应yarn 的配置信息 + * + * @author xuchao + * @date 2023-05-22 + */ +public class YarnAppConfig { + + private String applicationName; + + private String queue; + + public String getApplicationName() { + return applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + public String getQueue() { + return queue; + } + + public void setQueue(String queue) { + this.queue = queue; + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobConverter.java b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobConverter.java new file mode 100644 index 0000000000..f60f5afba3 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobConverter.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.entry; + +import java.lang.reflect.Field; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +/** + * @author xuchao + * @date 2023-09-21 + */ +public class JobConverter { + + public static String[] convertJobToArgs(JobDescriptor jobDescriptor) + throws IllegalAccessException { + List argList = new ArrayList<>(); + Field[] fields = jobDescriptor.getClass().getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + String val = (String) field.get(jobDescriptor); + argList.add("-" + field.getName()); + argList.add(val); + } + + return argList.toArray(new String[argList.size()]); + } + + /** + * @param jobSubmitReq + * @return + */ + public static JobDescriptor convertReqToJobDescr(JobSubmitReq jobSubmitReq) { + JobDescriptor jobDescriptor = new JobDescriptor(); + // 当前系统提供的server 模式只支持yarn session 模式下的数据同步场景 + jobDescriptor.setJobType("sync"); + jobDescriptor.setMode("yarn-session"); + + String job = + jobSubmitReq.isURLDecode() + ? jobSubmitReq.getJob() + : URLEncoder.encode(jobSubmitReq.getJob()); + jobDescriptor.setJob(job); + jobDescriptor.setConfProp(jobSubmitReq.getConfProp()); + jobDescriptor.setJobName(jobSubmitReq.getJobName()); + + return jobDescriptor; + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobDescriptor.java b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobDescriptor.java new file mode 100644 index 0000000000..a638f87dbe --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobDescriptor.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.entry; + +import lombok.Data; + +/** + * @author xuchao + * @date 2023-09-21 + */ +@Data +public class JobDescriptor { + + private String mode; + + private String jobType; + + private String jobName; + + /** 提交的任务信息-json 格式 */ + private String job; + + /** 任务额外属性信息 */ + private String confProp; +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobInfoReq.java b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobInfoReq.java new file mode 100644 index 0000000000..731d679f3e --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobInfoReq.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.entry; + +import lombok.Data; + +/** + * 获取状态的请求 + * + * @author xuchao + * @date 2023-09-20 + */ +@Data +public class JobInfoReq { + + private String jobId; +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobLogVO.java b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobLogVO.java new file mode 100644 index 0000000000..4e7870b8b2 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobLogVO.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.entry; + +import lombok.Data; + +/** + * @author xuchao + * @date 2023-09-20 + */ +@Data +public class JobLogVO { + + private String jobId; + + private String log; +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobSubmitReq.java b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobSubmitReq.java new file mode 100644 index 0000000000..348ebfa8bd --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobSubmitReq.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.entry; + +import lombok.Data; + +/** + * 提交任务运行的请求 + * + * @author xuchao + * @date 2023-09-21 + */ +@Data +public class JobSubmitReq { + + private String jobName; + + /** 提交的任务信息-json 格式 */ + private String job; + + /** 任务额外属性信息 */ + private String confProp; + + private boolean isURLDecode = false; +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobSubmitVO.java b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobSubmitVO.java new file mode 100644 index 0000000000..1a6560f64e --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/JobSubmitVO.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.entry; + +import lombok.Data; + +/** + * @author xuchao + * @date 2023-09-21 + */ +@Data +public class JobSubmitVO { + + private String jobId; +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/entry/ResponseValue.java b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/ResponseValue.java new file mode 100644 index 0000000000..35b3fba02a --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/ResponseValue.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.entry; + +import lombok.Data; + +/** + * web 返回的value + * + * @author xuchao + * @date 2023-09-19 + */ +@Data +public class ResponseValue { + + /** 返回状态码 0正常,1 异常 */ + private int code; + /** 返回结果 */ + private String data; + /** 状态为异常情况下返回的错误信息 */ + private String errorMsg; + /** 请求执行耗时 */ + private long space; +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/entry/StatusVO.java b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/StatusVO.java new file mode 100644 index 0000000000..30ac26b4c9 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/entry/StatusVO.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.entry; + +import lombok.Data; + +/** + * 返回的任务状态信息 + * + * @author xuchao + * @date 2023-09-20 + */ +@Data +public class StatusVO { + private String jobId; + + private String jobStatus; +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/http/CJHttpRequestRetryHandler.java b/chunjun-server/src/main/java/com/dtstack/chunjun/http/CJHttpRequestRetryHandler.java new file mode 100644 index 0000000000..6296a2f921 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/http/CJHttpRequestRetryHandler.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.http; + +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpRequest; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.protocol.HttpContext; + +import java.io.IOException; + +/** + * @author xuchao + * @date 2023-06-28 + */ +public class CJHttpRequestRetryHandler implements HttpRequestRetryHandler { + @Override + public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { + + if (executionCount >= 3) { // 如果已经重试了3次,就放弃 + return false; + } + + HttpClientContext clientContext = HttpClientContext.adapt(context); + HttpRequest request = clientContext.getRequest(); + // 如果请求是幂等的,就再次尝试 + if (!(request instanceof HttpEntityEnclosingRequest)) { + return true; + } + return false; + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/http/PoolHttpClient.java b/chunjun-server/src/main/java/com/dtstack/chunjun/http/PoolHttpClient.java new file mode 100644 index 0000000000..acc58c52d0 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/http/PoolHttpClient.java @@ -0,0 +1,336 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.http; + +import com.dtstack.chunjun.util.RetryUtil; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.LayeredConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.message.BasicHeader; +import org.apache.http.util.EntityUtils; +import org.codehaus.jackson.map.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; + +/** + * @author xuchao + * @date 2023-06-28 + */ +public class PoolHttpClient { + + private static final Logger LOGGER = LoggerFactory.getLogger(PoolHttpClient.class); + + private static int SocketTimeout = 5000; // 5秒 + + private static int ConnectTimeout = 5000; // 5秒 + + // 将最大连接数增加到100 + private static int maxTotal = 100; + + // 将每个路由基础的连接增加到20 + private static int maxPerRoute = 20; + + private static int SLEEP_TIME_MILLI_SECOND = 2000; + + private static int DEFAULT_RETRY_TIMES = 3; + + private static ObjectMapper objectMapper = new ObjectMapper(); + + private static CloseableHttpClient httpClient = getHttpClient(); + + private static Charset charset = Charset.forName("UTF-8"); + + private static CloseableHttpClient getHttpClient() { + ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory(); + LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory(); + Registry registry = + RegistryBuilder.create() + .register("http", plainsf) + .register("https", sslsf) + .build(); + PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry); + cm.setMaxTotal(maxTotal); + cm.setDefaultMaxPerRoute(maxPerRoute); + + // 设置请求和传输超时时间 + RequestConfig requestConfig = + RequestConfig.custom() + // setConnectionRequestTimeout:设置从connect Manager获取Connection + // 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。 + .setConnectionRequestTimeout(ConnectTimeout) + // setSocketTimeout:请求获取数据的超时时间,单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。 + .setSocketTimeout(SocketTimeout) + // setConnectTimeout:设置连接超时时间,单位毫秒。 + .setConnectTimeout(ConnectTimeout) + .build(); + + return HttpClients.custom() + .setDefaultRequestConfig(requestConfig) + .setConnectionManager(cm) + .setRetryHandler(new CJHttpRequestRetryHandler()) + .build(); + } + + public static String post(String url, Map bodyData) { + return post(url, bodyData, null); + } + + public static String post(String url, Object bodyData) { + String responseBody = null; + CloseableHttpResponse response = null; + try { + HttpPost httpPost = new HttpPost(url); + + httpPost.setHeader("Content-type", "application/json;charset=UTF-8"); + if (bodyData != null) { + httpPost.setEntity( + new StringEntity(objectMapper.writeValueAsString(bodyData), charset)); + } + + // 请求数据 + response = httpClient.execute(httpPost); + int status = response.getStatusLine().getStatusCode(); + if (status == HttpStatus.SC_OK) { + HttpEntity entity = response.getEntity(); + // FIXME 暂时不从header读取 + responseBody = EntityUtils.toString(entity, charset); + } else { + LOGGER.warn( + "request url:{} fail:{}", url, response.getStatusLine().getStatusCode()); + } + } catch (Exception e) { + LOGGER.error("url:{}--->http request error:", url, e); + } finally { + if (response != null) { + try { + response.close(); + } catch (IOException e) { + LOGGER.error("", e); + } + } + } + return responseBody; + } + + public static String post( + String url, Map bodyData, Map cookies) { + return post(url, bodyData, cookies, Boolean.FALSE); + } + + public static String post( + String url, + Map bodyData, + Map cookies, + Boolean isRedirect) { + String responseBody = null; + CloseableHttpResponse response = null; + int status = 0; + HttpPost httpPost = null; + try { + httpPost = new HttpPost(url); + if (cookies != null && cookies.size() > 0) { + httpPost.addHeader("Cookie", getCookieFormat(cookies)); + } + + httpPost.setHeader("Content-type", "application/json;charset=UTF-8"); + if (bodyData != null && bodyData.size() > 0) { + httpPost.setEntity( + new StringEntity(objectMapper.writeValueAsString(bodyData), charset)); + } + + response = httpClient.execute(httpPost); + // 请求数据 + status = response.getStatusLine().getStatusCode(); + if (status == HttpStatus.SC_OK) { + HttpEntity entity = response.getEntity(); + // FIXME 暂时不从header读取 + responseBody = EntityUtils.toString(entity, charset); + } else { + LOGGER.warn( + "request url:{} fail:{}", url, response.getStatusLine().getStatusCode()); + if (isRedirect && status == HttpStatus.SC_TEMPORARY_REDIRECT) { + Header header = response.getFirstHeader("location"); // 跳转的目标地址是在 HTTP-HEAD上 + String newuri = header.getValue(); + HttpPost newHttpPost = new HttpPost(newuri); + newHttpPost.setHeader("Content-type", "application/json;charset=UTF-8"); + if (bodyData != null && bodyData.size() > 0) { + newHttpPost.setEntity( + new StringEntity( + objectMapper.writeValueAsString(bodyData), charset)); + } + if (cookies != null && cookies.size() > 0) { + newHttpPost.addHeader("Cookie", getCookieFormat(cookies)); + } + + response = httpClient.execute(newHttpPost); + int newStatus = response.getStatusLine().getStatusCode(); + if (newStatus == HttpStatus.SC_OK) { + responseBody = EntityUtils.toString(response.getEntity(), charset); + } + } + } + } catch (Exception e) { + LOGGER.error("url:{}--->http request error:", url, e); + responseBody = null; + } finally { + if (HttpStatus.SC_OK != status && null != httpPost) { + httpPost.abort(); + } + if (response != null) { + try { + response.close(); + } catch (IOException e) { + LOGGER.error("", e); + } + } + } + return responseBody; + } + + private static String getRequest(String url) throws IOException { + return getRequest(url, (Map) null); + } + + private static String getRequest(String url, Map cookies) throws IOException { + Header[] headers = {}; + if (cookies != null && cookies.size() > 0) { + Header header = new BasicHeader("Cookie", getCookieFormat(cookies)); + headers = new Header[] {header}; + } + return getRequest(url, headers); + } + + private static String getRequest(String url, Header[] headers) throws IOException { + String respBody = null; + HttpGet httpGet = null; + CloseableHttpResponse response = null; + int statusCode = 0; + try { + httpGet = new HttpGet(url); + if (headers != null && headers.length > 0) { + httpGet.setHeaders(headers); + } + response = httpClient.execute(httpGet); + statusCode = response.getStatusLine().getStatusCode(); + if (statusCode == HttpStatus.SC_OK) { + HttpEntity entity = response.getEntity(); + respBody = EntityUtils.toString(entity, charset); + } else if (statusCode == HttpStatus.SC_UNAUTHORIZED) { + throw new RuntimeException("登陆状态失效"); + } else { + LOGGER.warn( + "request url:{} fail:{}", url, response.getStatusLine().getStatusCode()); + + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) { + throw new RuntimeException(HttpStatus.SC_NOT_FOUND + ""); + } else if (response.getStatusLine().getStatusCode() + == HttpStatus.SC_INTERNAL_SERVER_ERROR) { + throw new RuntimeException(HttpStatus.SC_INTERNAL_SERVER_ERROR + ""); + } + } + } catch (IOException e) { + LOGGER.error("url:{}--->http request error:", url, e); + throw e; + } finally { + if (HttpStatus.SC_OK != statusCode && null != httpGet) { + httpGet.abort(); + } + if (response != null) { + try { + response.close(); + } catch (IOException e) { + LOGGER.error("", e); + } + } + } + return respBody; + } + + public static String get(String url) throws IOException { + try { + return get(url, null); + } catch (Exception e) { + throw new IOException(e); + } + } + + public static String get(String url, Map cookies) throws IOException { + try { + return get(url, cookies, DEFAULT_RETRY_TIMES); + } catch (Exception e) { + throw new IOException(e); + } + } + + public static String get(String url, Map cookies, int retryNumber) + throws Exception { + try { + Header[] headers = {}; + if (cookies != null && cookies.size() > 0) { + Header header = new BasicHeader("Cookie", getCookieFormat(cookies)); + headers = new Header[] {header}; + } + return get(url, retryNumber, headers); + } catch (Exception e) { + throw new IOException(e); + } + } + + public static String get(String url, int retryNumber, Header[] headers) throws Exception { + return RetryUtil.executeWithRetry( + new Callable() { + @Override + public String call() throws Exception { + return getRequest(url, headers); + } + }, + retryNumber, + SLEEP_TIME_MILLI_SECOND, + false); + } + + private static String getCookieFormat(Map cookies) { + StringBuffer sb = new StringBuffer(); + Set> sets = cookies.entrySet(); + for (Map.Entry s : sets) { + sb.append(s.getKey()).append("=").append(s.getValue().toString()).append(";"); + } + return sb.toString(); + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/options/ServerOptions.java b/chunjun-server/src/main/java/com/dtstack/chunjun/options/ServerOptions.java new file mode 100644 index 0000000000..cf0fcd0b36 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/options/ServerOptions.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.options; + +/** + * @author xuchao + * @date 2024-01-09 + */ +public class ServerOptions { + + public static final String CHUNJUN_SERVER_PORT_KEY = "server.port"; + + public static final String CHUNJUN_SESSION_CHECK_KEY = "chunjun.session.check"; + + public static final String CHUNJUN_HOME_PATH_KEY = "chunjun.home.path"; + + public static final String CHUNJUN_CONF_PATH_KEY = "chunjun.conf.path"; + + public static final String CHUNJUN_LIB_PATH_KEY = "chunjun.lib.path"; + + public static final String FLINK_HOME_PATH_KEY = "flink.home.path"; + + public static final String FLINK_CONF_PATH_KEY = "flink.conf.path"; + + public static final String FLINK_LIB_PATH_KEY = "flink.lib.path"; + + public static final String HADOOP_CONF_PATH = "hadoop.conf.path"; + + public static final String CHUNJUN_SESSION_DEPLOY_KEY = "chunjun.session.deploy"; + + public static final String CHUNJUN_RESTFUL_ENABLE_KEY = "chunjun.restful.enable"; + + public static final String YARN_QUEUE_KEY = "yarn.queue"; + + public static final String LOG_LEVEL_KEY = "log.level"; +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/restapi/RequestHandler.java b/chunjun-server/src/main/java/com/dtstack/chunjun/restapi/RequestHandler.java new file mode 100644 index 0000000000..a86edcb056 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/restapi/RequestHandler.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.restapi; + +import com.dtstack.chunjun.client.YarnSessionClient; +import com.dtstack.chunjun.entry.JobConverter; +import com.dtstack.chunjun.entry.JobDescriptor; +import com.dtstack.chunjun.entry.JobInfoReq; +import com.dtstack.chunjun.entry.JobLogVO; +import com.dtstack.chunjun.entry.JobSubmitReq; +import com.dtstack.chunjun.entry.JobSubmitVO; +import com.dtstack.chunjun.entry.ResponseValue; +import com.dtstack.chunjun.entry.StatusVO; +import com.dtstack.chunjun.server.SessionManager; +import com.dtstack.chunjun.server.util.JsonMapper; + +import org.apache.flink.util.Preconditions; + +import org.apache.flink.shaded.guava30.com.google.common.base.Strings; + +import io.javalin.Javalin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; + +/** + * @author xuchao + * @date 2023-09-19 + */ +public class RequestHandler { + + private static final Logger LOG = LoggerFactory.getLogger(RequestHandler.class); + + private static final String GET_LOG_URL = "/jobLog"; + + private static final String GET_STATUS_URL = "/jobstatus"; + + private static final String SUBMIT_JOB_URL = "/submitJob"; + + private Javalin app; + + private SessionManager sessionManager; + + public RequestHandler(Javalin app, SessionManager sessionManager) { + this.app = app; + this.sessionManager = sessionManager; + } + + public void register() { + registerLogAPI(); + registerStatusAPI(); + registerSubmitAPI(); + } + + /** 注册获取指定jobid 状态api */ + public void registerStatusAPI() { + LOG.info("register get status api"); + app.post( + GET_STATUS_URL, + ctx -> { + JobInfoReq jobInfoReq = ctx.bodyAsClass(JobInfoReq.class); + String jobId = jobInfoReq.getJobId(); + Preconditions.checkState(!Strings.isNullOrEmpty(jobId), "jobId can't be null"); + YarnSessionClient yarnSessionClient = sessionManager.getYarnSessionClient(); + String status = yarnSessionClient.getJobStatus(jobId); + + StatusVO statusVO = new StatusVO(); + statusVO.setJobId(jobId); + statusVO.setJobStatus(status); + + ResponseValue responseValue = new ResponseValue(); + responseValue.setCode(0); + responseValue.setData(JsonMapper.writeValueAsString(statusVO)); + + ctx.result(JsonMapper.writeValueAsString(responseValue)); + }); + } + + /** 注册获取jobid 对应日志的api */ + public void registerLogAPI() { + LOG.info("register get job log api"); + app.post( + GET_LOG_URL, + ctx -> { + JobInfoReq jobInfoReq = ctx.bodyAsClass(JobInfoReq.class); + String jobId = jobInfoReq.getJobId(); + Preconditions.checkState(!Strings.isNullOrEmpty(jobId), "jobId can't be null"); + YarnSessionClient yarnSessionClient = sessionManager.getYarnSessionClient(); + String jobLog = yarnSessionClient.getJobLog(jobId); + + JobLogVO jobLogVO = new JobLogVO(); + jobLogVO.setJobId(jobId); + jobLogVO.setLog(jobLog); + + ResponseValue responseValue = new ResponseValue(); + responseValue.setCode(0); + responseValue.setData(JsonMapper.writeValueAsString(jobLogVO)); + + ctx.result(JsonMapper.writeValueAsString(responseValue)); + }); + } + + /** 注册提交任务的api */ + public void registerSubmitAPI() { + LOG.info("register submit job api"); + app.post( + SUBMIT_JOB_URL, + ctx -> { + JobSubmitReq jobInfoReq = ctx.bodyAsClass(JobSubmitReq.class); + String job = jobInfoReq.getJob(); + if (jobInfoReq.isURLDecode()) { + job = URLDecoder.decode(job, StandardCharsets.UTF_8.toString()); + jobInfoReq.setJob(job); + jobInfoReq.setURLDecode(false); + } + + Preconditions.checkState(!Strings.isNullOrEmpty(job), "jobInfo can't be null"); + YarnSessionClient yarnSessionClient = sessionManager.getYarnSessionClient(); + JobDescriptor jobDescriptor = JobConverter.convertReqToJobDescr(jobInfoReq); + String jobId = yarnSessionClient.submitJob(jobDescriptor); + + JobSubmitVO jobLogVO = new JobSubmitVO(); + jobLogVO.setJobId(jobId); + + ResponseValue responseValue = new ResponseValue(); + responseValue.setCode(0); + responseValue.setData(JsonMapper.writeValueAsString(jobLogVO)); + + ctx.result(JsonMapper.writeValueAsString(responseValue)); + }); + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/restapi/WebServer.java b/chunjun-server/src/main/java/com/dtstack/chunjun/restapi/WebServer.java new file mode 100644 index 0000000000..5420b69335 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/restapi/WebServer.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.restapi; + +import com.dtstack.chunjun.config.WebConfig; +import com.dtstack.chunjun.entry.ResponseValue; +import com.dtstack.chunjun.server.SessionManager; +import com.dtstack.chunjun.server.util.JsonMapper; + +import io.javalin.Javalin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +/** + * @author xuchao + * @date 2023-09-19 + */ +public class WebServer { + + private static final Logger LOG = LoggerFactory.getLogger(WebServer.class); + + private WebConfig webConfig; + + private Javalin app; + + private RequestHandler requestHandler; + + private SessionManager sessionManager; + + public WebServer(WebConfig webConfig, SessionManager sessionManager) { + this.webConfig = webConfig; + this.sessionManager = sessionManager; + } + + public void startServer() { + int port = webConfig.getPort(); + if (!(port == 0 || (1024 <= port && port < 65536))) { + throw new IllegalArgumentException( + String.format( + "startPort should be between 1024 and 65535 (inclusive), " + + "or 0 for a random free port. but now is %s.", + port)); + } + + app = Javalin.create().start(port); + requestHandler = new RequestHandler(app, sessionManager); + LOG.info("===============start web sever on port {}===========================", port); + app.get("/", ctx -> ctx.result("Hello ChunJun")); + requestHandler.register(); + addExceptionHandler(); + } + + public void addExceptionHandler() { + LOG.info("add exception handler"); + app.exception( + Exception.class, + (e, ctx) -> { + ResponseValue responseValue = new ResponseValue(); + responseValue.setCode(1); + responseValue.setErrorMsg(e.getMessage()); + String responseMsg = ""; + + try { + responseMsg = JsonMapper.writeValueAsString(responseValue); + } catch (IOException ex) { + responseMsg = "{\"code\":1, \"errorMsg\":\"" + ex.getMessage() + "\"}"; + ctx.status(500).result(responseMsg); + } + ctx.status(500).result(responseMsg); + }); + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/server/ESessionStatus.java b/chunjun-server/src/main/java/com/dtstack/chunjun/server/ESessionStatus.java new file mode 100644 index 0000000000..8043ad6919 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/server/ESessionStatus.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.server; + +/** + * session 当前的状态 + * + * @author xuchao + * @date 2023-05-17 + */ +public enum ESessionStatus { + // 未初始化状态,表明还为对接到具体的yarn app上 + UNINIT("UNINIT"), + HEALTHY("HEALTHY"), + UNHEALTHY("UNHEALTHY"); + + private final String value; + + private ESessionStatus(String value) { + this.value = value; + } + + public String getValue() { + return this.getValue(); + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/server/ServerLauncher.java b/chunjun-server/src/main/java/com/dtstack/chunjun/server/ServerLauncher.java new file mode 100644 index 0000000000..7fc922171e --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/server/ServerLauncher.java @@ -0,0 +1,240 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.server; + +import com.dtstack.chunjun.config.ChunJunConfig; +import com.dtstack.chunjun.config.ChunJunServerOptions; +import com.dtstack.chunjun.config.SessionConfig; +import com.dtstack.chunjun.config.WebConfig; +import com.dtstack.chunjun.config.YarnAppConfig; +import com.dtstack.chunjun.restapi.WebServer; +import com.dtstack.chunjun.server.util.EnvUtil; + +import org.apache.flink.configuration.ConfigConstants; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.util.Preconditions; +import org.apache.flink.yarn.configuration.YarnConfigOptionsInternal; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.net.MalformedURLException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; + +import static org.apache.flink.configuration.ConfigConstants.ENV_FLINK_LIB_DIR; +import static org.apache.flink.yarn.configuration.YarnConfigOptions.FLINK_DIST_JAR; + +/** + * 启动 chunjun server 服务 + * + * @author xuchao + * @date 2023-06-13 + */ +public class ServerLauncher { + + private static final Logger LOG = LoggerFactory.getLogger(ServerLauncher.class); + + private ChunJunConfig chunJunConfig; + + private int port = 18081; + + private SessionManager sessionManager; + + private WebServer webServer; + + private SessionConfig sessionConfig; + + public void startServer() throws Exception { + sessionConfig = + new SessionConfig( + chunJunConfig.getFlinkConfDir(), + chunJunConfig.getHadoopConfDir(), + chunJunConfig.getFlinkLibDir(), + chunJunConfig.getChunJunLibDir()); + + YarnAppConfig yarnAppConfig = new YarnAppConfig(); + yarnAppConfig.setApplicationName(chunJunConfig.getApplicationName()); + yarnAppConfig.setQueue(chunJunConfig.getQueue()); + + sessionConfig.setAppConfig(yarnAppConfig); + sessionConfig.loadFlinkConfiguration(); + sessionConfig.loadHadoopConfiguration(); + + // 初始化环境变量,设置env相关的属性配置 + initEnv(); + // 启动session 检查 + sessionManagerStart(); + // 启动restapi 服务 + webStart(); + + // 注册shutdown hook + Runtime.getRuntime().addShutdownHook(new ShutdownThread(this)); + } + + public void stop() { + sessionManager.stopSessionCheck(); + sessionManager.stopSessionDeploy(); + } + + public ChunJunConfig getChunJunConfig() { + return chunJunConfig; + } + + public void setChunJunConfig(ChunJunConfig chunJunConfig) { + this.chunJunConfig = chunJunConfig; + } + + public void initEnv() throws Exception { + // 如果环境变量没有设置ENV_FLINK_LIB_DIR, 但是进程要求必须传对应的参数,所以可以通过进程来设置 + // upload and register ship-only files,Plugin files only need to be shipped and should not + // be added to classpath. + Configuration configuration = sessionConfig.getFlinkConfig(); + + if (System.getenv().get(ENV_FLINK_LIB_DIR) == null) { + LOG.warn( + "Environment variable '{}' not set and ship files have not been provided manually.", + ENV_FLINK_LIB_DIR); + Map envMap = new HashMap<>(); + envMap.put(ENV_FLINK_LIB_DIR, sessionConfig.getFlinkLibDir()); + EnvUtil.setEnv(envMap); + } + + // 添加ChunJun dist 目录到configuration + String chunJunLibDir = sessionConfig.getChunJunLibDir(); + if (chunJunLibDir != null) { + chunJunLibDir = + chunJunLibDir.startsWith("file:") ? chunJunLibDir : "file:" + chunJunLibDir; + configuration.set(ChunJunServerOptions.CHUNJUN_DIST_PATH, chunJunLibDir); + } + + // 添加对flinkplugin 的环境变量的设置 + String envFlinkPluginDir = System.getenv().get(ConfigConstants.ENV_FLINK_PLUGINS_DIR); + if (envFlinkPluginDir == null) { + Map envMap = new HashMap<>(); + envMap.put(ConfigConstants.ENV_FLINK_PLUGINS_DIR, chunJunConfig.getFlinkPluginLibDir()); + EnvUtil.setEnv(envMap); + } + + // 添加日志路径环境变量 + String logConfigPath = chunJunConfig.getChunJunLogConfDir(); + LOG.info("chunjun session log level:{}", chunJunConfig.getLogLevel()); + String levelPath = + logConfigPath + File.separatorChar + chunJunConfig.getLogLevel().toLowerCase(); + + Preconditions.checkArgument( + new File(logConfigPath).exists(), + levelPath + " is not exists. please check log conf dir path."); + + sessionConfig + .getFlinkConfig() + .set(YarnConfigOptionsInternal.APPLICATION_LOG_CONFIG_FILE, levelPath); + + if (configuration.get(FLINK_DIST_JAR) == null) { + // 设置yarnClusterDescriptor yarn.flink-dist-jar + File flinkLibDir = new File(sessionConfig.getFlinkLibDir()); + if (!flinkLibDir.isDirectory()) { + throw new RuntimeException( + String.format( + "param set flinkLibDir(%s) is not a dir, please check it.", + sessionConfig.getFlinkLibDir())); + } + Arrays.stream(flinkLibDir.listFiles()) + .map( + file -> { + try { + if (file.toURI().toURL().toString().contains("flink-dist")) { + configuration.setString( + FLINK_DIST_JAR, file.toURI().toURL().toString()); + } + } catch (MalformedURLException e) { + LOG.warn("", e); + } + + return file; + }) + .collect(Collectors.toList()); + } + } + + public void sessionManagerStart() { + sessionManager = new SessionManager(sessionConfig); + // 是否启动session check 服务 + if (chunJunConfig.isEnableSessionCheck()) { + LOG.info("open session check!"); + sessionManager.startSessionCheck(); + } + + // 是否开启session deploy 服务 + if (chunJunConfig.isEnableSessionDeploy()) { + LOG.info("open session deploy!"); + sessionManager.startSessionDeploy(); + } + } + + public void webStart() { + if (chunJunConfig.isEnableRestful()) { + LOG.info("start web server "); + WebConfig webConfig = new WebConfig(); + webConfig.setPort(port); + webServer = new WebServer(webConfig, sessionManager); + webServer.startServer(); + } + } + + private static class ShutdownThread extends Thread { + private ServerLauncher serverLauncher; + + public ShutdownThread(ServerLauncher serverLauncher) { + this.serverLauncher = serverLauncher; + } + + public void run() { + LOG.info("shutting down the chunjun serverLancher."); + try { + serverLauncher.stop(); + } catch (Exception e) { + LOG.error("failed to shut down the chunjun server lancher,", e); + } + + LOG.info("chunjun serverLancher has been shutdown."); + } + } + + public static void main(String[] args) throws Exception { + ServerLauncher serverLauncher = new ServerLauncher(); + Properties argProp = new Properties(); + String appConfPath = "./conf/application.properties"; + File configFile = new File(appConfPath); + if (!configFile.exists()) { + throw new RuntimeException(String.format("%s config file is not exists.", appConfPath)); + } + + argProp.load(new FileInputStream(configFile)); + ChunJunConfig chunJunConfig = ChunJunConfig.fromProperties(argProp); + serverLauncher.setChunJunConfig(chunJunConfig); + + serverLauncher.startServer(); + LOG.info("start chunjun server success!"); + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/server/SessionDeployer.java b/chunjun-server/src/main/java/com/dtstack/chunjun/server/SessionDeployer.java new file mode 100644 index 0000000000..7b3c41f048 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/server/SessionDeployer.java @@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.server; + +import com.dtstack.chunjun.config.SessionConfig; +import com.dtstack.chunjun.config.YarnAppConfig; +import com.dtstack.chunjun.server.util.FileUtil; + +import org.apache.flink.client.deployment.ClusterSpecification; +import org.apache.flink.client.program.ClusterClient; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.configuration.DeploymentOptions; +import org.apache.flink.configuration.HighAvailabilityOptions; +import org.apache.flink.configuration.JobManagerOptions; +import org.apache.flink.configuration.MemorySize; +import org.apache.flink.configuration.TaskManagerOptions; +import org.apache.flink.runtime.jobmanager.HighAvailabilityMode; +import org.apache.flink.yarn.CJYarnClusterClientFactory; +import org.apache.flink.yarn.YarnClusterDescriptor; +import org.apache.flink.yarn.configuration.YarnConfigOptions; +import org.apache.flink.yarn.configuration.YarnDeploymentTarget; + +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.yarn.api.records.ApplicationId; +import org.apache.hadoop.yarn.conf.YarnConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +/** + * 启动一个session + * + * @author xuchao + * @date 2023-06-28 + */ +public class SessionDeployer { + + private static final Logger LOG = LoggerFactory.getLogger(SessionDeployer.PREFIX_HDFS); + + public static final String PREFIX_HDFS = "hdfs://"; + + public static final String REMOTE_FLINK_LIB_DIR = "remoteFlinkLibDir"; + + public static final String REMOTE_CHUNJUN_LIB_DIR = "remoteChunJunLibDir"; + + public static final String FLINK_LIB_DIR = "flinkLibDir"; + + public static final String CHUNJUN_LIB_DIR = "chunjunLibDir"; + + private String flinkLibDir; + + private String chunJunLibDir; + + private String flinkConfPath; + + private String hadoopConfDir; + + private boolean flinkHighAvailability; + + private YarnConfiguration yarnConf; + + /** flink-defined configuration */ + protected Configuration flinkConfiguration; + + private YarnAppConfig yarnAppConfig; + + private ClusterSpecification clusterSpecification; + + private SessionStatusInfo sessionStatus; + + public SessionDeployer(SessionConfig sessionConfig, SessionStatusInfo sessionStatusInfo) { + this.yarnAppConfig = sessionConfig.getAppConfig(); + this.flinkLibDir = sessionConfig.getFlinkLibDir(); + this.flinkConfPath = sessionConfig.getFlinkConfDir(); + this.chunJunLibDir = sessionConfig.getChunJunLibDir(); + this.hadoopConfDir = sessionConfig.getHadoopConfDir(); + + this.flinkConfiguration = sessionConfig.getFlinkConfig(); + this.yarnConf = sessionConfig.getHadoopConfig().getYarnConfiguration(); + this.sessionStatus = sessionStatusInfo; + } + + public void doDeploy() { + + ClusterSpecification.ClusterSpecificationBuilder builder = + new ClusterSpecification.ClusterSpecificationBuilder(); + + MemorySize taskManagerMB = flinkConfiguration.get(TaskManagerOptions.TOTAL_PROCESS_MEMORY); + MemorySize jobManagerMB = flinkConfiguration.get(JobManagerOptions.TOTAL_PROCESS_MEMORY); + Integer numTaskSlots = flinkConfiguration.get(TaskManagerOptions.NUM_TASK_SLOTS); + + builder.setMasterMemoryMB(jobManagerMB.getMebiBytes()); + builder.setTaskManagerMemoryMB(taskManagerMB.getMebiBytes()); + builder.setSlotsPerTaskManager(numTaskSlots); + clusterSpecification = builder.createClusterSpecification(); + + try (YarnClusterDescriptor yarnSessionDescriptor = createYarnClusterDescriptor()) { + ClusterClient clusterClient = + yarnSessionDescriptor + .deploySessionCluster(clusterSpecification) + .getClusterClient(); + LOG.info("start session with cluster id :" + clusterClient.getClusterId().toString()); + // 重新开始session check + sessionStatus.setStatus(ESessionStatus.HEALTHY); + } catch (Throwable e) { + LOG.error("Couldn't deploy Yarn session cluster, ", e); + // 重新开始session check + sessionStatus.setStatus(ESessionStatus.UNHEALTHY); + throw new RuntimeException(e); + } + } + + public YarnClusterDescriptor createYarnClusterDescriptor() { + Configuration flinkCopyConf = new Configuration(flinkConfiguration); + FileUtil.checkFileExist(flinkLibDir); + + if (!flinkHighAvailability) { + setNoneHaModeConfig(flinkCopyConf); + } else { + // 由engine管控的yarnsession clusterId不进行设置,默认使用appId作为clusterId + flinkCopyConf.removeConfig(HighAvailabilityOptions.HA_CLUSTER_ID); + } + + setHdfsFlinkJarPath(flinkCopyConf); + flinkCopyConf.setString( + YarnConfigOptions.APPLICATION_QUEUE.key(), yarnAppConfig.getQueue()); + flinkCopyConf.set(DeploymentOptions.TARGET, YarnDeploymentTarget.SESSION.getName()); + CJYarnClusterClientFactory clusterClientFactory = new CJYarnClusterClientFactory(); + + YarnConfiguration newYarnConfig = new YarnConfiguration(); + for (Map.Entry next : yarnConf) { + newYarnConfig.set(next.getKey(), next.getValue()); + } + + return clusterClientFactory.createClusterDescriptor(flinkCopyConf, newYarnConfig); + } + + public void setNoneHaModeConfig(Configuration configuration) { + configuration.setString( + HighAvailabilityOptions.HA_MODE, HighAvailabilityMode.NONE.toString()); + configuration.removeConfig(HighAvailabilityOptions.HA_CLUSTER_ID); + configuration.removeConfig(HighAvailabilityOptions.HA_ZOOKEEPER_ROOT); + configuration.removeConfig(HighAvailabilityOptions.HA_ZOOKEEPER_QUORUM); + } + + /** 插件包及Lib包提前上传至HDFS,设置远程HDFS路径参数 */ + // TODO + public void setHdfsFlinkJarPath(Configuration flinkConfiguration) { + // 检查HDFS上是否已经上传插件包及Lib包 + String remoteFlinkLibDir = null; + // remotePluginRootDir默认不为空 + String remoteChunjunLibDir = null; + + // 不考虑二者只有其一上传到了hdfs上的情况 + if (StringUtils.startsWith(remoteFlinkLibDir, PREFIX_HDFS) + && StringUtils.startsWith(remoteChunjunLibDir, PREFIX_HDFS)) { + flinkConfiguration.setString(REMOTE_FLINK_LIB_DIR, remoteFlinkLibDir); + flinkConfiguration.setString(REMOTE_CHUNJUN_LIB_DIR, remoteChunjunLibDir); + flinkConfiguration.setString(FLINK_LIB_DIR, flinkLibDir); + flinkConfiguration.setString(CHUNJUN_LIB_DIR, chunJunLibDir); + } + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/server/SessionManager.java b/chunjun-server/src/main/java/com/dtstack/chunjun/server/SessionManager.java new file mode 100644 index 0000000000..31155f6a16 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/server/SessionManager.java @@ -0,0 +1,157 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.server; + +import com.dtstack.chunjun.client.YarnSessionClient; +import com.dtstack.chunjun.config.SessionConfig; +import com.dtstack.chunjun.config.YarnAppConfig; +import com.dtstack.chunjun.factory.ChunJunThreadFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * session 管理器,对session 进行监控和自管理(根据检查结果启动session) + * + * @author xuchao + * @date 2023-05-16 + */ +public class SessionManager { + + private static final Logger LOG = LoggerFactory.getLogger(SessionManager.class); + + private static final int DEPLOY_CHECK_INTERVAL = 2 * 1000; + + private static final int DEPLOY_MAX_RETRY = 3; + + private int retryNum = 0; + + private static final String FLINK_VERSION = "flink116"; + + private SessionConfig sessionConfig; + + private YarnAppConfig yarnAppConfig; + + private SessionStatusMonitor sessionStatusMonitor; + + private ScheduledExecutorService sessionDeployScheduler; + + private YarnSessionClient yarnSessionClient; + + private final SessionStatusInfo sessionStatusInfo = new SessionStatusInfo(); + + private SessionDeployer sessionDeployer; + + public SessionManager(SessionConfig sessionConfig) { + this.sessionConfig = sessionConfig; + this.yarnAppConfig = sessionConfig.getAppConfig(); + initYarnSessionClient(); + } + + public void initYarnSessionClient() { + yarnSessionClient = new YarnSessionClient(sessionConfig); + yarnSessionClient.open(); + + if (yarnSessionClient.getClient() == null) { + // 初始化的时候检查远程是否存在匹配的session + sessionStatusInfo.setStatus(ESessionStatus.UNHEALTHY); + } else { + sessionStatusInfo.setAppId(yarnSessionClient.getClient().getClusterId().toString()); + } + + sessionDeployScheduler = + new ScheduledThreadPoolExecutor( + 1, + new ChunJunThreadFactory( + "session_deploy_factory", + true, + (t, e) -> { + LOG.error("session_deploy_factory occur error!", e); + })); + } + + /** 开启session 监控 */ + public void startSessionCheck() { + sessionStatusMonitor = new SessionStatusMonitor(yarnSessionClient, sessionStatusInfo); + sessionStatusMonitor.start(); + } + + public void stopSessionCheck() { + if (sessionStatusMonitor != null) { + sessionStatusMonitor.shutdown(); + LOG.info("stopSessionCheck stopped"); + } + } + + public void startSessionDeploy() { + + sessionDeployer = new SessionDeployer(sessionConfig, sessionStatusInfo); + sessionDeployScheduler.scheduleWithFixedDelay( + () -> { + if (!ESessionStatus.UNHEALTHY.equals(sessionStatusInfo.getStatus())) { + return; + } + + LOG.warn("current session status is unhealthy, will deploy a new session."); + + try { + sessionDeployer.doDeploy(); + LOG.info("deploy yarn session success."); + retryNum = 0; + } catch (Exception e) { + retryNum++; + // 每次重试间隔递增retryNum * 30s + long sleepTime = retryNum * 30000L; + LOG.error( + "deploy session error, retryNum:{}, and sleep {}ms", + retryNum, + sleepTime); + try { + Thread.sleep(sleepTime); + } catch (InterruptedException ex) { + LOG.info("", ex); + } + + if (retryNum >= DEPLOY_MAX_RETRY) { + LOG.error( + "!!!!!deploy session retry max then {}, exit schedule thread.", + DEPLOY_MAX_RETRY); + throw e; + } + } + }, + DEPLOY_CHECK_INTERVAL, + DEPLOY_CHECK_INTERVAL, + TimeUnit.MILLISECONDS); + } + + public void stopSessionDeploy() { + if (sessionDeployScheduler != null) { + sessionDeployScheduler.shutdown(); + LOG.info("sessionDeployScheduler stopped"); + } + } + + public YarnSessionClient getYarnSessionClient() { + return yarnSessionClient; + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/server/SessionStatusInfo.java b/chunjun-server/src/main/java/com/dtstack/chunjun/server/SessionStatusInfo.java new file mode 100644 index 0000000000..e72866cb31 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/server/SessionStatusInfo.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.server; + +/** + * 当前session 的状态信息 + * + * @author xuchao + * @date 2023-05-17 + */ +public class SessionStatusInfo { + + private String appId; + + private ESessionStatus status = ESessionStatus.UNINIT; + + public SessionStatusInfo() {} + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public ESessionStatus getStatus() { + return status; + } + + public void setStatus(ESessionStatus status) { + this.status = status; + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/server/SessionStatusMonitor.java b/chunjun-server/src/main/java/com/dtstack/chunjun/server/SessionStatusMonitor.java new file mode 100644 index 0000000000..9d9c8a7a88 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/server/SessionStatusMonitor.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.server; + +import com.dtstack.chunjun.client.YarnSessionClient; +import com.dtstack.chunjun.http.PoolHttpClient; + +import org.apache.hadoop.yarn.api.records.YarnApplicationState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * 监控对应session的监控状态 基于对jobmanager 获取tm 列表的restapi 进行检查 + * + * @author xuchao + * @date 2023-05-17 + */ +public class SessionStatusMonitor implements Runnable { + + private static final Logger LOG = LoggerFactory.getLogger(SessionStatusMonitor.class); + + private static final String REQ_URL_FORMAT = "%s/jobmanager/config"; + + private static final long CHECK_INTERVAL = 5 * 1000L; + + /** 从accepted --> running 额外等待时间 */ + private static final long ACCEPTED_GAP = 10 * 1000L; + + private static final int FAIL_LIMIT = 3; + + private ExecutorService executorService; + + private YarnSessionClient yarnSessionClient; + + private SessionStatusInfo sessionStatusInfo; + + private boolean run = true; + + private int checkFailCount = 0; + + public SessionStatusMonitor( + YarnSessionClient yarnSessionClient, SessionStatusInfo sessionStatusInfo) { + this.yarnSessionClient = yarnSessionClient; + this.sessionStatusInfo = sessionStatusInfo; + this.executorService = Executors.newSingleThreadExecutor(); + } + + public void start() { + executorService.submit(this); + LOG.info("session status check started!"); + } + + public void shutdown() { + run = false; + executorService.shutdown(); + LOG.info("session status check stopped!"); + } + + @Override + public void run() { + + LOG.warn("session status check thread start!"); + while (run) { + try { + + // yarnSessionClient 没有获取到对应的appid,不需要进行check 等到重新构建yarnSessionClient 再check + if (yarnSessionClient.getClient() == null + && sessionStatusInfo.getStatus() == ESessionStatus.HEALTHY) { + yarnSessionClient.open(); + continue; + } + + if (sessionStatusInfo.getStatus() == ESessionStatus.UNHEALTHY) { + continue; + } + + // 检查yarn 对应app的状态,如果是Accepted 的情况,等待 + YarnApplicationState yarnApplicationState = + yarnSessionClient.getCurrentSessionStatus(); + if (yarnApplicationState == YarnApplicationState.ACCEPTED) { + LOG.warn("current session is Accepted. "); + Thread.sleep(ACCEPTED_GAP); + continue; + } + + String reqWebURL = + String.format( + REQ_URL_FORMAT, yarnSessionClient.getClient().getWebInterfaceURL()); + String response = PoolHttpClient.get(reqWebURL); + if (response == null) { + // 异常情况:连续3次认为session已经不可以用了 + checkFailCount++; + } else { + checkFailCount = 0; + } + + } catch (Exception e) { + checkFailCount++; + } finally { + try { + Thread.sleep(CHECK_INTERVAL); + } catch (InterruptedException e) { + LOG.warn("", e); + } + } + + if (checkFailCount >= FAIL_LIMIT) { + sessionStatusInfo.setStatus(ESessionStatus.UNHEALTHY); + try { + yarnSessionClient.close(); + } catch (IOException e) { + LOG.warn("close yarnSessionClient exception.", e); + } + LOG.warn("current check failed."); + } + } + + LOG.warn("session status check thread exit!"); + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/server/util/EnvUtil.java b/chunjun-server/src/main/java/com/dtstack/chunjun/server/util/EnvUtil.java new file mode 100644 index 0000000000..b30bdd56ad --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/server/util/EnvUtil.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.server.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Map; + +/** + * 设置环境变量工具类 + * + * @author xuchao + * @date 2023-07-28 + */ +public class EnvUtil { + public static void setEnv(Map newenv) throws Exception { + getModifiableEnvironment().putAll(newenv); + } + + private static Map getModifiableEnvironment() throws Exception { + Class pe = Class.forName("java.lang.ProcessEnvironment"); + Method getenv = pe.getDeclaredMethod("getenv"); + getenv.setAccessible(true); + Object unmodifiableEnvironment = getenv.invoke(null); + Class map = Class.forName("java.util.Collections$UnmodifiableMap"); + Field m = map.getDeclaredField("m"); + m.setAccessible(true); + return (Map) m.get(unmodifiableEnvironment); + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/server/util/FileUtil.java b/chunjun-server/src/main/java/com/dtstack/chunjun/server/util/FileUtil.java new file mode 100644 index 0000000000..55fbfaa2e8 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/server/util/FileUtil.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.server.util; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; + +/** + * 文件操作相关 + * + * @author xuchao + * @date 2023-07-05 + */ +public class FileUtil { + + private static final Logger LOG = LoggerFactory.getLogger(FileUtil.class); + + public static void checkFileExist(String filePath) { + if (StringUtils.isNotBlank(filePath)) { + if (!new File(filePath).exists()) { + throw new RuntimeException( + String.format("The file jar %s path is not exist ", filePath)); + } + } + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/server/util/JobGraphBuilder.java b/chunjun-server/src/main/java/com/dtstack/chunjun/server/util/JobGraphBuilder.java new file mode 100644 index 0000000000..9daf1a7033 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/server/util/JobGraphBuilder.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.server.util; + +import com.dtstack.chunjun.config.SessionConfig; +import com.dtstack.chunjun.throwable.ChunJunRuntimeException; + +import org.apache.flink.client.program.PackagedProgram; +import org.apache.flink.client.program.PackagedProgramUtils; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.runtime.jobgraph.JobGraph; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 基于json info 构建 JobGraph + * + * @author xuchao + * @date 2023-09-21 + */ +public class JobGraphBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(JobGraphBuilder.class); + + private static final String MAIN_CLASS = "com.dtstack.chunjun.Main"; + + private static final String CORE_JAR_NAME_PREFIX = "chunjun"; + + private SessionConfig config; + + public JobGraphBuilder(SessionConfig chunJunConfig) { + this.config = chunJunConfig; + } + + public JobGraph buildJobGraph(String[] programArgs) throws Exception { + String pluginRoot = config.getChunJunLibDir(); + String coreJarPath = getCoreJarPath(pluginRoot); + File jarFile = new File(coreJarPath); + PackagedProgram program = + PackagedProgram.newBuilder() + .setJarFile(jarFile) + .setUserClassPaths(Lists.newArrayList(getURLFromRootDir(pluginRoot))) + .setEntryPointClassName(MAIN_CLASS) + // + // .setConfiguration(launcherOptions.loadFlinkConfiguration()) + .setArguments(programArgs) + .build(); + JobGraph jobGraph = + PackagedProgramUtils.createJobGraph(program, new Configuration(), 1, false); + List pluginClassPath = + jobGraph.getUserArtifacts().entrySet().stream() + .filter(tmp -> tmp.getKey().startsWith("class_path")) + .map(tmp -> new File(tmp.getValue().filePath)) + .map( + file -> { + try { + return file.toURI().toURL(); + } catch (MalformedURLException e) { + LOG.error(e.getMessage()); + } + return null; + }) + .collect(Collectors.toList()); + jobGraph.setClasspaths(pluginClassPath); + return jobGraph; + } + + public String getCoreJarPath(String pluginRoot) { + File pluginDir = new File(pluginRoot); + if (pluginDir.exists() && pluginDir.isDirectory()) { + File[] jarFiles = + pluginDir.listFiles( + (dir, name) -> + name.toLowerCase().startsWith(CORE_JAR_NAME_PREFIX) + && name.toLowerCase().endsWith(".jar")); + + if (jarFiles != null && jarFiles.length > 0) { + return pluginRoot + File.separator + jarFiles[0].getName(); + } + } + + throw new RuntimeException( + String.format( + "can't find chunjun core file(name: chunjun*.jar) from chunjun dir %s.", + pluginRoot)); + } + + public Set getURLFromRootDir(String path) { + Set urlSet = Sets.newHashSet(); + File plugins = new File(path); + if (!plugins.exists()) { + throw new ChunJunRuntimeException( + path + " is not exist! Please check the configuration."); + } + + addFileToURL(urlSet, Optional.of(plugins)); + return urlSet; + } + + public void addFileToURL(Set urlSet, Optional pluginFile) { + pluginFile.ifPresent( + item -> { + File[] files = item.listFiles(); + assert files != null; + for (File file : files) { + if (file.isDirectory()) { + addFileToURL(urlSet, Optional.of(file)); + } + + if (file.isFile() && file.isAbsolute()) { + try { + urlSet.add(file.toURI().toURL()); + } catch (MalformedURLException e) { + throw new ChunJunRuntimeException( + "The error should not occur, please check the code.", e); + } + } + } + }); + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/server/util/JsonMapper.java b/chunjun-server/src/main/java/com/dtstack/chunjun/server/util/JsonMapper.java new file mode 100644 index 0000000000..7c9b74fc0d --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/server/util/JsonMapper.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.server.util; + +import org.codehaus.jackson.map.ObjectMapper; + +import java.io.IOException; + +/** + * @author xuchao + * @date 2023-09-20 + */ +public class JsonMapper { + private static ObjectMapper objectMapper = new ObjectMapper(); + + public static T fromJsonString(String json, Class targetClass) throws IOException { + return objectMapper.readValue(json, targetClass); + } + + public static String writeValueAsString(Object obj) throws IOException { + return objectMapper.writeValueAsString(obj); + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/yarn/HadoopConfTool.java b/chunjun-server/src/main/java/com/dtstack/chunjun/yarn/HadoopConfTool.java new file mode 100644 index 0000000000..83a5e168af --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/yarn/HadoopConfTool.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.yarn; + +import com.dtstack.chunjun.constants.ConstantValue; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.CommonConfigurationKeys; +import org.apache.hadoop.yarn.conf.YarnConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.Map; + +/** + * 解析获取hadoop 的配置 + * + * @author xuchao + * @date 2023-05-22 + */ +public class HadoopConfTool { + private static final Logger LOG = LoggerFactory.getLogger(HadoopConfTool.class); + + public static final String FS_HDFS_IMPL_DISABLE_CACHE = "fs.hdfs.impl.disable.cache"; + public static final String FS_LOCAL_IMPL_DISABLE_CACHE = "fs.file.impl.disable.cache"; + + public static void setFsHdfsImplDisableCache(Configuration conf) { + conf.setBoolean(FS_HDFS_IMPL_DISABLE_CACHE, true); + } + + public static Configuration loadConf(String yarnConfDir) { + Configuration yarnConf = new Configuration(false); + try { + + File dir = new File(yarnConfDir); + if (dir.exists() && dir.isDirectory()) { + + File[] xmlFileList = + new File(yarnConfDir) + .listFiles( + (dir1, name) -> + name.endsWith(ConstantValue.FILE_SUFFIX_XML)); + + if (xmlFileList != null) { + for (File xmlFile : xmlFileList) { + yarnConf.addResource(xmlFile.toURI().toURL()); + } + } + } + + } catch (Exception e) { + throw new RuntimeException(e); + } + + return yarnConf; + } + + /** 调整默认值 ipc.client.fallback-to-simple-auth-allowed: true */ + public static void replaceDefaultParam(Configuration yarnConf, Map yarnMap) { + yarnConf.setBoolean( + CommonConfigurationKeys.IPC_CLIENT_FALLBACK_TO_SIMPLE_AUTH_ALLOWED_KEY, true); + + if (yarnMap == null) { + return; + } + + if (yarnMap.get(YarnConfiguration.RESOURCEMANAGER_CONNECT_MAX_WAIT_MS) != null) { + yarnConf.set( + YarnConfiguration.RESOURCEMANAGER_CONNECT_MAX_WAIT_MS, + (String) yarnMap.get(YarnConfiguration.RESOURCEMANAGER_CONNECT_MAX_WAIT_MS)); + } else { + yarnConf.setLong(YarnConfiguration.RESOURCEMANAGER_CONNECT_MAX_WAIT_MS, 15000L); + } + + if (yarnMap.get(YarnConfiguration.RESOURCEMANAGER_CONNECT_RETRY_INTERVAL_MS) != null) { + yarnConf.set( + YarnConfiguration.RESOURCEMANAGER_CONNECT_RETRY_INTERVAL_MS, + (String) + yarnMap.get( + YarnConfiguration.RESOURCEMANAGER_CONNECT_RETRY_INTERVAL_MS)); + } else { + yarnConf.setLong(YarnConfiguration.RESOURCEMANAGER_CONNECT_RETRY_INTERVAL_MS, 5000L); + } + + LOG.info( + "yarn.resourcemanager.connect.max-wait.ms:{} yarn.resourcemanager.connect.retry-interval.ms:{}", + yarnConf.getLong(YarnConfiguration.RESOURCEMANAGER_CONNECT_MAX_WAIT_MS, -1), + yarnConf.getLong(YarnConfiguration.RESOURCEMANAGER_CONNECT_RETRY_INTERVAL_MS, -1)); + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/yarn/YarnClientUtil.java b/chunjun-server/src/main/java/com/dtstack/chunjun/yarn/YarnClientUtil.java new file mode 100644 index 0000000000..f1142a6148 --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/yarn/YarnClientUtil.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.dtstack.chunjun.yarn; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.yarn.client.api.YarnClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author xuchao + * @date 2023-06-27 + */ +public class YarnClientUtil { + + private static final Logger LOG = LoggerFactory.getLogger(YarnClientUtil.class); + + public static YarnClient buildYarnClient(Configuration yarnConf) { + long startTime = System.currentTimeMillis(); + YarnClient yarnClient; + try { + LOG.info("build yarn client."); + yarnClient = YarnClient.createYarnClient(); + yarnClient.init(yarnConf); + yarnClient.start(); + long endTime = System.currentTimeMillis(); + LOG.info("build yarn client success. cost {} ms.", endTime - startTime); + } catch (Throwable e) { + LOG.error("build yarn client error.", e); + throw new RuntimeException(e); + } + + return yarnClient; + } +} diff --git a/chunjun-server/src/main/java/com/dtstack/chunjun/yarn/YarnConfLoader.java b/chunjun-server/src/main/java/com/dtstack/chunjun/yarn/YarnConfLoader.java new file mode 100644 index 0000000000..2d6685b81a --- /dev/null +++ b/chunjun-server/src/main/java/com/dtstack/chunjun/yarn/YarnConfLoader.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.dtstack.chunjun.yarn; + +import com.dtstack.chunjun.constants.ConstantValue; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.yarn.conf.YarnConfiguration; + +import java.io.File; +import java.util.Map; + +/** load yarn conf from specify dir */ +public class YarnConfLoader { + + public static YarnConfiguration loadConf(String yarnConfDir) { + YarnConfiguration yarnConf = new YarnConfiguration(); + try { + + File dir = new File(yarnConfDir); + if (dir.exists() && dir.isDirectory()) { + + File[] xmlFileList = + new File(yarnConfDir) + .listFiles( + (dir1, name) -> + name.endsWith(ConstantValue.FILE_SUFFIX_XML)); + + if (xmlFileList != null) { + for (File xmlFile : xmlFileList) { + yarnConf.addResource(xmlFile.toURI().toURL()); + } + } + } + + } catch (Exception e) { + throw new RuntimeException(e); + } + + setHaYarnConf(yarnConf); + return yarnConf; + } + + /** deal yarn HA conf */ + private static void setHaYarnConf(Configuration yarnConf) { + for (Map.Entry entry : yarnConf) { + String key = entry.getKey(); + String value = entry.getValue(); + if (key.startsWith("yarn.resourcemanager.hostname.")) { + String rm = key.substring("yarn.resourcemanager.hostname.".length()); + String addressKey = "yarn.resourcemanager.address." + rm; + if (yarnConf.get(addressKey) == null) { + yarnConf.set(addressKey, value + ":" + YarnConfiguration.DEFAULT_RM_PORT); + } + } + } + } +} diff --git a/chunjun-server/src/main/java/org/apache/flink/yarn/CJYarnClusterClientFactory.java b/chunjun-server/src/main/java/org/apache/flink/yarn/CJYarnClusterClientFactory.java new file mode 100644 index 0000000000..f2856e516e --- /dev/null +++ b/chunjun-server/src/main/java/org/apache/flink/yarn/CJYarnClusterClientFactory.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.flink.yarn; + +import org.apache.flink.configuration.Configuration; +import org.apache.flink.configuration.DeploymentOptionsInternal; +import org.apache.flink.yarn.configuration.YarnLogConfigUtil; + +import org.apache.hadoop.yarn.client.api.YarnClient; +import org.apache.hadoop.yarn.conf.YarnConfiguration; + +import static org.apache.flink.util.Preconditions.checkNotNull; + +/** + * 调整org.apache.flink.yarn.YarnClusterClientFactory.getClusterDescriptor 逻辑添加对yarnConf 的设定 flink + * 源码中客户端构建yarnConf 依赖bin + * 脚本里的环境变量的设定INTERNAL_HADOOP_CLASSPATHS="${HADOOP_CLASSPATH}:${HADOOP_CONF_DIR}:${YARN_CONF_DIR}" + * 然后基于classloader.getResource("yarn-site.xml") 来构建资源配置 + * + * @author xuchao + * @date 2023-07-13 + */ +public class CJYarnClusterClientFactory extends YarnClusterClientFactory { + + public YarnClusterDescriptor createClusterDescriptor( + Configuration configuration, YarnConfiguration yarnConfiguration) { + checkNotNull(configuration); + + final String configurationDirectory = configuration.get(DeploymentOptionsInternal.CONF_DIR); + YarnLogConfigUtil.setLogConfigFileInConfig(configuration, configurationDirectory); + + return getClusterDescriptor(configuration, yarnConfiguration); + } + + private YarnClusterDescriptor getClusterDescriptor( + Configuration configuration, YarnConfiguration yarnConfiguration) { + final YarnClient yarnClient = YarnClient.createYarnClient(); + + yarnClient.init(yarnConfiguration); + yarnClient.start(); + + return new YarnClusterDescriptor( + configuration, + yarnConfiguration, + yarnClient, + YarnClientYarnClusterInformationRetriever.create(yarnClient), + false); + } +} diff --git a/chunjun-server/src/main/java/org/apache/flink/yarn/CJYarnClusterDescriptor.java b/chunjun-server/src/main/java/org/apache/flink/yarn/CJYarnClusterDescriptor.java new file mode 100644 index 0000000000..bf228251c7 --- /dev/null +++ b/chunjun-server/src/main/java/org/apache/flink/yarn/CJYarnClusterDescriptor.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.flink.yarn; + +import com.dtstack.chunjun.config.ChunJunServerOptions; + +import org.apache.flink.client.deployment.ClusterSpecification; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.runtime.jobgraph.JobGraph; +import org.apache.flink.yarn.configuration.YarnConfigOptions; + +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hdfs.DFSConfigKeys; +import org.apache.hadoop.yarn.api.records.ApplicationReport; +import org.apache.hadoop.yarn.api.records.ApplicationSubmissionContext; +import org.apache.hadoop.yarn.api.records.LocalResourceType; +import org.apache.hadoop.yarn.client.api.YarnClient; +import org.apache.hadoop.yarn.client.api.YarnClientApplication; +import org.apache.hadoop.yarn.conf.YarnConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 扩展YarnClusterDescriptor 提供ChunJun 包上传功能 + * + * @author xuchao + * @date 2023-09-13 + */ +public class CJYarnClusterDescriptor extends YarnClusterDescriptor { + + private static final Logger LOG = LoggerFactory.getLogger(CJYarnClusterDescriptor.class); + + private final YarnConfiguration yarnConfiguration; + + private final Configuration flinkConfiguration; + + public CJYarnClusterDescriptor( + Configuration flinkConfiguration, + YarnConfiguration yarnConfiguration, + YarnClient yarnClient, + YarnClusterInformationRetriever yarnClusterInformationRetriever, + boolean sharedYarnClient) { + super( + flinkConfiguration, + yarnConfiguration, + yarnClient, + yarnClusterInformationRetriever, + sharedYarnClient); + this.flinkConfiguration = flinkConfiguration; + this.yarnConfiguration = yarnConfiguration; + } + + @Override + public ApplicationReport startAppMaster( + Configuration configuration, + String applicationName, + String yarnClusterEntrypoint, + JobGraph jobGraph, + YarnClient yarnClient, + YarnClientApplication yarnApplication, + ClusterSpecification clusterSpecification) + throws Exception { + // 只上传ChunJun 包到hdfs 作为资源目录,不添加到 classpath ,防止chunjun 扩展对connector 对 flink + // 包的影响,比如guava版本不一致,导致类冲突 + // 只在启动session 的情况下主动上传ChunJun 包 + if (jobGraph == null) { + LOG.info("start to upload chunjun dir."); + uploadCJDir(configuration, yarnApplication); + LOG.info("upload chunjun dir end."); + } + return super.startAppMaster( + configuration, + applicationName, + yarnClusterEntrypoint, + jobGraph, + yarnClient, + yarnApplication, + clusterSpecification); + } + + public void uploadCJDir(Configuration configuration, YarnClientApplication yarnApplication) + throws Exception { + FileSystem fs = FileSystem.get(yarnConfiguration); + + ApplicationSubmissionContext appContext = yarnApplication.getApplicationSubmissionContext(); + Path stagingDirPath = getStagingDir(fs); + FileSystem stagingDirFs = stagingDirPath.getFileSystem(yarnConfiguration); + final List providedLibDirs = + Utils.getQualifiedRemoteProvidedLibDirs(configuration, yarnConfiguration); + + YarnApplicationFileUploader fileUploader = + YarnApplicationFileUploader.from( + stagingDirFs, + stagingDirPath, + providedLibDirs, + appContext.getApplicationId(), + getFileReplication()); + + String path = configuration.get(ChunJunServerOptions.CHUNJUN_DIST_PATH); + Path chunjunDistPath = new Path(path); + Collection shipFiles = new ArrayList<>(); + shipFiles.add(chunjunDistPath); + shipFiles = + shipFiles.stream() + .filter(filePath -> !filePath.toString().contains("/server/")) + .collect(Collectors.toList()); + + fileUploader.registerMultipleLocalResources( + shipFiles, Path.CUR_DIR, LocalResourceType.FILE); + } + + private int getFileReplication() { + final int yarnFileReplication = + yarnConfiguration.getInt( + DFSConfigKeys.DFS_REPLICATION_KEY, DFSConfigKeys.DFS_REPLICATION_DEFAULT); + final int fileReplication = + flinkConfiguration.getInteger(YarnConfigOptions.FILE_REPLICATION); + return fileReplication > 0 ? fileReplication : yarnFileReplication; + } +} diff --git a/chunjun-server/src/main/java/org/apache/flink/yarn/YarnClusterDescriptor.java b/chunjun-server/src/main/java/org/apache/flink/yarn/YarnClusterDescriptor.java new file mode 100644 index 0000000000..efa63ddf72 --- /dev/null +++ b/chunjun-server/src/main/java/org/apache/flink/yarn/YarnClusterDescriptor.java @@ -0,0 +1,1984 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.flink.yarn; + +import com.dtstack.chunjun.config.ChunJunServerOptions; + +import org.apache.flink.annotation.VisibleForTesting; +import org.apache.flink.api.common.cache.DistributedCache; +import org.apache.flink.api.java.tuple.Tuple2; +import org.apache.flink.client.deployment.ClusterDeploymentException; +import org.apache.flink.client.deployment.ClusterDescriptor; +import org.apache.flink.client.deployment.ClusterRetrieveException; +import org.apache.flink.client.deployment.ClusterSpecification; +import org.apache.flink.client.deployment.application.ApplicationConfiguration; +import org.apache.flink.client.program.ClusterClientProvider; +import org.apache.flink.client.program.PackagedProgramUtils; +import org.apache.flink.client.program.rest.RestClusterClient; +import org.apache.flink.configuration.ConfigConstants; +import org.apache.flink.configuration.ConfigOption; +import org.apache.flink.configuration.ConfigUtils; +import org.apache.flink.configuration.Configuration; +import org.apache.flink.configuration.ConfigurationUtils; +import org.apache.flink.configuration.CoreOptions; +import org.apache.flink.configuration.HighAvailabilityOptions; +import org.apache.flink.configuration.IllegalConfigurationException; +import org.apache.flink.configuration.JobManagerOptions; +import org.apache.flink.configuration.PipelineOptions; +import org.apache.flink.configuration.ResourceManagerOptions; +import org.apache.flink.configuration.RestOptions; +import org.apache.flink.configuration.SecurityOptions; +import org.apache.flink.configuration.TaskManagerOptions; +import org.apache.flink.core.plugin.PluginConfig; +import org.apache.flink.core.plugin.PluginUtils; +import org.apache.flink.runtime.clusterframework.BootstrapTools; +import org.apache.flink.runtime.entrypoint.ClusterEntrypoint; +import org.apache.flink.runtime.entrypoint.ClusterEntrypointUtils; +import org.apache.flink.runtime.jobgraph.JobGraph; +import org.apache.flink.runtime.jobmanager.HighAvailabilityMode; +import org.apache.flink.runtime.jobmanager.JobManagerProcessSpec; +import org.apache.flink.runtime.jobmanager.JobManagerProcessUtils; +import org.apache.flink.runtime.security.token.DelegationTokenConverter; +import org.apache.flink.runtime.security.token.DelegationTokenManager; +import org.apache.flink.runtime.security.token.KerberosDelegationTokenManager; +import org.apache.flink.runtime.util.HadoopUtils; +import org.apache.flink.util.CollectionUtil; +import org.apache.flink.util.FlinkException; +import org.apache.flink.util.Preconditions; +import org.apache.flink.util.ShutdownHookUtil; +import org.apache.flink.util.StringUtils; +import org.apache.flink.yarn.configuration.YarnConfigOptions; +import org.apache.flink.yarn.configuration.YarnConfigOptionsInternal; +import org.apache.flink.yarn.configuration.YarnDeploymentTarget; +import org.apache.flink.yarn.configuration.YarnLogConfigUtil; +import org.apache.flink.yarn.entrypoint.YarnApplicationClusterEntryPoint; +import org.apache.flink.yarn.entrypoint.YarnJobClusterEntrypoint; +import org.apache.flink.yarn.entrypoint.YarnSessionClusterEntrypoint; + +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hdfs.DFSConfigKeys; +import org.apache.hadoop.security.Credentials; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.yarn.api.ApplicationConstants; +import org.apache.hadoop.yarn.api.protocolrecords.GetNewApplicationResponse; +import org.apache.hadoop.yarn.api.records.ApplicationId; +import org.apache.hadoop.yarn.api.records.ApplicationReport; +import org.apache.hadoop.yarn.api.records.ApplicationSubmissionContext; +import org.apache.hadoop.yarn.api.records.ContainerLaunchContext; +import org.apache.hadoop.yarn.api.records.FinalApplicationStatus; +import org.apache.hadoop.yarn.api.records.LocalResourceType; +import org.apache.hadoop.yarn.api.records.NodeReport; +import org.apache.hadoop.yarn.api.records.NodeState; +import org.apache.hadoop.yarn.api.records.Priority; +import org.apache.hadoop.yarn.api.records.QueueInfo; +import org.apache.hadoop.yarn.api.records.Resource; +import org.apache.hadoop.yarn.api.records.YarnApplicationState; +import org.apache.hadoop.yarn.api.records.YarnClusterMetrics; +import org.apache.hadoop.yarn.client.api.YarnClient; +import org.apache.hadoop.yarn.client.api.YarnClientApplication; +import org.apache.hadoop.yarn.conf.YarnConfiguration; +import org.apache.hadoop.yarn.exceptions.YarnException; +import org.apache.hadoop.yarn.util.ConverterUtils; +import org.apache.hadoop.yarn.util.Records; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URLDecoder; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.apache.flink.client.deployment.application.ApplicationConfiguration.APPLICATION_MAIN_CLASS; +import static org.apache.flink.configuration.ConfigConstants.DEFAULT_FLINK_USR_LIB_DIR; +import static org.apache.flink.configuration.ConfigConstants.ENV_FLINK_LIB_DIR; +import static org.apache.flink.configuration.ConfigConstants.ENV_FLINK_OPT_DIR; +import static org.apache.flink.runtime.entrypoint.component.FileJobGraphRetriever.JOB_GRAPH_FILE_PATH; +import static org.apache.flink.util.Preconditions.checkArgument; +import static org.apache.flink.util.Preconditions.checkNotNull; +import static org.apache.flink.yarn.YarnConfigKeys.ENV_FLINK_CLASSPATH; +import static org.apache.flink.yarn.YarnConfigKeys.LOCAL_RESOURCE_DESCRIPTOR_SEPARATOR; + +/** + * The descriptor with deployment information for deploying a Flink cluster on Yarn. 修改 + * 方法startAppMaster 从 private --> public 逻辑变更放到扩展类里面 + */ +public class YarnClusterDescriptor implements ClusterDescriptor { + private static final Logger LOG = LoggerFactory.getLogger(YarnClusterDescriptor.class); + + private final YarnConfiguration yarnConfiguration; + + private final YarnClient yarnClient; + + private final YarnClusterInformationRetriever yarnClusterInformationRetriever; + + /** True if the descriptor must not shut down the YarnClient. */ + private final boolean sharedYarnClient; + + /** Lazily initialized list of files to ship. */ + private final List shipFiles = new LinkedList<>(); + + private final List shipArchives = new LinkedList<>(); + + private final String yarnQueue; + + private Path flinkJarPath; + + private final Configuration flinkConfiguration; + + private final String customName; + + private final String nodeLabel; + + private final String applicationType; + + private YarnConfigOptions.UserJarInclusion userJarInclusion; + + public YarnClusterDescriptor( + Configuration flinkConfiguration, + YarnConfiguration yarnConfiguration, + YarnClient yarnClient, + YarnClusterInformationRetriever yarnClusterInformationRetriever, + boolean sharedYarnClient) { + + this.yarnConfiguration = Preconditions.checkNotNull(yarnConfiguration); + this.yarnClient = Preconditions.checkNotNull(yarnClient); + this.yarnClusterInformationRetriever = + Preconditions.checkNotNull(yarnClusterInformationRetriever); + this.sharedYarnClient = sharedYarnClient; + + this.flinkConfiguration = Preconditions.checkNotNull(flinkConfiguration); + this.userJarInclusion = getUserJarInclusionMode(flinkConfiguration); + + getLocalFlinkDistPath(flinkConfiguration).ifPresent(this::setLocalJarPath); + decodeFilesToShipToCluster(flinkConfiguration, YarnConfigOptions.SHIP_FILES) + .ifPresent(this::addShipFiles); + decodeFilesToShipToCluster(flinkConfiguration, YarnConfigOptions.SHIP_ARCHIVES) + .ifPresent(this::addShipArchives); + + this.yarnQueue = flinkConfiguration.getString(YarnConfigOptions.APPLICATION_QUEUE); + this.customName = flinkConfiguration.getString(YarnConfigOptions.APPLICATION_NAME); + this.applicationType = flinkConfiguration.getString(YarnConfigOptions.APPLICATION_TYPE); + this.nodeLabel = flinkConfiguration.getString(YarnConfigOptions.NODE_LABEL); + } + + private Optional> decodeFilesToShipToCluster( + final Configuration configuration, final ConfigOption> configOption) { + checkNotNull(configuration); + checkNotNull(configOption); + + final List files = + ConfigUtils.decodeListFromConfig(configuration, configOption, File::new); + return files.isEmpty() ? Optional.empty() : Optional.of(files); + } + + private Optional getLocalFlinkDistPath(final Configuration configuration) { + final String localJarPath = configuration.getString(YarnConfigOptions.FLINK_DIST_JAR); + if (localJarPath != null) { + return Optional.of(new Path(localJarPath)); + } + + LOG.info( + "No path for the flink jar passed. Using the location of " + + getClass() + + " to locate the jar"); + + // check whether it's actually a jar file --> when testing we execute this class without a + // flink-dist jar + final String decodedPath = getDecodedJarPath(); + return decodedPath.endsWith(".jar") + ? Optional.of(new Path(new File(decodedPath).toURI())) + : Optional.empty(); + } + + private String getDecodedJarPath() { + final String encodedJarPath = + getClass().getProtectionDomain().getCodeSource().getLocation().getPath(); + try { + return URLDecoder.decode(encodedJarPath, Charset.defaultCharset().name()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException( + "Couldn't decode the encoded Flink dist jar path: " + + encodedJarPath + + " You can supply a path manually via the command line."); + } + } + + @VisibleForTesting + List getShipFiles() { + return shipFiles; + } + + public YarnClient getYarnClient() { + return yarnClient; + } + + /** + * The class to start the application master with. This class runs the main method in case of + * session cluster. + */ + protected String getYarnSessionClusterEntrypoint() { + return YarnSessionClusterEntrypoint.class.getName(); + } + + /** + * The class to start the application master with. This class runs the main method in case of + * the job cluster. + */ + protected String getYarnJobClusterEntrypoint() { + return YarnJobClusterEntrypoint.class.getName(); + } + + public Configuration getFlinkConfiguration() { + return flinkConfiguration; + } + + public void setLocalJarPath(Path localJarPath) { + if (!localJarPath.toString().endsWith("jar")) { + throw new IllegalArgumentException( + "The passed jar path ('" + + localJarPath + + "') does not end with the 'jar' extension"); + } + this.flinkJarPath = localJarPath; + } + + /** + * Adds the given files to the list of files to ship. + * + *

Note that any file matching "flink-dist*.jar" will be excluded from the upload by + * {@link YarnApplicationFileUploader#registerMultipleLocalResources(Collection, String, + * LocalResourceType)} since we upload the Flink uber jar ourselves and do not need to deploy it + * multiple times. + * + * @param shipFiles files to ship + */ + public void addShipFiles(List shipFiles) { + checkArgument( + !isUsrLibDirIncludedInShipFiles(shipFiles), + "User-shipped directories configured via : %s should not include %s.", + YarnConfigOptions.SHIP_FILES.key(), + ConfigConstants.DEFAULT_FLINK_USR_LIB_DIR); + this.shipFiles.addAll(shipFiles); + } + + private void addShipArchives(List shipArchives) { + checkArgument( + isArchiveOnlyIncludedInShipArchiveFiles(shipArchives), + "Non-archive files are included."); + this.shipArchives.addAll(shipArchives); + } + + private static boolean isArchiveOnlyIncludedInShipArchiveFiles(List shipFiles) { + return shipFiles.stream() + .filter(File::isFile) + .map(File::getName) + .map(String::toLowerCase) + .allMatch( + name -> + name.endsWith(".tar.gz") + || name.endsWith(".tar") + || name.endsWith(".tgz") + || name.endsWith(".dst") + || name.endsWith(".jar") + || name.endsWith(".zip")); + } + + private void isReadyForDeployment(ClusterSpecification clusterSpecification) throws Exception { + + if (this.flinkJarPath == null) { + throw new YarnDeploymentException("The Flink jar path is null"); + } + if (this.flinkConfiguration == null) { + throw new YarnDeploymentException("Flink configuration object has not been set"); + } + + // Check if we don't exceed YARN's maximum virtual cores. + final int numYarnMaxVcores = yarnClusterInformationRetriever.getMaxVcores(); + + int configuredAmVcores = flinkConfiguration.getInteger(YarnConfigOptions.APP_MASTER_VCORES); + if (configuredAmVcores > numYarnMaxVcores) { + throw new IllegalConfigurationException( + String.format( + "The number of requested virtual cores for application master %d" + + " exceeds the maximum number of virtual cores %d available in the Yarn Cluster.", + configuredAmVcores, numYarnMaxVcores)); + } + + int configuredVcores = + flinkConfiguration.getInteger( + YarnConfigOptions.VCORES, clusterSpecification.getSlotsPerTaskManager()); + // don't configure more than the maximum configured number of vcores + if (configuredVcores > numYarnMaxVcores) { + throw new IllegalConfigurationException( + String.format( + "The number of requested virtual cores per node %d" + + " exceeds the maximum number of virtual cores %d available in the Yarn Cluster." + + " Please note that the number of virtual cores is set to the number of task slots by default" + + " unless configured in the Flink config with '%s.'", + configuredVcores, numYarnMaxVcores, YarnConfigOptions.VCORES.key())); + } + + // check if required Hadoop environment variables are set. If not, warn user + if (System.getenv("HADOOP_CONF_DIR") == null && System.getenv("YARN_CONF_DIR") == null) { + LOG.warn( + "Neither the HADOOP_CONF_DIR nor the YARN_CONF_DIR environment variable is set. " + + "The Flink YARN Client needs one of these to be set to properly load the Hadoop " + + "configuration for accessing YARN."); + } + } + + public String getNodeLabel() { + return nodeLabel; + } + + // ------------------------------------------------------------- + // Lifecycle management + // ------------------------------------------------------------- + + @Override + public void close() { + if (!sharedYarnClient) { + yarnClient.stop(); + } + } + + // ------------------------------------------------------------- + // ClusterClient overrides + // ------------------------------------------------------------- + + @Override + public ClusterClientProvider retrieve(ApplicationId applicationId) + throws ClusterRetrieveException { + + try { + // check if required Hadoop environment variables are set. If not, warn user + if (System.getenv("HADOOP_CONF_DIR") == null + && System.getenv("YARN_CONF_DIR") == null) { + LOG.warn( + "Neither the HADOOP_CONF_DIR nor the YARN_CONF_DIR environment variable is set." + + "The Flink YARN Client needs one of these to be set to properly load the Hadoop " + + "configuration for accessing YARN."); + } + + final ApplicationReport report = yarnClient.getApplicationReport(applicationId); + + if (report.getFinalApplicationStatus() != FinalApplicationStatus.UNDEFINED) { + // Flink cluster is not running anymore + LOG.error( + "The application {} doesn't run anymore. It has previously completed with final status: {}", + applicationId, + report.getFinalApplicationStatus()); + throw new RuntimeException( + "The Yarn application " + applicationId + " doesn't run anymore."); + } + + setClusterEntrypointInfoToConfig(report); + + return () -> { + try { + return new RestClusterClient<>(flinkConfiguration, report.getApplicationId()); + } catch (Exception e) { + throw new RuntimeException("Couldn't retrieve Yarn cluster", e); + } + }; + } catch (Exception e) { + throw new ClusterRetrieveException("Couldn't retrieve Yarn cluster", e); + } + } + + @Override + public ClusterClientProvider deploySessionCluster( + ClusterSpecification clusterSpecification) throws ClusterDeploymentException { + try { + return deployInternal( + clusterSpecification, + "Flink session cluster", + getYarnSessionClusterEntrypoint(), + null, + false); + } catch (Exception e) { + throw new ClusterDeploymentException("Couldn't deploy Yarn session cluster", e); + } + } + + @Override + public ClusterClientProvider deployApplicationCluster( + final ClusterSpecification clusterSpecification, + final ApplicationConfiguration applicationConfiguration) + throws ClusterDeploymentException { + checkNotNull(clusterSpecification); + checkNotNull(applicationConfiguration); + + final YarnDeploymentTarget deploymentTarget = + YarnDeploymentTarget.fromConfig(flinkConfiguration); + if (YarnDeploymentTarget.APPLICATION != deploymentTarget) { + throw new ClusterDeploymentException( + "Couldn't deploy Yarn Application Cluster." + + " Expected deployment.target=" + + YarnDeploymentTarget.APPLICATION.getName() + + " but actual one was \"" + + deploymentTarget.getName() + + "\""); + } + + applicationConfiguration.applyToConfiguration(flinkConfiguration); + + // No need to do pipelineJars validation if it is a PyFlink job. + if (!(PackagedProgramUtils.isPython(applicationConfiguration.getApplicationClassName()) + || PackagedProgramUtils.isPython(applicationConfiguration.getProgramArguments()))) { + final List pipelineJars = + flinkConfiguration + .getOptional(PipelineOptions.JARS) + .orElse(Collections.emptyList()); + Preconditions.checkArgument(pipelineJars.size() == 1, "Should only have one jar"); + } + + try { + return deployInternal( + clusterSpecification, + "Flink Application Cluster", + YarnApplicationClusterEntryPoint.class.getName(), + null, + false); + } catch (Exception e) { + throw new ClusterDeploymentException("Couldn't deploy Yarn Application Cluster", e); + } + } + + @Override + public ClusterClientProvider deployJobCluster( + ClusterSpecification clusterSpecification, JobGraph jobGraph, boolean detached) + throws ClusterDeploymentException { + + LOG.warn( + "Job Clusters are deprecated since Flink 1.15. Please use an Application Cluster/Application Mode instead."); + try { + return deployInternal( + clusterSpecification, + "Flink per-job cluster", + getYarnJobClusterEntrypoint(), + jobGraph, + detached); + } catch (Exception e) { + throw new ClusterDeploymentException("Could not deploy Yarn job cluster.", e); + } + } + + @Override + public void killCluster(ApplicationId applicationId) throws FlinkException { + try { + yarnClient.killApplication(applicationId); + + try (final FileSystem fs = FileSystem.get(yarnConfiguration)) { + final Path applicationDir = + YarnApplicationFileUploader.getApplicationDirPath( + getStagingDir(fs), applicationId); + + Utils.deleteApplicationFiles(applicationDir.toUri().toString()); + } + + } catch (YarnException | IOException e) { + throw new FlinkException( + "Could not kill the Yarn Flink cluster with id " + applicationId + '.', e); + } + } + + /** + * This method will block until the ApplicationMaster/JobManager have been deployed on YARN. + * + * @param clusterSpecification Initial cluster specification for the Flink cluster to be + * deployed + * @param applicationName name of the Yarn application to start + * @param yarnClusterEntrypoint Class name of the Yarn cluster entry point. + * @param jobGraph A job graph which is deployed with the Flink cluster, {@code null} if none + * @param detached True if the cluster should be started in detached mode + */ + private ClusterClientProvider deployInternal( + ClusterSpecification clusterSpecification, + String applicationName, + String yarnClusterEntrypoint, + @Nullable JobGraph jobGraph, + boolean detached) + throws Exception { + + final UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); + if (HadoopUtils.isKerberosSecurityEnabled(currentUser)) { + boolean useTicketCache = + flinkConfiguration.getBoolean(SecurityOptions.KERBEROS_LOGIN_USETICKETCACHE); + + if (!HadoopUtils.areKerberosCredentialsValid(currentUser, useTicketCache)) { + throw new RuntimeException( + "Hadoop security with Kerberos is enabled but the login user " + + "does not have Kerberos credentials or delegation tokens!"); + } + + final boolean fetchToken = + flinkConfiguration.getBoolean(SecurityOptions.KERBEROS_FETCH_DELEGATION_TOKEN); + final boolean yarnAccessFSEnabled = + !CollectionUtil.isNullOrEmpty( + flinkConfiguration.get( + SecurityOptions.KERBEROS_HADOOP_FILESYSTEMS_TO_ACCESS)); + if (!fetchToken && yarnAccessFSEnabled) { + throw new IllegalConfigurationException( + String.format( + "When %s is disabled, %s must be disabled as well.", + SecurityOptions.KERBEROS_FETCH_DELEGATION_TOKEN.key(), + SecurityOptions.KERBEROS_HADOOP_FILESYSTEMS_TO_ACCESS.key())); + } + } + + isReadyForDeployment(clusterSpecification); + + // ------------------ Check if the specified queue exists -------------------- + + checkYarnQueues(yarnClient); + + // ------------------ Check if the YARN ClusterClient has the requested resources + // -------------- + + // Create application via yarnClient + final YarnClientApplication yarnApplication = yarnClient.createApplication(); + final GetNewApplicationResponse appResponse = yarnApplication.getNewApplicationResponse(); + + Resource maxRes = appResponse.getMaximumResourceCapability(); + + final ClusterResourceDescription freeClusterMem; + try { + freeClusterMem = getCurrentFreeClusterResources(yarnClient); + } catch (YarnException | IOException e) { + failSessionDuringDeployment(yarnClient, yarnApplication); + throw new YarnDeploymentException( + "Could not retrieve information about free cluster resources.", e); + } + + final int yarnMinAllocationMB = + yarnConfiguration.getInt( + YarnConfiguration.RM_SCHEDULER_MINIMUM_ALLOCATION_MB, + YarnConfiguration.DEFAULT_RM_SCHEDULER_MINIMUM_ALLOCATION_MB); + if (yarnMinAllocationMB <= 0) { + throw new YarnDeploymentException( + "The minimum allocation memory " + + "(" + + yarnMinAllocationMB + + " MB) configured via '" + + YarnConfiguration.RM_SCHEDULER_MINIMUM_ALLOCATION_MB + + "' should be greater than 0."); + } + + final ClusterSpecification validClusterSpecification; + try { + validClusterSpecification = + validateClusterResources( + clusterSpecification, yarnMinAllocationMB, maxRes, freeClusterMem); + } catch (YarnDeploymentException yde) { + failSessionDuringDeployment(yarnClient, yarnApplication); + throw yde; + } + + LOG.info("Cluster specification: {}", validClusterSpecification); + + final ClusterEntrypoint.ExecutionMode executionMode = + detached + ? ClusterEntrypoint.ExecutionMode.DETACHED + : ClusterEntrypoint.ExecutionMode.NORMAL; + + flinkConfiguration.setString( + ClusterEntrypoint.INTERNAL_CLUSTER_EXECUTION_MODE, executionMode.toString()); + + ApplicationReport report = + startAppMaster( + flinkConfiguration, + applicationName, + yarnClusterEntrypoint, + jobGraph, + yarnClient, + yarnApplication, + validClusterSpecification); + + // print the application id for user to cancel themselves. + if (detached) { + final ApplicationId yarnApplicationId = report.getApplicationId(); + logDetachedClusterInformation(yarnApplicationId, LOG); + } + + setClusterEntrypointInfoToConfig(report); + + return () -> { + try { + return new RestClusterClient<>(flinkConfiguration, report.getApplicationId()); + } catch (Exception e) { + throw new RuntimeException("Error while creating RestClusterClient.", e); + } + }; + } + + private ClusterSpecification validateClusterResources( + ClusterSpecification clusterSpecification, + int yarnMinAllocationMB, + Resource maximumResourceCapability, + ClusterResourceDescription freeClusterResources) + throws YarnDeploymentException { + + int jobManagerMemoryMb = clusterSpecification.getMasterMemoryMB(); + final int taskManagerMemoryMb = clusterSpecification.getTaskManagerMemoryMB(); + + logIfComponentMemNotIntegerMultipleOfYarnMinAllocation( + "JobManager", jobManagerMemoryMb, yarnMinAllocationMB); + logIfComponentMemNotIntegerMultipleOfYarnMinAllocation( + "TaskManager", taskManagerMemoryMb, yarnMinAllocationMB); + + // set the memory to minAllocationMB to do the next checks correctly + if (jobManagerMemoryMb < yarnMinAllocationMB) { + jobManagerMemoryMb = yarnMinAllocationMB; + } + + final String note = + "Please check the 'yarn.scheduler.maximum-allocation-mb' and the 'yarn.nodemanager.resource.memory-mb' configuration values\n"; + if (jobManagerMemoryMb > maximumResourceCapability.getMemory()) { + throw new YarnDeploymentException( + "The cluster does not have the requested resources for the JobManager available!\n" + + "Maximum Memory: " + + maximumResourceCapability.getMemory() + + "MB Requested: " + + jobManagerMemoryMb + + "MB. " + + note); + } + + if (taskManagerMemoryMb > maximumResourceCapability.getMemory()) { + throw new YarnDeploymentException( + "The cluster does not have the requested resources for the TaskManagers available!\n" + + "Maximum Memory: " + + maximumResourceCapability.getMemory() + + " Requested: " + + taskManagerMemoryMb + + "MB. " + + note); + } + + final String noteRsc = + "\nThe Flink YARN client will try to allocate the YARN session, but maybe not all TaskManagers are " + + "connecting from the beginning because the resources are currently not available in the cluster. " + + "The allocation might take more time than usual because the Flink YARN client needs to wait until " + + "the resources become available."; + + if (taskManagerMemoryMb > freeClusterResources.containerLimit) { + LOG.warn( + "The requested amount of memory for the TaskManagers (" + + taskManagerMemoryMb + + "MB) is more than " + + "the largest possible YARN container: " + + freeClusterResources.containerLimit + + noteRsc); + } + if (jobManagerMemoryMb > freeClusterResources.containerLimit) { + LOG.warn( + "The requested amount of memory for the JobManager (" + + jobManagerMemoryMb + + "MB) is more than " + + "the largest possible YARN container: " + + freeClusterResources.containerLimit + + noteRsc); + } + + return new ClusterSpecification.ClusterSpecificationBuilder() + .setMasterMemoryMB(jobManagerMemoryMb) + .setTaskManagerMemoryMB(taskManagerMemoryMb) + .setSlotsPerTaskManager(clusterSpecification.getSlotsPerTaskManager()) + .createClusterSpecification(); + } + + private void logIfComponentMemNotIntegerMultipleOfYarnMinAllocation( + String componentName, int componentMemoryMB, int yarnMinAllocationMB) { + int normalizedMemMB = + (componentMemoryMB + (yarnMinAllocationMB - 1)) + / yarnMinAllocationMB + * yarnMinAllocationMB; + if (normalizedMemMB <= 0) { + normalizedMemMB = yarnMinAllocationMB; + } + if (componentMemoryMB != normalizedMemMB) { + LOG.info( + "The configured {} memory is {} MB. YARN will allocate {} MB to make up an integer multiple of its " + + "minimum allocation memory ({} MB, configured via 'yarn.scheduler.minimum-allocation-mb'). The extra {} MB " + + "may not be used by Flink.", + componentName, + componentMemoryMB, + normalizedMemMB, + yarnMinAllocationMB, + normalizedMemMB - componentMemoryMB); + } + } + + private void checkYarnQueues(YarnClient yarnClient) { + try { + List queues = yarnClient.getAllQueues(); + if (queues.size() > 0 + && this.yarnQueue + != null) { // check only if there are queues configured in yarn and for + // this session. + boolean queueFound = false; + for (QueueInfo queue : queues) { + if (queue.getQueueName().equals(this.yarnQueue) + || queue.getQueueName().equals("root." + this.yarnQueue)) { + queueFound = true; + break; + } + } + if (!queueFound) { + String queueNames = StringUtils.toQuotedListString(queues.toArray()); + LOG.warn( + "The specified queue '" + + this.yarnQueue + + "' does not exist. " + + "Available queues: " + + queueNames); + } + } else { + LOG.debug("The YARN cluster does not have any queues configured"); + } + } catch (Throwable e) { + LOG.warn("Error while getting queue information from YARN: " + e.getMessage()); + if (LOG.isDebugEnabled()) { + LOG.debug("Error details", e); + } + } + } + + /** 只修改 方法 从 private --> public 逻辑变更放到扩展类里面 */ + public ApplicationReport startAppMaster( + Configuration configuration, + String applicationName, + String yarnClusterEntrypoint, + JobGraph jobGraph, + YarnClient yarnClient, + YarnClientApplication yarnApplication, + ClusterSpecification clusterSpecification) + throws Exception { + + // ------------------ Initialize the file systems ------------------------- + + org.apache.flink.core.fs.FileSystem.initialize( + configuration, PluginUtils.createPluginManagerFromRootFolder(configuration)); + + final FileSystem fs = FileSystem.get(yarnConfiguration); + + // hard coded check for the GoogleHDFS client because its not overriding the getScheme() + // method. + if (!fs.getClass().getSimpleName().equals("GoogleHadoopFileSystem") + && fs.getScheme().startsWith("file")) { + LOG.warn( + "The file system scheme is '" + + fs.getScheme() + + "'. This indicates that the " + + "specified Hadoop configuration path is wrong and the system is using the default Hadoop configuration values." + + "The Flink YARN client needs to store its files in a distributed file system"); + } + + ApplicationSubmissionContext appContext = yarnApplication.getApplicationSubmissionContext(); + + final List providedLibDirs = + Utils.getQualifiedRemoteProvidedLibDirs(configuration, yarnConfiguration); + + final Optional providedUsrLibDir = + Utils.getQualifiedRemoteProvidedUsrLib(configuration, yarnConfiguration); + + Path stagingDirPath = getStagingDir(fs); + FileSystem stagingDirFs = stagingDirPath.getFileSystem(yarnConfiguration); + final YarnApplicationFileUploader fileUploader = + YarnApplicationFileUploader.from( + stagingDirFs, + stagingDirPath, + providedLibDirs, + appContext.getApplicationId(), + getFileReplication()); + + String cjDistPathStr = configuration.get(ChunJunServerOptions.CHUNJUN_DIST_PATH); + Path cjDistPath = new Path(cjDistPathStr); + Collection cjDistPaths = getCJUploadPath(cjDistPath); + + List cjDistUploadPath = + fileUploader.registerMultipleLocalResources( + cjDistPaths, Path.CUR_DIR, LocalResourceType.FILE); + + // The files need to be shipped and added to classpath. + Set systemShipFiles = new HashSet<>(shipFiles.size()); + for (File file : shipFiles) { + systemShipFiles.add(file.getAbsoluteFile()); + } + + // Set-up ApplicationSubmissionContext for the application + + final ApplicationId appId = appContext.getApplicationId(); + + // ------------------ Add Zookeeper namespace to local flinkConfiguraton ------ + setHAClusterIdIfNotSet(configuration, appId); + + if (HighAvailabilityMode.isHighAvailabilityModeActivated(configuration)) { + // activate re-execution of failed applications + appContext.setMaxAppAttempts( + configuration.getInteger( + YarnConfigOptions.APPLICATION_ATTEMPTS.key(), + YarnConfiguration.DEFAULT_RM_AM_MAX_ATTEMPTS)); + + activateHighAvailabilitySupport(appContext); + } else { + // set number of application retries to 1 in the default case + appContext.setMaxAppAttempts( + configuration.getInteger(YarnConfigOptions.APPLICATION_ATTEMPTS.key(), 1)); + } + + final Set userJarFiles = new HashSet<>(); + if (jobGraph != null) { + userJarFiles.addAll( + jobGraph.getUserJars().stream() + .map(f -> f.toUri()) + .map(Path::new) + .collect(Collectors.toSet())); + } + + final List jarUrls = + ConfigUtils.decodeListFromConfig(configuration, PipelineOptions.JARS, URI::create); + if (jarUrls != null + && YarnApplicationClusterEntryPoint.class.getName().equals(yarnClusterEntrypoint)) { + userJarFiles.addAll(jarUrls.stream().map(Path::new).collect(Collectors.toSet())); + } + + // only for per job mode + if (jobGraph != null) { + for (Map.Entry entry : + jobGraph.getUserArtifacts().entrySet()) { + // only upload local files + if (!Utils.isRemotePath(entry.getValue().filePath)) { + Path localPath = new Path(entry.getValue().filePath); + Tuple2 remoteFileInfo = + fileUploader.uploadLocalFileToRemote(localPath, entry.getKey()); + jobGraph.setUserArtifactRemotePath( + entry.getKey(), remoteFileInfo.f0.toString()); + } + } + + jobGraph.writeUserArtifactEntriesToConfiguration(); + } + + if (providedLibDirs == null || providedLibDirs.isEmpty()) { + addLibFoldersToShipFiles(systemShipFiles); + } + + // Register all files in provided lib dirs as local resources with public visibility + // and upload the remaining dependencies as local resources with APPLICATION visibility. + final List systemClassPaths = fileUploader.registerProvidedLocalResources(); + String logConfigFilePath = + configuration.getString(YarnConfigOptionsInternal.APPLICATION_LOG_CONFIG_FILE); + if (logConfigFilePath != null) { + logConfigFilePath = logConfigFilePath + File.separator + "log4j.properties"; + File logFile = new File(logConfigFilePath); + systemShipFiles.add(logFile); + String relatePath = "." + File.separator + logFile.getName(); + // 修改APPLICATION_LOG_CONFIG_FILE 的相对路径 + systemClassPaths.add(relatePath); + configuration.setString( + YarnConfigOptionsInternal.APPLICATION_LOG_CONFIG_FILE.key(), relatePath); + } + + final List uploadedDependencies = + fileUploader.registerMultipleLocalResources( + systemShipFiles.stream() + .map(e -> new Path(e.toURI())) + .collect(Collectors.toSet()), + Path.CUR_DIR, + LocalResourceType.FILE); + systemClassPaths.addAll(uploadedDependencies); + + // upload and register ship-only files + // Plugin files only need to be shipped and should not be added to classpath. + if (providedLibDirs == null || providedLibDirs.isEmpty()) { + Set shipOnlyFiles = new HashSet<>(); + addPluginsFoldersToShipFiles(shipOnlyFiles); + fileUploader.registerMultipleLocalResources( + shipOnlyFiles.stream() + .map(e -> new Path(e.toURI())) + .collect(Collectors.toSet()), + Path.CUR_DIR, + LocalResourceType.FILE); + } + + if (!shipArchives.isEmpty()) { + fileUploader.registerMultipleLocalResources( + shipArchives.stream().map(e -> new Path(e.toURI())).collect(Collectors.toSet()), + Path.CUR_DIR, + LocalResourceType.ARCHIVE); + } + + // only for application mode + // Python jar file only needs to be shipped and should not be added to classpath. + if (YarnApplicationClusterEntryPoint.class.getName().equals(yarnClusterEntrypoint) + && PackagedProgramUtils.isPython(configuration.get(APPLICATION_MAIN_CLASS))) { + fileUploader.registerMultipleLocalResources( + Collections.singletonList( + new Path(PackagedProgramUtils.getPythonJar().toURI())), + ConfigConstants.DEFAULT_FLINK_OPT_DIR, + LocalResourceType.FILE); + } + + // Upload and register user jars + final List userClassPaths = + fileUploader.registerMultipleLocalResources( + userJarFiles, + userJarInclusion == YarnConfigOptions.UserJarInclusion.DISABLED + ? ConfigConstants.DEFAULT_FLINK_USR_LIB_DIR + : Path.CUR_DIR, + LocalResourceType.FILE); + + // add cjlib to classpath + if (cjDistUploadPath != null) { + userClassPaths.addAll(cjDistUploadPath); + } + + // usrlib in remote will be used first. + if (providedUsrLibDir.isPresent()) { + final List usrLibClassPaths = + fileUploader.registerMultipleLocalResources( + Collections.singletonList(providedUsrLibDir.get()), + Path.CUR_DIR, + LocalResourceType.FILE); + userClassPaths.addAll(usrLibClassPaths); + } else if (ClusterEntrypointUtils.tryFindUserLibDirectory().isPresent()) { + // local usrlib will be automatically shipped if it exists and there is no remote + // usrlib. + final Set usrLibShipFiles = new HashSet<>(); + addUsrLibFolderToShipFiles(usrLibShipFiles); + final List usrLibClassPaths = + fileUploader.registerMultipleLocalResources( + usrLibShipFiles.stream() + .map(e -> new Path(e.toURI())) + .collect(Collectors.toSet()), + Path.CUR_DIR, + LocalResourceType.FILE); + userClassPaths.addAll(usrLibClassPaths); + } + + if (userJarInclusion == YarnConfigOptions.UserJarInclusion.ORDER) { + systemClassPaths.addAll(userClassPaths); + } + + // normalize classpath by sorting + Collections.sort(systemClassPaths); + Collections.sort(userClassPaths); + + // classpath assembler + StringBuilder classPathBuilder = new StringBuilder(); + if (userJarInclusion == YarnConfigOptions.UserJarInclusion.FIRST) { + for (String userClassPath : userClassPaths) { + classPathBuilder.append(userClassPath).append(File.pathSeparator); + } + } + for (String classPath : systemClassPaths) { + classPathBuilder.append(classPath).append(File.pathSeparator); + } + + // Setup jar for ApplicationMaster + final YarnLocalResourceDescriptor localResourceDescFlinkJar = + fileUploader.uploadFlinkDist(flinkJarPath); + classPathBuilder + .append(localResourceDescFlinkJar.getResourceKey()) + .append(File.pathSeparator); + + // write job graph to tmp file and add it to local resource + // TODO: server use user main method to generate job graph + if (jobGraph != null) { + File tmpJobGraphFile = null; + try { + tmpJobGraphFile = File.createTempFile(appId.toString(), null); + try (FileOutputStream output = new FileOutputStream(tmpJobGraphFile); + ObjectOutputStream obOutput = new ObjectOutputStream(output)) { + obOutput.writeObject(jobGraph); + } + + final String jobGraphFilename = "job.graph"; + configuration.setString(JOB_GRAPH_FILE_PATH, jobGraphFilename); + + fileUploader.registerSingleLocalResource( + jobGraphFilename, + new Path(tmpJobGraphFile.toURI()), + "", + LocalResourceType.FILE, + true, + false); + classPathBuilder.append(jobGraphFilename).append(File.pathSeparator); + } catch (Exception e) { + LOG.warn("Add job graph to local resource fail."); + throw e; + } finally { + if (tmpJobGraphFile != null && !tmpJobGraphFile.delete()) { + LOG.warn("Fail to delete temporary file {}.", tmpJobGraphFile.toPath()); + } + } + } + + // Upload the flink configuration + // write out configuration file + File tmpConfigurationFile = null; + try { + tmpConfigurationFile = File.createTempFile(appId + "-flink-conf.yaml", null); + + // remove localhost bind hosts as they render production clusters unusable + removeLocalhostBindHostSetting(configuration, JobManagerOptions.BIND_HOST); + removeLocalhostBindHostSetting(configuration, TaskManagerOptions.BIND_HOST); + // this setting is unconditionally overridden anyway, so we remove it for clarity + configuration.removeConfig(TaskManagerOptions.HOST); + + BootstrapTools.writeConfiguration(configuration, tmpConfigurationFile); + + String flinkConfigKey = "flink-conf.yaml"; + fileUploader.registerSingleLocalResource( + flinkConfigKey, + new Path(tmpConfigurationFile.getAbsolutePath()), + "", + LocalResourceType.FILE, + true, + true); + classPathBuilder.append("flink-conf.yaml").append(File.pathSeparator); + } finally { + if (tmpConfigurationFile != null && !tmpConfigurationFile.delete()) { + LOG.warn("Fail to delete temporary file {}.", tmpConfigurationFile.toPath()); + } + } + + if (userJarInclusion == YarnConfigOptions.UserJarInclusion.LAST) { + for (String userClassPath : userClassPaths) { + classPathBuilder.append(userClassPath).append(File.pathSeparator); + } + } + + // To support Yarn Secure Integration Test Scenario + // In Integration test setup, the Yarn containers created by YarnMiniCluster does not have + // the Yarn site XML + // and KRB5 configuration files. We are adding these files as container local resources for + // the container + // applications (JM/TMs) to have proper secure cluster setup + Path remoteYarnSiteXmlPath = null; + if (System.getenv("IN_TESTS") != null) { + File f = new File(System.getenv("YARN_CONF_DIR"), Utils.YARN_SITE_FILE_NAME); + LOG.info( + "Adding Yarn configuration {} to the AM container local resource bucket", + f.getAbsolutePath()); + Path yarnSitePath = new Path(f.getAbsolutePath()); + remoteYarnSiteXmlPath = + fileUploader + .registerSingleLocalResource( + Utils.YARN_SITE_FILE_NAME, + yarnSitePath, + "", + LocalResourceType.FILE, + false, + false) + .getPath(); + if (System.getProperty("java.security.krb5.conf") != null) { + configuration.set( + SecurityOptions.KERBEROS_KRB5_PATH, + System.getProperty("java.security.krb5.conf")); + } + } + + Path remoteKrb5Path = null; + boolean hasKrb5 = false; + String krb5Config = configuration.get(SecurityOptions.KERBEROS_KRB5_PATH); + if (!StringUtils.isNullOrWhitespaceOnly(krb5Config)) { + final File krb5 = new File(krb5Config); + LOG.info( + "Adding KRB5 configuration {} to the AM container local resource bucket", + krb5.getAbsolutePath()); + final Path krb5ConfPath = new Path(krb5.getAbsolutePath()); + remoteKrb5Path = + fileUploader + .registerSingleLocalResource( + Utils.KRB5_FILE_NAME, + krb5ConfPath, + "", + LocalResourceType.FILE, + false, + false) + .getPath(); + hasKrb5 = true; + } + + Path remotePathKeytab = null; + String localizedKeytabPath = null; + String keytab = configuration.getString(SecurityOptions.KERBEROS_LOGIN_KEYTAB); + if (keytab != null) { + boolean localizeKeytab = + flinkConfiguration.getBoolean(YarnConfigOptions.SHIP_LOCAL_KEYTAB); + localizedKeytabPath = + flinkConfiguration.getString(YarnConfigOptions.LOCALIZED_KEYTAB_PATH); + if (localizeKeytab) { + // Localize the keytab to YARN containers via local resource. + LOG.info("Adding keytab {} to the AM container local resource bucket", keytab); + remotePathKeytab = + fileUploader + .registerSingleLocalResource( + localizedKeytabPath, + new Path(keytab), + "", + LocalResourceType.FILE, + false, + false) + .getPath(); + } else { + // // Assume Keytab is pre-installed in the container. + localizedKeytabPath = + flinkConfiguration.getString(YarnConfigOptions.LOCALIZED_KEYTAB_PATH); + } + } + + final JobManagerProcessSpec processSpec = + JobManagerProcessUtils.processSpecFromConfigWithNewOptionToInterpretLegacyHeap( + flinkConfiguration, JobManagerOptions.TOTAL_PROCESS_MEMORY); + final ContainerLaunchContext amContainer = + setupApplicationMasterContainer(yarnClusterEntrypoint, hasKrb5, processSpec); + + // New delegation token framework + if (configuration.getBoolean(SecurityOptions.KERBEROS_FETCH_DELEGATION_TOKEN)) { + setTokensFor(amContainer); + } + // Old delegation token framework + if (UserGroupInformation.isSecurityEnabled()) { + LOG.info("Adding delegation token to the AM container."); + final List pathsToObtainToken = new ArrayList<>(); + boolean fetchToken = + configuration.getBoolean(SecurityOptions.KERBEROS_FETCH_DELEGATION_TOKEN); + if (fetchToken) { + List yarnAccessList = + ConfigUtils.decodeListFromConfig( + configuration, + SecurityOptions.KERBEROS_HADOOP_FILESYSTEMS_TO_ACCESS, + Path::new); + pathsToObtainToken.addAll(yarnAccessList); + pathsToObtainToken.addAll(fileUploader.getRemotePaths()); + } + Utils.setTokensFor(amContainer, pathsToObtainToken, yarnConfiguration, fetchToken); + } + + amContainer.setLocalResources(fileUploader.getRegisteredLocalResources()); + fileUploader.close(); + + // Setup CLASSPATH and environment variables for ApplicationMaster + final Map appMasterEnv = + generateApplicationMasterEnv( + fileUploader, + classPathBuilder.toString(), + localResourceDescFlinkJar.toString(), + appId.toString()); + + if (localizedKeytabPath != null) { + appMasterEnv.put(YarnConfigKeys.LOCAL_KEYTAB_PATH, localizedKeytabPath); + String principal = configuration.getString(SecurityOptions.KERBEROS_LOGIN_PRINCIPAL); + appMasterEnv.put(YarnConfigKeys.KEYTAB_PRINCIPAL, principal); + if (remotePathKeytab != null) { + appMasterEnv.put(YarnConfigKeys.REMOTE_KEYTAB_PATH, remotePathKeytab.toString()); + } + } + + // To support Yarn Secure Integration Test Scenario + if (remoteYarnSiteXmlPath != null) { + appMasterEnv.put( + YarnConfigKeys.ENV_YARN_SITE_XML_PATH, remoteYarnSiteXmlPath.toString()); + } + if (remoteKrb5Path != null) { + appMasterEnv.put(YarnConfigKeys.ENV_KRB5_PATH, remoteKrb5Path.toString()); + } + + amContainer.setEnvironment(appMasterEnv); + + // Set up resource type requirements for ApplicationMaster + Resource capability = Records.newRecord(Resource.class); + capability.setMemory(clusterSpecification.getMasterMemoryMB()); + capability.setVirtualCores( + flinkConfiguration.getInteger(YarnConfigOptions.APP_MASTER_VCORES)); + + final String customApplicationName = customName != null ? customName : applicationName; + + appContext.setApplicationName(customApplicationName); + appContext.setApplicationType(applicationType != null ? applicationType : "Apache Flink"); + appContext.setAMContainerSpec(amContainer); + appContext.setResource(capability); + + // Set priority for application + int priorityNum = flinkConfiguration.getInteger(YarnConfigOptions.APPLICATION_PRIORITY); + if (priorityNum >= 0) { + Priority priority = Priority.newInstance(priorityNum); + appContext.setPriority(priority); + } + + if (yarnQueue != null) { + appContext.setQueue(yarnQueue); + } + + setApplicationNodeLabel(appContext); + + setApplicationTags(appContext); + + // add a hook to clean up in case deployment fails + Thread deploymentFailureHook = + new DeploymentFailureHook(yarnApplication, fileUploader.getApplicationDir()); + Runtime.getRuntime().addShutdownHook(deploymentFailureHook); + LOG.info("Submitting application master " + appId); + yarnClient.submitApplication(appContext); + + LOG.info("Waiting for the cluster to be allocated"); + final long startTime = System.currentTimeMillis(); + ApplicationReport report; + YarnApplicationState lastAppState = YarnApplicationState.NEW; + loop: + while (true) { + try { + report = yarnClient.getApplicationReport(appId); + } catch (IOException e) { + throw new YarnDeploymentException("Failed to deploy the cluster.", e); + } + YarnApplicationState appState = report.getYarnApplicationState(); + LOG.debug("Application State: {}", appState); + switch (appState) { + case FAILED: + case KILLED: + throw new YarnDeploymentException( + "The YARN application unexpectedly switched to state " + + appState + + " during deployment. \n" + + "Diagnostics from YARN: " + + report.getDiagnostics() + + "\n" + + "If log aggregation is enabled on your cluster, use this command to further investigate the issue:\n" + + "yarn logs -applicationId " + + appId); + // break .. + case RUNNING: + LOG.info("YARN application has been deployed successfully."); + break loop; + case FINISHED: + LOG.info("YARN application has been finished successfully."); + break loop; + default: + if (appState != lastAppState) { + LOG.info("Deploying cluster, current state " + appState); + } + if (System.currentTimeMillis() - startTime > 60000) { + LOG.info( + "Deployment took more than 60 seconds. Please check if the requested resources are available in the YARN cluster"); + } + } + lastAppState = appState; + Thread.sleep(250); + } + + // since deployment was successful, remove the hook + ShutdownHookUtil.removeShutdownHook(deploymentFailureHook, getClass().getSimpleName(), LOG); + return report; + } + + /** + * chunjun server 包含在chunjun 目录下,不需要上传 + * + * @param path + * @return + */ + public List getCJUploadPath(Path path) { + File cjDir = new File(path.toUri().getPath()); + if (cjDir.exists() && cjDir.isDirectory()) { + List paths = new ArrayList<>(); + Arrays.stream(cjDir.listFiles()) + .filter( + file -> + !(file.toURI().getPath().contains("/server/") + || file.toURI().getPath().contains("/logs/") + || file.toURI().getPath().contains("/conf/") + || file.toURI().getPath().contains("/run/") + || file.toURI().getPath().contains("/bin/") + || file.toURI().getPath().contains("nohup"))) + .forEach(ele -> paths.add(new Path(ele.getPath()))); + + return paths; + } else { + throw new RuntimeException( + "chunjun server not found or not a dir path " + path.toUri().getPath()); + } + } + + private void removeLocalhostBindHostSetting( + Configuration configuration, ConfigOption option) { + configuration + .getOptional(option) + .filter(bindHost -> bindHost.equals("localhost")) + .ifPresent( + bindHost -> { + LOG.info( + "Removing 'localhost' {} setting from effective configuration; using '0.0.0.0' instead.", + option); + configuration.removeConfig(option); + }); + } + + private void setTokensFor(ContainerLaunchContext containerLaunchContext) throws Exception { + LOG.info("Adding delegation tokens to the AM container."); + + Credentials credentials = new Credentials(); + + DelegationTokenManager delegationTokenManager = + new KerberosDelegationTokenManager(flinkConfiguration, null, null); + delegationTokenManager.obtainDelegationTokens(credentials); + + ByteBuffer tokens = ByteBuffer.wrap(DelegationTokenConverter.serialize(credentials)); + containerLaunchContext.setTokens(tokens); + + LOG.info("Delegation tokens added to the AM container."); + } + + /** + * Returns the configured remote target home directory if set, otherwise returns the default + * home directory. + * + * @param defaultFileSystem default file system used + * @return the remote target home directory + */ + @VisibleForTesting + Path getStagingDir(FileSystem defaultFileSystem) throws IOException { + final String configuredStagingDir = + flinkConfiguration.getString(YarnConfigOptions.STAGING_DIRECTORY); + if (configuredStagingDir == null) { + return defaultFileSystem.getHomeDirectory(); + } + FileSystem stagingDirFs = + new Path(configuredStagingDir).getFileSystem(defaultFileSystem.getConf()); + return stagingDirFs.makeQualified(new Path(configuredStagingDir)); + } + + private int getFileReplication() { + final int yarnFileReplication = + yarnConfiguration.getInt( + DFSConfigKeys.DFS_REPLICATION_KEY, DFSConfigKeys.DFS_REPLICATION_DEFAULT); + final int fileReplication = + flinkConfiguration.getInteger(YarnConfigOptions.FILE_REPLICATION); + return fileReplication > 0 ? fileReplication : yarnFileReplication; + } + + private static String encodeYarnLocalResourceDescriptorListToString( + List resources) { + return String.join( + LOCAL_RESOURCE_DESCRIPTOR_SEPARATOR, + resources.stream() + .map(YarnLocalResourceDescriptor::toString) + .collect(Collectors.toList())); + } + + /** + * Kills YARN application and stops YARN client. + * + *

Use this method to kill the App before it has been properly deployed + */ + private void failSessionDuringDeployment( + YarnClient yarnClient, YarnClientApplication yarnApplication) { + LOG.info("Killing YARN application"); + + try { + yarnClient.killApplication( + yarnApplication.getNewApplicationResponse().getApplicationId()); + } catch (Exception e) { + // we only log a debug message here because the "killApplication" call is a best-effort + // call (we don't know if the application has been deployed when the error occurred). + LOG.debug("Error while killing YARN application", e); + } + } + + private static class ClusterResourceDescription { + public final int totalFreeMemory; + public final int containerLimit; + public final int[] nodeManagersFree; + + public ClusterResourceDescription( + int totalFreeMemory, int containerLimit, int[] nodeManagersFree) { + this.totalFreeMemory = totalFreeMemory; + this.containerLimit = containerLimit; + this.nodeManagersFree = nodeManagersFree; + } + } + + private ClusterResourceDescription getCurrentFreeClusterResources(YarnClient yarnClient) + throws YarnException, IOException { + List nodes = yarnClient.getNodeReports(NodeState.RUNNING); + + int totalFreeMemory = 0; + int containerLimit = 0; + int[] nodeManagersFree = new int[nodes.size()]; + + for (int i = 0; i < nodes.size(); i++) { + NodeReport rep = nodes.get(i); + int free = + rep.getCapability().getMemory() + - (rep.getUsed() != null ? rep.getUsed().getMemory() : 0); + nodeManagersFree[i] = free; + totalFreeMemory += free; + if (free > containerLimit) { + containerLimit = free; + } + } + return new ClusterResourceDescription(totalFreeMemory, containerLimit, nodeManagersFree); + } + + @Override + public String getClusterDescription() { + + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos); + + YarnClusterMetrics metrics = yarnClient.getYarnClusterMetrics(); + + ps.append("NodeManagers in the ClusterClient " + metrics.getNumNodeManagers()); + List nodes = yarnClient.getNodeReports(NodeState.RUNNING); + final String format = "|%-16s |%-16s %n"; + ps.printf("|Property |Value %n"); + ps.println("+---------------------------------------+"); + int totalMemory = 0; + int totalCores = 0; + for (NodeReport rep : nodes) { + final Resource res = rep.getCapability(); + totalMemory += res.getMemory(); + totalCores += res.getVirtualCores(); + ps.format(format, "NodeID", rep.getNodeId()); + ps.format(format, "Memory", res.getMemory() + " MB"); + ps.format(format, "vCores", res.getVirtualCores()); + ps.format(format, "HealthReport", rep.getHealthReport()); + ps.format(format, "Containers", rep.getNumContainers()); + ps.println("+---------------------------------------+"); + } + ps.println("Summary: totalMemory " + totalMemory + " totalCores " + totalCores); + List qInfo = yarnClient.getAllQueues(); + for (QueueInfo q : qInfo) { + ps.println( + "Queue: " + + q.getQueueName() + + ", Current Capacity: " + + q.getCurrentCapacity() + + " Max Capacity: " + + q.getMaximumCapacity() + + " Applications: " + + q.getApplications().size()); + } + return baos.toString(); + } catch (Exception e) { + throw new RuntimeException("Couldn't get cluster description", e); + } + } + + private void activateHighAvailabilitySupport(ApplicationSubmissionContext appContext) + throws InvocationTargetException, IllegalAccessException { + + ApplicationSubmissionContextReflector reflector = + ApplicationSubmissionContextReflector.getInstance(); + + reflector.setKeepContainersAcrossApplicationAttempts(appContext, true); + + reflector.setAttemptFailuresValidityInterval( + appContext, + flinkConfiguration.getLong( + YarnConfigOptions.APPLICATION_ATTEMPT_FAILURE_VALIDITY_INTERVAL)); + } + + private void setApplicationTags(final ApplicationSubmissionContext appContext) + throws InvocationTargetException, IllegalAccessException { + + final ApplicationSubmissionContextReflector reflector = + ApplicationSubmissionContextReflector.getInstance(); + final String tagsString = flinkConfiguration.getString(YarnConfigOptions.APPLICATION_TAGS); + + final Set applicationTags = new HashSet<>(); + + // Trim whitespace and cull empty tags + for (final String tag : tagsString.split(",")) { + final String trimmedTag = tag.trim(); + if (!trimmedTag.isEmpty()) { + applicationTags.add(trimmedTag); + } + } + + reflector.setApplicationTags(appContext, applicationTags); + } + + private void setApplicationNodeLabel(final ApplicationSubmissionContext appContext) + throws InvocationTargetException, IllegalAccessException { + + if (nodeLabel != null) { + final ApplicationSubmissionContextReflector reflector = + ApplicationSubmissionContextReflector.getInstance(); + reflector.setApplicationNodeLabel(appContext, nodeLabel); + } + } + + /** + * Singleton object which uses reflection to determine whether the {@link + * ApplicationSubmissionContext} supports various methods which, depending on the Hadoop + * version, may or may not be supported. + * + *

If an unsupported method is invoked, nothing happens. + * + *

Currently three methods are proxied: - setApplicationTags (>= 2.4.0) - + * setAttemptFailuresValidityInterval (>= 2.6.0) - setKeepContainersAcrossApplicationAttempts + * (>= 2.4.0) - setNodeLabelExpression (>= 2.6.0) + */ + private static class ApplicationSubmissionContextReflector { + private static final Logger LOG = + LoggerFactory.getLogger(ApplicationSubmissionContextReflector.class); + + private static final ApplicationSubmissionContextReflector instance = + new ApplicationSubmissionContextReflector(ApplicationSubmissionContext.class); + + public static ApplicationSubmissionContextReflector getInstance() { + return instance; + } + + private static final String APPLICATION_TAGS_METHOD_NAME = "setApplicationTags"; + private static final String ATTEMPT_FAILURES_METHOD_NAME = + "setAttemptFailuresValidityInterval"; + private static final String KEEP_CONTAINERS_METHOD_NAME = + "setKeepContainersAcrossApplicationAttempts"; + private static final String NODE_LABEL_EXPRESSION_NAME = "setNodeLabelExpression"; + + private final Method applicationTagsMethod; + private final Method attemptFailuresValidityIntervalMethod; + private final Method keepContainersMethod; + @Nullable private final Method nodeLabelExpressionMethod; + + private ApplicationSubmissionContextReflector(Class clazz) { + Method applicationTagsMethod; + Method attemptFailuresValidityIntervalMethod; + Method keepContainersMethod; + Method nodeLabelExpressionMethod; + + try { + // this method is only supported by Hadoop 2.4.0 onwards + applicationTagsMethod = clazz.getMethod(APPLICATION_TAGS_METHOD_NAME, Set.class); + LOG.debug( + "{} supports method {}.", + clazz.getCanonicalName(), + APPLICATION_TAGS_METHOD_NAME); + } catch (NoSuchMethodException e) { + LOG.debug( + "{} does not support method {}.", + clazz.getCanonicalName(), + APPLICATION_TAGS_METHOD_NAME); + // assign null because the Hadoop version apparently does not support this call. + applicationTagsMethod = null; + } + + this.applicationTagsMethod = applicationTagsMethod; + + try { + // this method is only supported by Hadoop 2.6.0 onwards + attemptFailuresValidityIntervalMethod = + clazz.getMethod(ATTEMPT_FAILURES_METHOD_NAME, long.class); + LOG.debug( + "{} supports method {}.", + clazz.getCanonicalName(), + ATTEMPT_FAILURES_METHOD_NAME); + } catch (NoSuchMethodException e) { + LOG.debug( + "{} does not support method {}.", + clazz.getCanonicalName(), + ATTEMPT_FAILURES_METHOD_NAME); + // assign null because the Hadoop version apparently does not support this call. + attemptFailuresValidityIntervalMethod = null; + } + + this.attemptFailuresValidityIntervalMethod = attemptFailuresValidityIntervalMethod; + + try { + // this method is only supported by Hadoop 2.4.0 onwards + keepContainersMethod = clazz.getMethod(KEEP_CONTAINERS_METHOD_NAME, boolean.class); + LOG.debug( + "{} supports method {}.", + clazz.getCanonicalName(), + KEEP_CONTAINERS_METHOD_NAME); + } catch (NoSuchMethodException e) { + LOG.debug( + "{} does not support method {}.", + clazz.getCanonicalName(), + KEEP_CONTAINERS_METHOD_NAME); + // assign null because the Hadoop version apparently does not support this call. + keepContainersMethod = null; + } + + this.keepContainersMethod = keepContainersMethod; + + try { + nodeLabelExpressionMethod = + clazz.getMethod(NODE_LABEL_EXPRESSION_NAME, String.class); + LOG.debug( + "{} supports method {}.", + clazz.getCanonicalName(), + NODE_LABEL_EXPRESSION_NAME); + } catch (NoSuchMethodException e) { + LOG.debug( + "{} does not support method {}.", + clazz.getCanonicalName(), + NODE_LABEL_EXPRESSION_NAME); + nodeLabelExpressionMethod = null; + } + + this.nodeLabelExpressionMethod = nodeLabelExpressionMethod; + } + + public void setApplicationTags( + ApplicationSubmissionContext appContext, Set applicationTags) + throws InvocationTargetException, IllegalAccessException { + if (applicationTagsMethod != null) { + LOG.debug( + "Calling method {} of {}.", + applicationTagsMethod.getName(), + appContext.getClass().getCanonicalName()); + applicationTagsMethod.invoke(appContext, applicationTags); + } else { + LOG.debug( + "{} does not support method {}. Doing nothing.", + appContext.getClass().getCanonicalName(), + APPLICATION_TAGS_METHOD_NAME); + } + } + + public void setApplicationNodeLabel( + ApplicationSubmissionContext appContext, String nodeLabel) + throws InvocationTargetException, IllegalAccessException { + if (nodeLabelExpressionMethod != null) { + LOG.debug( + "Calling method {} of {}.", + nodeLabelExpressionMethod.getName(), + appContext.getClass().getCanonicalName()); + nodeLabelExpressionMethod.invoke(appContext, nodeLabel); + } else { + LOG.debug( + "{} does not support method {}. Doing nothing.", + appContext.getClass().getCanonicalName(), + NODE_LABEL_EXPRESSION_NAME); + } + } + + public void setAttemptFailuresValidityInterval( + ApplicationSubmissionContext appContext, long validityInterval) + throws InvocationTargetException, IllegalAccessException { + if (attemptFailuresValidityIntervalMethod != null) { + LOG.debug( + "Calling method {} of {}.", + attemptFailuresValidityIntervalMethod.getName(), + appContext.getClass().getCanonicalName()); + attemptFailuresValidityIntervalMethod.invoke(appContext, validityInterval); + } else { + LOG.debug( + "{} does not support method {}. Doing nothing.", + appContext.getClass().getCanonicalName(), + ATTEMPT_FAILURES_METHOD_NAME); + } + } + + public void setKeepContainersAcrossApplicationAttempts( + ApplicationSubmissionContext appContext, boolean keepContainers) + throws InvocationTargetException, IllegalAccessException { + + if (keepContainersMethod != null) { + LOG.debug( + "Calling method {} of {}.", + keepContainersMethod.getName(), + appContext.getClass().getCanonicalName()); + keepContainersMethod.invoke(appContext, keepContainers); + } else { + LOG.debug( + "{} does not support method {}. Doing nothing.", + appContext.getClass().getCanonicalName(), + KEEP_CONTAINERS_METHOD_NAME); + } + } + } + + private static class YarnDeploymentException extends RuntimeException { + private static final long serialVersionUID = -812040641215388943L; + + public YarnDeploymentException(String message) { + super(message); + } + + public YarnDeploymentException(String message, Throwable cause) { + super(message, cause); + } + } + + private class DeploymentFailureHook extends Thread { + + private final YarnClient yarnClient; + private final YarnClientApplication yarnApplication; + private final Path yarnFilesDir; + + DeploymentFailureHook(YarnClientApplication yarnApplication, Path yarnFilesDir) { + this.yarnApplication = Preconditions.checkNotNull(yarnApplication); + this.yarnFilesDir = Preconditions.checkNotNull(yarnFilesDir); + + // A new yarn client need to be created in shutdown hook in order to avoid + // the yarn client has been closed by YarnClusterDescriptor. + this.yarnClient = YarnClient.createYarnClient(); + this.yarnClient.init(yarnConfiguration); + } + + @Override + public void run() { + LOG.info("Cancelling deployment from Deployment Failure Hook"); + yarnClient.start(); + failSessionDuringDeployment(yarnClient, yarnApplication); + yarnClient.stop(); + LOG.info("Deleting files in {}.", yarnFilesDir); + try { + FileSystem fs = FileSystem.get(yarnConfiguration); + + if (!fs.delete(yarnFilesDir, true)) { + throw new IOException( + "Deleting files in " + yarnFilesDir + " was unsuccessful"); + } + + fs.close(); + } catch (IOException e) { + LOG.error("Failed to delete Flink Jar and configuration files in HDFS", e); + } + } + } + + @VisibleForTesting + void addLibFoldersToShipFiles(Collection effectiveShipFiles) { + // Add lib folder to the ship files if the environment variable is set. + // This is for convenience when running from the command-line. + // (for other files users explicitly set the ship files) + String libDir = System.getenv().get(ENV_FLINK_LIB_DIR); + if (libDir != null) { + File directoryFile = new File(libDir); + if (directoryFile.isDirectory()) { + effectiveShipFiles.add(directoryFile); + } else { + throw new YarnDeploymentException( + "The environment variable '" + + ENV_FLINK_LIB_DIR + + "' is set to '" + + libDir + + "' but the directory doesn't exist."); + } + } else if (shipFiles.isEmpty()) { + LOG.warn( + "Environment variable '{}' not set and ship files have not been provided manually. " + + "Not shipping any library files.", + ENV_FLINK_LIB_DIR); + } + } + + @VisibleForTesting + void addUsrLibFolderToShipFiles(Collection effectiveShipFiles) { + // Add usrlib folder to the ship files if it exists + // Classes in the folder will be loaded by UserClassLoader if CLASSPATH_INCLUDE_USER_JAR is + // DISABLED. + ClusterEntrypointUtils.tryFindUserLibDirectory() + .ifPresent( + usrLibDirFile -> { + effectiveShipFiles.add(usrLibDirFile); + LOG.info( + "usrlib: {} will be shipped automatically.", + usrLibDirFile.getAbsolutePath()); + }); + } + + @VisibleForTesting + void addPluginsFoldersToShipFiles(Collection effectiveShipFiles) { + final Optional pluginsDir = PluginConfig.getPluginsDir(); + pluginsDir.ifPresent(effectiveShipFiles::add); + } + + ContainerLaunchContext setupApplicationMasterContainer( + String yarnClusterEntrypoint, boolean hasKrb5, JobManagerProcessSpec processSpec) { + // ------------------ Prepare Application Master Container ------------------------------ + + // respect custom JVM options in the YAML file + String javaOpts = flinkConfiguration.getString(CoreOptions.FLINK_JVM_OPTIONS); + if (flinkConfiguration.getString(CoreOptions.FLINK_JM_JVM_OPTIONS).length() > 0) { + javaOpts += " " + flinkConfiguration.getString(CoreOptions.FLINK_JM_JVM_OPTIONS); + } + + // krb5.conf file will be available as local resource in JM/TM container + if (hasKrb5) { + javaOpts += " -Djava.security.krb5.conf=krb5.conf"; + } + + // Set up the container launch context for the application master + ContainerLaunchContext amContainer = Records.newRecord(ContainerLaunchContext.class); + + final Map startCommandValues = new HashMap<>(); + startCommandValues.put("java", "$JAVA_HOME/bin/java"); + + String jvmHeapMem = + JobManagerProcessUtils.generateJvmParametersStr(processSpec, flinkConfiguration); + startCommandValues.put("jvmmem", jvmHeapMem); + + startCommandValues.put("jvmopts", javaOpts); + startCommandValues.put( + "logging", YarnLogConfigUtil.getLoggingYarnCommand(flinkConfiguration)); + + startCommandValues.put("class", yarnClusterEntrypoint); + startCommandValues.put( + "redirects", + "1> " + + ApplicationConstants.LOG_DIR_EXPANSION_VAR + + "/jobmanager.out " + + "2> " + + ApplicationConstants.LOG_DIR_EXPANSION_VAR + + "/jobmanager.err"); + String dynamicParameterListStr = + JobManagerProcessUtils.generateDynamicConfigsStr(processSpec); + startCommandValues.put("args", dynamicParameterListStr); + + final String commandTemplate = + flinkConfiguration.getString( + ConfigConstants.YARN_CONTAINER_START_COMMAND_TEMPLATE, + ConfigConstants.DEFAULT_YARN_CONTAINER_START_COMMAND_TEMPLATE); + final String amCommand = + BootstrapTools.getStartCommand(commandTemplate, startCommandValues); + + amContainer.setCommands(Collections.singletonList(amCommand)); + + LOG.debug("Application Master start command: " + amCommand); + + return amContainer; + } + + private static YarnConfigOptions.UserJarInclusion getUserJarInclusionMode( + org.apache.flink.configuration.Configuration config) { + return config.get(YarnConfigOptions.CLASSPATH_INCLUDE_USER_JAR); + } + + private static boolean isUsrLibDirIncludedInShipFiles(List shipFiles) { + return shipFiles.stream() + .filter(File::isDirectory) + .map(File::getName) + .anyMatch(name -> name.equals(DEFAULT_FLINK_USR_LIB_DIR)); + } + + private void setClusterEntrypointInfoToConfig(final ApplicationReport report) { + checkNotNull(report); + + final ApplicationId appId = report.getApplicationId(); + final String host = report.getHost(); + final int port = report.getRpcPort(); + + LOG.info("Found Web Interface {}:{} of application '{}'.", host, port, appId); + + flinkConfiguration.setString(JobManagerOptions.ADDRESS, host); + flinkConfiguration.setInteger(JobManagerOptions.PORT, port); + + flinkConfiguration.setString(RestOptions.ADDRESS, host); + flinkConfiguration.setInteger(RestOptions.PORT, port); + + flinkConfiguration.set(YarnConfigOptions.APPLICATION_ID, ConverterUtils.toString(appId)); + + setHAClusterIdIfNotSet(flinkConfiguration, appId); + } + + private void setHAClusterIdIfNotSet(Configuration configuration, ApplicationId appId) { + // set cluster-id to app id if not specified + if (!configuration.contains(HighAvailabilityOptions.HA_CLUSTER_ID)) { + configuration.set( + HighAvailabilityOptions.HA_CLUSTER_ID, ConverterUtils.toString(appId)); + } + } + + public static void logDetachedClusterInformation( + ApplicationId yarnApplicationId, Logger logger) { + logger.info( + "The Flink YARN session cluster has been started in detached mode. In order to " + + "stop Flink gracefully, use the following command:\n" + + "$ echo \"stop\" | ./bin/yarn-session.sh -id {}\n" + + "If this should not be possible, then you can also kill Flink via YARN's web interface or via:\n" + + "$ yarn application -kill {}\n" + + "Note that killing Flink might not clean up all job artifacts and temporary files.", + yarnApplicationId, + yarnApplicationId); + } + + @VisibleForTesting + Map generateApplicationMasterEnv( + final YarnApplicationFileUploader fileUploader, + final String classPathStr, + final String localFlinkJarStr, + final String appIdStr) + throws IOException { + final Map env = new HashMap<>(); + // set user specified app master environment variables + env.putAll( + ConfigurationUtils.getPrefixedKeyValuePairs( + ResourceManagerOptions.CONTAINERIZED_MASTER_ENV_PREFIX, + this.flinkConfiguration)); + // set Flink app class path + env.put(ENV_FLINK_CLASSPATH, classPathStr); + // Set FLINK_LIB_DIR to `lib` folder under working dir in container + env.put(ENV_FLINK_LIB_DIR, Path.CUR_DIR + "/" + ConfigConstants.DEFAULT_FLINK_LIB_DIR); + // Set FLINK_OPT_DIR to `opt` folder under working dir in container + env.put(ENV_FLINK_OPT_DIR, Path.CUR_DIR + "/" + ConfigConstants.DEFAULT_FLINK_OPT_DIR); + // set Flink on YARN internal configuration values + env.put(YarnConfigKeys.FLINK_DIST_JAR, localFlinkJarStr); + env.put(YarnConfigKeys.ENV_APP_ID, appIdStr); + env.put(YarnConfigKeys.ENV_CLIENT_HOME_DIR, fileUploader.getHomeDir().toString()); + env.put( + YarnConfigKeys.ENV_CLIENT_SHIP_FILES, + encodeYarnLocalResourceDescriptorListToString( + fileUploader.getEnvShipResourceList())); + env.put( + YarnConfigKeys.FLINK_YARN_FILES, + fileUploader.getApplicationDir().toUri().toString()); + // https://github.com/apache/hadoop/blob/trunk/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/YarnApplicationSecurity.md#identity-on-an-insecure-cluster-hadoop_user_name + env.put( + YarnConfigKeys.ENV_HADOOP_USER_NAME, + UserGroupInformation.getCurrentUser().getUserName()); + // set classpath from YARN configuration + Utils.setupYarnClassPath(this.yarnConfiguration, env); + return env; + } +} diff --git a/chunjun-server/src/main/resources/log4j2.properties b/chunjun-server/src/main/resources/log4j2.properties new file mode 100644 index 0000000000..2dc1b8ca50 --- /dev/null +++ b/chunjun-server/src/main/resources/log4j2.properties @@ -0,0 +1,42 @@ +################################################################################ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +rootLogger.level = INFO + +rootLogger.appenderRef.consoleStdout.ref = consoleStdoutAppender +rootLogger.appenderRef.consoleStderr.ref = consoleStderrAppender + +appender.consoleStdout.name = consoleStdoutAppender +appender.consoleStdout.type = CONSOLE +appender.consoleStdout.target = SYSTEM_OUT +appender.consoleStdout.layout.type = PatternLayout +appender.consoleStdout.layout.pattern = %d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %c - %m%n +appender.consoleStdout.filter.acceptLtWarn.type = ThresholdFilter +appender.consoleStdout.filter.acceptLtWarn.level = WARN +appender.consoleStdout.filter.acceptLtWarn.onMatch = DENY +appender.consoleStdout.filter.acceptLtWarn.onMismatch = ACCEPT + +appender.consoleStderr.name = consoleStderrAppender +appender.consoleStderr.type = CONSOLE +appender.consoleStderr.target = SYSTEM_ERR +appender.consoleStderr.layout.type = PatternLayout +appender.consoleStderr.layout.pattern = %d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %c - %m%n +appender.consoleStderr.filter.acceptGteWarn.type = ThresholdFilter +appender.consoleStderr.filter.acceptGteWarn.level = WARN +appender.consoleStderr.filter.acceptGteWarn.onMatch = ACCEPT +appender.consoleStderr.filter.acceptGteWarn.onMismatch = DENY diff --git a/conf/application.properties b/conf/application.properties new file mode 100644 index 0000000000..dee097dca7 --- /dev/null +++ b/conf/application.properties @@ -0,0 +1,28 @@ +#server port +#server.port = 18081 +#open session check +#flink session check,default for true +#chunjun.session.check = true +#chunjun.session.deploy = true +#chunjun.restful.enable = true + +#yarn config +yarn.queue = default + +#log config +log.level = INFO + +#env $FLINK_HOME +flink.home.path = /Users/xuchao/MyPrograms/Flink/flink-1.16 +#flink.conf.path,default get from flink.home.path +#flink.conf.path = /Users/xuchao/MyPrograms/Flink/flink-1.16/conf/ +#flink.lib.path,default get from flink.home.path +#flink.lib.path = /Users/xuchao/MyPrograms/Flink/flink-1.16/lib/ + +chunjun.home.path = /Users/xuchao/IdeaProjects/chunjun +#chunjun.conf.path,default get from chunjun.home.path +#chunjun.lib.path,default get from chunjun.home.path + +hadoop.conf.path = /Users/xuchao/conf/hadoopconf/dev_hadoop_new_flinkx01 + + diff --git a/conf/logconf/debug/log4j.properties b/conf/logconf/debug/log4j.properties new file mode 100644 index 0000000000..7124272246 --- /dev/null +++ b/conf/logconf/debug/log4j.properties @@ -0,0 +1,51 @@ +################################################################################ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +# This affects logging for both user code and Flink +log4j.rootLogger=DEBUG, rollingFile + +# Uncomment this if you want to _only_ change Flink's logging +#log4j.logger.org.apache.flink=INFO + +# The following lines keep the log level of common libraries/connectors on +# log level INFO. The root logger does not override this. You have to manually +# change the log levels here. +log4j.logger.akka=DEBUG +log4j.logger.org.apache.kafka=DEBUG +log4j.logger.org.apache.hadoop=DEBUG +log4j.logger.org.apache.zookeeper=DEBUG + +# Log all infos in the given file +log4j.appender.file=org.apache.log4j.FileAppender +log4j.appender.file.file=${log.file} +log4j.appender.file.append=false +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %-60c %x - %m%n + +log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender +log4j.appender.rollingFile.Threshold=DEBUG +log4j.appender.rollingFile.ImmediateFlush=true +log4j.appender.rollingFile.Append=false +log4j.appender.rollingFile.File=${log.file} +log4j.appender.rollingFile.MaxFileSize=30MB +log4j.appender.rollingFile.MaxBackupIndex=3 +log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout +log4j.appender.rollingFile.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss,SSS} %-5p %-60c %x - %m%n + +# Suppress the irrelevant (wrong) warnings from the Netty channel handler +log4j.logger.org.apache.flink.shaded.akka.org.jboss.netty.channel.DefaultChannelPipeline=ERROR, file diff --git a/conf/logconf/debug/logback.xml b/conf/logconf/debug/logback.xml new file mode 100644 index 0000000000..931a1fc6d5 --- /dev/null +++ b/conf/logconf/debug/logback.xml @@ -0,0 +1,76 @@ + + + + + ${log.file} + false + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{60} %X{sourceThread} - %msg%n + + + + + ${log.file} + false + + %d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %-60c %x - %m%n + + + INFO + + + 3 + ${log.file}.%i + + + 30MB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/logconf/error/log4j.properties b/conf/logconf/error/log4j.properties new file mode 100644 index 0000000000..209806df69 --- /dev/null +++ b/conf/logconf/error/log4j.properties @@ -0,0 +1,51 @@ +################################################################################ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +# This affects logging for both user code and Flink +log4j.rootLogger=ERROR, rollingFile + +# Uncomment this if you want to _only_ change Flink's logging +#log4j.logger.org.apache.flink=INFO + +# The following lines keep the log level of common libraries/connectors on +# log level INFO. The root logger does not override this. You have to manually +# change the log levels here. +log4j.logger.akka=ERROR +log4j.logger.org.apache.kafka=ERROR +log4j.logger.org.apache.hadoop=ERROR +log4j.logger.org.apache.zookeeper=ERROR + +# Log all infos in the given file +log4j.appender.file=org.apache.log4j.FileAppender +log4j.appender.file.file=${log.file} +log4j.appender.file.append=false +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %-60c %x - %m%n + +log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender +log4j.appender.rollingFile.Threshold=ERROR +log4j.appender.rollingFile.ImmediateFlush=true +log4j.appender.rollingFile.Append=false +log4j.appender.rollingFile.File=${log.file} +log4j.appender.rollingFile.MaxFileSize=30MB +log4j.appender.rollingFile.MaxBackupIndex=3 +log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout +log4j.appender.rollingFile.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss,SSS} %-5p %-60c %x - %m%n + +# Suppress the irrelevant (wrong) warnings from the Netty channel handler +log4j.logger.org.apache.flink.shaded.akka.org.jboss.netty.channel.DefaultChannelPipeline=ERROR, file diff --git a/conf/logconf/error/logback.xml b/conf/logconf/error/logback.xml new file mode 100644 index 0000000000..d4de1b4773 --- /dev/null +++ b/conf/logconf/error/logback.xml @@ -0,0 +1,76 @@ + + + + + ${log.file} + false + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{60} %X{sourceThread} - %msg%n + + + + + ${log.file} + false + + %d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %-60c %x - %m%n + + + INFO + + + 3 + ${log.file}.%i + + + 30MB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/logconf/fatal/log4j.properties b/conf/logconf/fatal/log4j.properties new file mode 100644 index 0000000000..b5500fe4ef --- /dev/null +++ b/conf/logconf/fatal/log4j.properties @@ -0,0 +1,52 @@ +################################################################################ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +# This affects logging for both user code and Flink +log4j.rootLogger=FATAL, rollingFile + +# Uncomment this if you want to _only_ change Flink's logging +#log4j.logger.org.apache.flink=INFO + +# The following lines keep the log level of common libraries/connectors on +# log level INFO. The root logger does not override this. You have to manually +# change the log levels here. +log4j.logger.akka=FATAL +log4j.logger.org.apache.kafka=FATAL +log4j.logger.org.apache.hadoop=FATAL +log4j.logger.org.apache.zookeeper=FATAL + +# Log all infos in the given file +log4j.appender.file=org.apache.log4j.FileAppender +log4j.appender.file.file=${log.file} +log4j.appender.file.append=false +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %-60c %x - %m%n + +log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender +log4j.appender.rollingFile.Threshold=FATAL +log4j.appender.rollingFile.ImmediateFlush=true +log4j.appender.rollingFile.Append=false +log4j.appender.rollingFile.File=${log.file} +log4j.appender.rollingFile.MaxFileSize=30MB +log4j.appender.rollingFile.MaxBackupIndex=3 +log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout +log4j.appender.rollingFile.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss,SSS} %-5p %-60c %x - %m%n + +# Suppress the irrelevant (wrong) warnings from the Netty channel handler +log4j.logger.org.apache.flink.shaded.akka.org.jboss.netty.channel.DefaultChannelPipeline=FATAL, file + diff --git a/conf/logconf/fatal/logback.xml b/conf/logconf/fatal/logback.xml new file mode 100644 index 0000000000..67f908b5d1 --- /dev/null +++ b/conf/logconf/fatal/logback.xml @@ -0,0 +1,76 @@ + + + + + ${log.file} + false + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{60} %X{sourceThread} - %msg%n + + + + + ${log.file} + false + + %d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %-60c %x - %m%n + + + INFO + + + 3 + ${log.file}.%i + + + 30MB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/logconf/info/log4j.properties b/conf/logconf/info/log4j.properties new file mode 100644 index 0000000000..24fe206bbb --- /dev/null +++ b/conf/logconf/info/log4j.properties @@ -0,0 +1,43 @@ +# Allows this configuration to be modified at runtime. The file will be checked every 30 seconds. +monitorInterval=30 + +# This affects logging for both user code and Flink +rootLogger.level = INFO +rootLogger.appenderRef.file.ref = MainAppender + +# Uncomment this if you want to _only_ change Flink's logging +#logger.flink.name = org.apache.flink +#logger.flink.level = INFO + +# The following lines keep the log level of common libraries/connectors on +# log level INFO. The root logger does not override this. You have to manually +# change the log levels here. +logger.akka.name = akka +logger.akka.level = INFO +logger.kafka.name= org.apache.kafka +logger.kafka.level = INFO +logger.hadoop.name = org.apache.hadoop +logger.hadoop.level = INFO +logger.zookeeper.name = org.apache.zookeeper +logger.zookeeper.level = INFO +logger.shaded_zookeeper.name = org.apache.flink.shaded.zookeeper3 +logger.shaded_zookeeper.level = INFO + +# Log all infos in the given file +appender.main.name = MainAppender +appender.main.type = RollingFile +appender.main.append = true +appender.main.fileName = ${sys:log.file} +appender.main.filePattern = ${sys:log.file}.%i +appender.main.layout.type = PatternLayout +appender.main.layout.pattern = %d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %-60c %x - %m%n +appender.main.policies.type = Policies +appender.main.policies.size.type = SizeBasedTriggeringPolicy +appender.main.policies.size.size = 100MB +appender.main.policies.startup.type = OnStartupTriggeringPolicy +appender.main.strategy.type = DefaultRolloverStrategy +appender.main.strategy.max = ${env:MAX_LOG_FILE_NUMBER:-10} + +# Suppress the irrelevant (wrong) warnings from the Netty channel handler +logger.netty.name = org.jboss.netty.channel.DefaultChannelPipeline +logger.netty.level = OFF diff --git a/conf/logconf/info/logback.xml b/conf/logconf/info/logback.xml new file mode 100644 index 0000000000..5e6214d2c7 --- /dev/null +++ b/conf/logconf/info/logback.xml @@ -0,0 +1,76 @@ + + + + + ${log.file} + false + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{60} %X{sourceThread} - %msg%n + + + + + ${log.file} + false + + %d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %-60c %x - %m%n + + + INFO + + + 3 + ${log.file}.%i + + + 30MB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/logconf/log4j.properties b/conf/logconf/log4j.properties new file mode 100644 index 0000000000..a8e5bfb457 --- /dev/null +++ b/conf/logconf/log4j.properties @@ -0,0 +1,54 @@ +################################################################################ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +# This affects logging for both user code and Flink +log4j.rootLogger=INFO, rollingFile + +# Uncomment this if you want to _only_ change Flink's logging +#log4j.logger.org.apache.flink=INFO + +# The following lines keep the log level of common libraries/connectors on +# log level INFO. The root logger does not override this. You have to manually +# change the log levels here. +log4j.logger.akka=INFO +log4j.logger.org.apache.kafka=INFO +log4j.logger.org.apache.hadoop=INFO +log4j.logger.org.apache.zookeeper=INFO + +# Log all infos in the given file +log4j.appender.file=org.apache.log4j.FileAppender +log4j.appender.file.file=${log.file} +log4j.appender.file.append=false +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %-60c %x - %m%n + +log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender +log4j.appender.rollingFile.Threshold=INFO +log4j.appender.rollingFile.ImmediateFlush=true +log4j.appender.rollingFile.Append=false +log4j.appender.rollingFile.File=${log.file} +log4j.appender.rollingFile.MaxFileSize=30MB +log4j.appender.rollingFile.MaxBackupIndex=3 +log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout +log4j.appender.rollingFile.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss,SSS} %-5p %-60c %x - %m%n + +# Suppress the irrelevant (wrong) warnings from the Netty channel handler +log4j.logger.org.apache.flink.shaded.akka.org.jboss.netty.channel.DefaultChannelPipeline=ERROR, file + + + diff --git a/conf/logconf/warn/log4j.properties b/conf/logconf/warn/log4j.properties new file mode 100644 index 0000000000..082a3163eb --- /dev/null +++ b/conf/logconf/warn/log4j.properties @@ -0,0 +1,51 @@ +################################################################################ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ + +# This affects logging for both user code and Flink +log4j.rootLogger=WARN, rollingFile + +# Uncomment this if you want to _only_ change Flink's logging +#log4j.logger.org.apache.flink=INFO + +# The following lines keep the log level of common libraries/connectors on +# log level INFO. The root logger does not override this. You have to manually +# change the log levels here. +log4j.logger.akka=WARN +log4j.logger.org.apache.kafka=WARN +log4j.logger.org.apache.hadoop=WARN +log4j.logger.org.apache.zookeeper=WARN + +# Log all infos in the given file +log4j.appender.file=org.apache.log4j.FileAppender +log4j.appender.file.file=${log.file} +log4j.appender.file.append=false +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %-60c %x - %m%n + +log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender +log4j.appender.rollingFile.Threshold=WARN +log4j.appender.rollingFile.ImmediateFlush=true +log4j.appender.rollingFile.Append=false +log4j.appender.rollingFile.File=${log.file} +log4j.appender.rollingFile.MaxFileSize=30MB +log4j.appender.rollingFile.MaxBackupIndex=3 +log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout +log4j.appender.rollingFile.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss,SSS} %-5p %-60c %x - %m%n + +# Suppress the irrelevant (wrong) warnings from the Netty channel handler +log4j.logger.org.apache.flink.shaded.akka.org.jboss.netty.channel.DefaultChannelPipeline=WARN, file diff --git a/conf/logconf/warn/logback.xml b/conf/logconf/warn/logback.xml new file mode 100644 index 0000000000..b337c44eef --- /dev/null +++ b/conf/logconf/warn/logback.xml @@ -0,0 +1,76 @@ + + + + + ${log.file} + false + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{60} %X{sourceThread} - %msg%n + + + + + ${log.file} + false + + %d{yyyy-MM-dd HH:mm:ss,SSS} %-5p %-60c %x - %m%n + + + INFO + + + 3 + ${log.file}.%i + + + 30MB + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 5ce3ae4940..dc31571dc4 100755 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,7 @@ chunjun-assembly chunjun-e2e chunjun-local-test + chunjun-server