/*
* 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 gobblin.yarn;
import java.util.Collections;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.yarn.api.ApplicationConstants;
import org.apache.hadoop.yarn.api.records.ContainerId;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.util.ConverterUtils;
import org.apache.helix.NotificationContext;
import org.apache.helix.messaging.handling.HelixTaskResult;
import org.apache.helix.messaging.handling.MessageHandler;
import org.apache.helix.messaging.handling.MessageHandlerFactory;
import org.apache.helix.model.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Optional;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import gobblin.annotation.Alpha;
import gobblin.cluster.GobblinClusterConfigurationKeys;
import gobblin.cluster.GobblinClusterManager;
import gobblin.util.JvmUtils;
import gobblin.util.logs.Log4jConfigurationHelper;
import gobblin.yarn.event.DelegationTokenUpdatedEvent;
/**
* The Yarn ApplicationMaster class for Gobblin.
*
* <p>
* This class runs the {@link YarnService} for all Yarn-related stuffs like ApplicationMaster registration
* and un-registration and Yarn container provisioning.
* </p>
*
* @author Yinan Li
*/
@Alpha
public class GobblinApplicationMaster extends GobblinClusterManager {
private static final Logger LOGGER = LoggerFactory.getLogger(GobblinApplicationMaster.class);
public GobblinApplicationMaster(String applicationName, ContainerId containerId, Config config,
YarnConfiguration yarnConfiguration) throws Exception {
super(applicationName, containerId.getApplicationAttemptId().getApplicationId().toString(), config,
Optional.<Path>absent());
GobblinYarnLogSource gobblinYarnLogSource = new GobblinYarnLogSource();
if (gobblinYarnLogSource.isLogSourcePresent()) {
this.applicationLauncher
.addService(gobblinYarnLogSource.buildLogCopier(config, containerId, this.fs, this.appWorkDir));
}
this.applicationLauncher
.addService(buildYarnService(config, applicationName, this.applicationId, yarnConfiguration, this.fs));
if (UserGroupInformation.isSecurityEnabled()) {
LOGGER.info("Adding YarnContainerSecurityManager since security is enabled");
this.applicationLauncher.addService(buildYarnContainerSecurityManager(config, this.fs));
}
}
/**
* Build the {@link YarnService} for the Application Master.
*/
private YarnService buildYarnService(Config config, String applicationName, String applicationId,
YarnConfiguration yarnConfiguration, FileSystem fs)
throws Exception {
return new YarnService(config, applicationName, applicationId, yarnConfiguration, fs, this.eventBus);
}
/**
* Build the {@link YarnContainerSecurityManager} for the Application Master.
*/
private YarnContainerSecurityManager buildYarnContainerSecurityManager(Config config, FileSystem fs) {
return new YarnContainerSecurityManager(config, fs, this.eventBus);
}
@Override
protected MessageHandlerFactory getUserDefinedMessageHandlerFactory() {
return new ControllerUserDefinedMessageHandlerFactory();
}
/**
* A custom {@link MessageHandlerFactory} for {@link ControllerUserDefinedMessageHandler}s that
* handle messages of type {@link org.apache.helix.model.Message.MessageType#USER_DEFINE_MSG}.
*/
private class ControllerUserDefinedMessageHandlerFactory implements MessageHandlerFactory {
@Override
public MessageHandler createHandler(Message message, NotificationContext context) {
return new ControllerUserDefinedMessageHandler(message, context);
}
@Override
public String getMessageType() {
return Message.MessageType.USER_DEFINE_MSG.toString();
}
public List<String> getMessageTypes() {
return Collections.singletonList(getMessageType());
}
@Override
public void reset() {
}
/**
* A custom {@link MessageHandler} for handling user-defined messages to the controller.
*
* <p>
* Currently it handles the following sub types of messages:
*
* <ul>
* <li>{@link HelixMessageSubTypes#TOKEN_FILE_UPDATED}</li>
* </ul>
* </p>
*/
private class ControllerUserDefinedMessageHandler extends MessageHandler {
public ControllerUserDefinedMessageHandler(Message message, NotificationContext context) {
super(message, context);
}
@Override
public HelixTaskResult handleMessage() throws InterruptedException {
String messageSubType = this._message.getMsgSubType();
if (messageSubType.equalsIgnoreCase(HelixMessageSubTypes.TOKEN_FILE_UPDATED.toString())) {
LOGGER.info("Handling message " + HelixMessageSubTypes.TOKEN_FILE_UPDATED.toString());
eventBus.post(new DelegationTokenUpdatedEvent());
HelixTaskResult helixTaskResult = new HelixTaskResult();
helixTaskResult.setSuccess(true);
return helixTaskResult;
}
throw new IllegalArgumentException(String.format("Unknown %s message subtype: %s",
Message.MessageType.USER_DEFINE_MSG.toString(), messageSubType));
}
@Override
public void onError(Exception e, ErrorCode code, ErrorType type) {
LOGGER.error(
String.format("Failed to handle message with exception %s, error code %s, error type %s", e, code, type));
}
}
}
private static Options buildOptions() {
Options options = new Options();
options.addOption("a", GobblinClusterConfigurationKeys.APPLICATION_NAME_OPTION_NAME, true, "Yarn application name");
return options;
}
private static void printUsage(Options options) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp(GobblinApplicationMaster.class.getSimpleName(), options);
}
public static void main(String[] args) throws Exception {
Options options = buildOptions();
try {
CommandLine cmd = new DefaultParser().parse(options, args);
if (!cmd.hasOption(GobblinClusterConfigurationKeys.APPLICATION_NAME_OPTION_NAME)) {
printUsage(options);
System.exit(1);
}
Log4jConfigurationHelper.updateLog4jConfiguration(GobblinApplicationMaster.class,
GobblinYarnConfigurationKeys.GOBBLIN_YARN_LOG4J_CONFIGURATION_FILE,
GobblinYarnConfigurationKeys.GOBBLIN_YARN_LOG4J_CONFIGURATION_FILE);
LOGGER.info(JvmUtils.getJvmInputArguments());
ContainerId containerId =
ConverterUtils.toContainerId(System.getenv().get(ApplicationConstants.Environment.CONTAINER_ID.key()));
try (GobblinApplicationMaster applicationMaster = new GobblinApplicationMaster(
cmd.getOptionValue(GobblinClusterConfigurationKeys.APPLICATION_NAME_OPTION_NAME), containerId,
ConfigFactory.load(), new YarnConfiguration())) {
applicationMaster.start();
}
} catch (ParseException pe) {
printUsage(options);
System.exit(1);
}
}
}