blob: bf8147ac8534f2c72db63fe522e763632a778097 [file] [log] [blame]
// 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.cloud.hypervisor.kvm.resource.rolling.maintenance;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class RollingMaintenanceServiceExecutor extends RollingMaintenanceExecutorBase implements RollingMaintenanceExecutor {
private static final String servicePrefix = "cloudstack-rolling-maintenance";
private static final String resultsFileSuffix = "rolling-maintenance-results";
private static final String outputFileSuffix = "rolling-maintenance-output";
private static final Logger s_logger = Logger.getLogger(RollingMaintenanceServiceExecutor.class);
public RollingMaintenanceServiceExecutor(String hooksDir) {
super(hooksDir);
}
/**
* Generate and return escaped instance name to use on systemd service invokation
*/
private String generateInstanceName(String stage, String file, String payload) {
String instanceName = String.format("%s,%s,%s,%s,%s", stage, file, getTimeout(),
getResultsFilePath(), getOutputFilePath());
if (StringUtils.isNotBlank(payload)) {
instanceName += "," + payload;
}
return Script.runSimpleBashScript(String.format("systemd-escape '%s'", instanceName));
}
private String invokeService(String action, String stage, String file, String payload) {
s_logger.debug("Invoking rolling maintenance service for stage: " + stage + " and file " + file + " with action: " + action);
final OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
Script command = new Script("/bin/systemctl", s_logger);
command.add(action);
String service = servicePrefix + "@" + generateInstanceName(stage, file, payload);
command.add(service);
String result = command.execute(parser);
int exitValue = command.getExitValue();
s_logger.trace("Execution: " + command.toString() + " - exit code: " + exitValue +
": " + result + (StringUtils.isNotBlank(parser.getLines()) ? parser.getLines() : ""));
return StringUtils.isBlank(result) ? parser.getLines().replace("\n", " ") : result;
}
@Override
public Pair<Boolean, String> startStageExecution(String stage, File scriptFile, int timeout, String payload) {
checkHooksDirectory();
setTimeout(timeout);
String result = invokeService("start", stage, scriptFile.getAbsolutePath(), payload);
if (StringUtils.isNotBlank(result)) {
throw new CloudRuntimeException("Error starting stage: " + stage + " execution: " + result);
}
s_logger.trace("Stage " + stage + "execution started");
return new Pair<>(true, "OK");
}
private String getResultsFilePath() {
return getHooksDir() + resultsFileSuffix;
}
private String getOutputFilePath() {
return getHooksDir() + outputFileSuffix;
}
private String readFromFile(String filePath) {
StringBuilder contentBuilder = new StringBuilder();
try (Stream<String> stream = Files.lines( Paths.get(filePath), StandardCharsets.UTF_8)) {
stream.forEach(s -> contentBuilder.append(s).append("\n"));
} catch (IOException e) {
e.printStackTrace();
}
return contentBuilder.toString();
}
@Override
public String getStageExecutionOutput(String stage, File scriptFile) {
return readFromFile(getOutputFilePath());
}
@Override
public boolean isStageRunning(String stage, File scriptFile, String payload) {
String result = invokeService("is-active", stage, scriptFile.getAbsolutePath(), payload);
if (StringUtils.isNotBlank(result) && result.equals("failed")) {
String status = invokeService("status", stage, scriptFile.getAbsolutePath(), payload);
String errorMsg = "Stage " + stage + " execution failed, status: " + status;
s_logger.error(errorMsg);
throw new CloudRuntimeException(errorMsg);
}
return StringUtils.isNotBlank(result) && result.equals("active");
}
@Override
public boolean getStageExecutionSuccess(String stage, File scriptFile) {
String fileContent = readFromFile(getResultsFilePath());
if (StringUtils.isBlank(fileContent)) {
throw new CloudRuntimeException("Empty content in file " + getResultsFilePath());
}
fileContent = fileContent.replace("\n", "");
String[] parts = fileContent.split(",");
if (parts.length < 3) {
throw new CloudRuntimeException("Results file " + getResultsFilePath() + " unexpected content: " + fileContent);
}
if (!parts[0].equalsIgnoreCase(stage)) {
throw new CloudRuntimeException("Expected stage " + stage + " results but got stage " + parts[0]);
}
setAvoidMaintenance(Boolean.parseBoolean(parts[2]));
return Boolean.parseBoolean(parts[1]);
}
}