/*
* 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.ambari.server.checks;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.apache.ambari.server.audit.AuditLoggerModule;
import org.apache.ambari.server.controller.ControllerModule;
import org.apache.ambari.server.orm.DBAccessor;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.persist.PersistService;
/**
* Mpack Install Checker
*/
public class MpackInstallChecker {
private static final String MPACK_STACKS_ARG = "mpack-stacks";
private static final Logger LOG = LoggerFactory.getLogger
(MpackInstallChecker.class);
private PersistService persistService;
private DBAccessor dbAccessor;
private Injector injector;
private Connection connection;
private boolean errorsFound = false;
private static Options getOptions() {
Options options = new Options();
options.addOption(Option.builder().longOpt(MPACK_STACKS_ARG).desc(
"List of stacks defined in the management pack").required().type(String.class).hasArg().valueSeparator(' ').build());
return options;
}
private static MpackContext processArguments(String... args) {
CommandLineParser cmdLineParser = new DefaultParser();
HelpFormatter formatter = new HelpFormatter();
MpackContext ctx = null;
try {
CommandLine line = cmdLineParser.parse(getOptions(), args);
String mpackStacksStr = (String) line.getParsedOptionValue(MPACK_STACKS_ARG);
HashSet<String> stacksInMpack = new HashSet<>(Arrays.asList(mpackStacksStr.split(",")));
ctx = new MpackContext(stacksInMpack);
} catch (Exception exp) {
System.err.println("Parsing failed. Reason: " + exp.getMessage());
LOG.error("Parsing failed. Reason: ", exp);
System.exit(1);
}
return ctx;
}
public boolean isErrorsFound() {
return errorsFound;
}
@Inject
public MpackInstallChecker(DBAccessor dbAccessor,
Injector injector,
PersistService persistService) {
this.dbAccessor = dbAccessor;
this.injector = injector;
this.persistService = persistService;
}
/**
* Extension of audit logger module
*/
public static class MpackCheckerAuditModule extends AuditLoggerModule {
public MpackCheckerAuditModule() throws Exception {
}
@Override
protected void configure() {
super.configure();
}
}
public void startPersistenceService() {
persistService.start();
}
public void stopPersistenceService() {
persistService.stop();
}
public Connection getConnection() {
if (connection == null) {
if (dbAccessor == null) {
dbAccessor = injector.getInstance(DBAccessor.class);
}
connection = dbAccessor.getConnection();
}
return connection;
}
/**
* Check if any clusters are deployed with a stack that is not included in the management pack
* @param stacksInMpack List of stacks included in the management pack
*/
public void checkClusters(HashSet<String> stacksInMpack) {
ResultSet rs = null;
Statement statement = null;
Map<String, Map<String, String>> clusterStackInfo = new HashMap<>();
String GET_STACK_NAME_VERSION_QUERY = "select c.cluster_name, s.stack_name, s.stack_version from clusters c " +
"join stack s on c.desired_stack_id = s.stack_id";
Connection conn = getConnection();
try {
statement = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
rs = statement.executeQuery(GET_STACK_NAME_VERSION_QUERY);
if (rs != null) {
while (rs.next()) {
Map<String, String> stackInfoMap = new HashMap<>();
stackInfoMap.put(rs.getString("stack_name"), rs.getString("stack_version"));
clusterStackInfo.put(rs.getString("cluster_name"), stackInfoMap);
}
}
for (Map.Entry<String, Map<String, String>> clusterStackInfoEntry : clusterStackInfo.entrySet()) {
String clusterName = clusterStackInfoEntry.getKey();
Map<String, String> stackInfo = clusterStackInfoEntry.getValue();
String stackName = stackInfo.keySet().iterator().next();
String stackVersion = stackInfo.get(stackName);
if(!stacksInMpack.contains(stackName)) {
String errorMsg = String.format("This Ambari instance is already managing the cluster %s that has the " +
"%s-%s stack installed on it. The management pack you are attempting to install only contains stack " +
"definitions for %s. Since this management pack does not contain a stack that has already being " +
"deployed by Ambari, the --purge option would cause your existing Ambari installation to be unusable. " +
"Due to that we cannot install this management pack.",
clusterName, stackName, stackVersion, stacksInMpack.toString());
LOG.error(errorMsg);
System.err.println(errorMsg);
errorsFound = true;
}
}
} catch (SQLException e) {
System.err.println("SQL Exception occured during check for validating installed clusters. Reason: " + e.getMessage());
LOG.error("SQL Exception occured during check for validating installed clusters. Reason: ", e);
errorsFound = true;
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
System.err.println("SQL Exception occurred during result set closing procedure. Reason: " + e.getMessage());
LOG.error("SQL Exception occurred during result set closing procedure. Reason: " , e);
errorsFound = true;
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
System.err.println("SQL Exception occurred during statement closing procedure. Reason: " + e.getMessage());
LOG.error("SQL Exception occurred during statement closing procedure. Reason: ", e);
errorsFound = true;
}
}
}
}
/**
* Main method for management pack installation checker
*/
public static void main(String[] args) throws Exception {
Injector injector = Guice.createInjector(new ControllerModule(), new MpackCheckerAuditModule());
MpackInstallChecker mpackInstallChecker = injector.getInstance(MpackInstallChecker.class);
MpackContext mpackContext = processArguments(args);
mpackInstallChecker.startPersistenceService();
mpackInstallChecker.checkClusters(mpackContext.getStacksInMpack());
mpackInstallChecker.stopPersistenceService();
if(mpackInstallChecker.isErrorsFound()) {
LOG.error("Mpack installation checker failed!");
System.err.println("Mpack installation checker failed!");
System.exit(1);
} else {
LOG.info("No errors found");
System.out.println("No errors found");
}
}
/**
* Context object that encapsulates values passed in as arguments to the {@link MpackInstallChecker} class.
*/
private static class MpackContext {
private HashSet<String> stacksInMpack;
public MpackContext(HashSet<String> stacksInMpack) {
this.stacksInMpack = stacksInMpack;
}
public HashSet<String> getStacksInMpack() {
return stacksInMpack;
}
}
}