Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.exactpro.sf.embedded.updater.UpdateService Maven / Gradle / Ivy
/*******************************************************************************
* Copyright 2009-2019 Exactpro (Exactpro Systems Limited)
*
* Licensed 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.exactpro.sf.embedded.updater;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.exactpro.sf.center.IVersion;
import com.exactpro.sf.common.util.EPSCommonException;
import com.exactpro.sf.configuration.workspace.FolderType;
import com.exactpro.sf.configuration.workspace.IWorkspaceDispatcher;
import com.exactpro.sf.configuration.workspace.WorkspaceSecurityException;
import com.exactpro.sf.configuration.workspace.WorkspaceStructureException;
import com.exactpro.sf.embedded.IEmbeddedService;
import com.exactpro.sf.embedded.configuration.ServiceStatus;
import com.exactpro.sf.embedded.updater.configuration.UpdateServiceSettings;
import com.exactpro.sf.embedded.updater.exception.UpdateInProgressException;
import com.exactpro.sf.services.ITaskExecutor;
import com.exactpro.sf.storage.IMapableSettings;
import com.exactpro.sf.util.DateTimeUtility;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.module.kotlin.KotlinModule;
public class UpdateService implements IEmbeddedService {
private static final Logger logger = LoggerFactory.getLogger(UpdateService.class);
public static final String TIME_PATTERN = "HH:mm";
public static final DateTimeFormatter TIME_FORMATTER = DateTimeUtility.createFormatter(TIME_PATTERN);
private static final long DEFAULT_COLLECT_DATA_TIMEOUT = 3;
private static final String DEPLOYER_CFG_FILE = "deployer.cfg.xml";
private static final String PATH_PARAMETER = "Path";
private static final String SERVER_URL_PARAMETER = "ServerURL";
private static final String PROTOCOL_PREFIX = "http://";
private static final UpdatedState EMPTY = UpdatedState.EMPTY_STATE;
private static final ObjectMapper MAPPER = new ObjectMapper()
.registerModule(new KotlinModule());
private final IWorkspaceDispatcher wd;
/**
* Relative (to ROOT folder) path
*/
private final String deployerPath;
private final ITaskExecutor taskExecutor;
private final List currentComponents;
private Future> updateCheckerFuture;
private Future> updateFuture;
private volatile UpdatedState componentUpdateInfos = EMPTY;
private final AtomicReference serviceStatus = new AtomicReference<>(ServiceStatus.Disconnected);
private volatile LocalDateTime lastCheckTime;
private volatile String errorMsg;
private volatile String updateErrorMsg;
private volatile boolean updating;
private boolean enableAutoUpdate;
private DayOfWeek dayForUpdate;
private LocalTime fromTime;
private LocalTime toTime;
private File deployerFile;
private UpdateServiceSettings settings;
public UpdateService(IWorkspaceDispatcher workspaceDispatcher, HierarchicalConfiguration config, ITaskExecutor taskExecutor, Collection currentVersions) {
this.wd = workspaceDispatcher;
this.taskExecutor = taskExecutor;
deployerPath = config.getString(PATH_PARAMETER, "");
currentComponents = currentVersions.stream()
.map(ComponentUpdateInfo::new)
.collect(Collectors.toList());
}
@Override
public synchronized void init() {
try {
logger.info("Start initializing...");
if (getStatus() == ServiceStatus.Connected || getStatus() == ServiceStatus.Checking) {
throw new IllegalStateException("Already init");
}
if (StringUtils.isEmpty(deployerPath)) {
throw new IllegalStateException("Path to deployer wasn't set");
}
try {
deployerFile = wd.getFile(FolderType.ROOT, deployerPath);
} catch (FileNotFoundException e) {
logger.warn("Deployer wasn't found in directory {}", deployerPath, e);
throw new EPSCommonException("Deployer wasn't found", e);
}
readDeployerConfiguration();
enableAutoUpdate = settings.isEnableAutoUpdate();
if (enableAutoUpdate) {
dayForUpdate = DayOfWeek.valueOf(Objects.requireNonNull(settings.getDayOfWeek(), "'Day of week ' parameter"));
fromTime = LocalTime.parse(Objects.requireNonNull(settings.getFromTime(), "'From time ' parameter"), TIME_FORMATTER);
toTime = LocalTime.parse(Objects.requireNonNull(settings.getToTime(), "'To time ' parameter"), TIME_FORMATTER);
logger.debug("Auto update is enabled. Day for update {}; Interval start: {}; Interval end: {}", dayForUpdate, fromTime, toTime);
}
changeStatus(ServiceStatus.Connected);
checkForUpdates(false);
startCheckUpdateTask();
this.errorMsg = "";
logger.info("Initialized");
} catch (Exception e) {
logger.error("Can't init Update Service", e);
setError(e);
throw new EPSCommonException(e);
}
}
private void changeStatus(ServiceStatus serviceStatus) {
this.serviceStatus.set(serviceStatus);
}
public void checkUpdates() {
try {
checkForUpdates(false);
} catch (UpdateInProgressException e) {
logger.error("Update in progress", e);
throw e;
} catch (Exception e) {
logger.error("Error during user check request", e);
setError(e);
throw new EPSCommonException(e);
}
}
private void checkForUpdates(boolean autoUpdateIfNeed) {
if (updating) {
throw new UpdateInProgressException();
}
UpdatedState updateInfos = EMPTY;
try {
ServiceStatus prevStatus = serviceStatus.getAndUpdate(status -> ServiceStatus.Checking);
if (prevStatus == ServiceStatus.Checking) {
return;
}
logger.info("Collecting data...");
String collectDataParameters = String.join(StringUtils.SPACE, createCollectDataParameters());
logger.debug("Exec {} with params {}", deployerFile.getAbsolutePath(), collectDataParameters);
Process collectDataProcess = Runtime.getRuntime().exec(String.format("%s %s", deployerFile.getAbsolutePath(), collectDataParameters));
boolean isFinished = collectDataProcess.waitFor(DEFAULT_COLLECT_DATA_TIMEOUT, TimeUnit.HOURS);
int exitCode = isFinished ? collectDataProcess.exitValue() : -1;
if (exitCode != 0) {
if (isFinished) {
logger.error("Error during collecting sailfish data. Exit code: {}", exitCode);
} else {
logger.error("The time limit for collecting data has been exceeded");
}
}
logger.info("Checking for updates");
String checkUpdateParameters = String.join(StringUtils.SPACE, createCheckUpdateParameters());
logger.debug("Exec {} with params {}", deployerFile.getAbsolutePath(), checkUpdateParameters);
Process updateProcess = Runtime.getRuntime().exec(String.format("%s %s", deployerFile.getAbsolutePath(), checkUpdateParameters));
String line;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(updateProcess.getInputStream()))) {
line = reader.readLine();
}
exitCode = updateProcess.waitFor();
if (exitCode != 0) {
throw new RuntimeException("Deployer got an error during checking for updates; Exit code: " + exitCode);
}
logger.debug("Deployer output: {}", line);
updateInfos = parseComponentsInfo(line, currentComponents);
if (autoUpdateIfNeed && needUpdate(updateInfos) && isTimeForAutoUpdate(DateTimeUtility.nowLocalDateTime())) {
logger.info("Execute scheduled update");
update();
}
} catch (InterruptedException e) {
logger.warn("Check for update was interrupted", e);
} catch (Exception e) {
logger.error("Can't check updates", e);
throw new EPSCommonException("Can't check updates", e);
} finally {
lastCheckTime = DateTimeUtility.nowLocalDateTime();
this.componentUpdateInfos = updateInfos;
changeStatus(ServiceStatus.Connected);
}
}
private static boolean needUpdate(UpdatedState updatedState) {
return !updatedState.getNeedUpdate().isEmpty() || !updatedState.getAdded().isEmpty();
}
public synchronized void update() {
if (getStatus() == ServiceStatus.Error || getStatus() == ServiceStatus.Disconnected) {
throw new IllegalStateException("Can't execute update in current state: " + serviceStatus);
}
if(updating) {
throw new IllegalStateException("Update process already started");
}
logger.info("Start updating...");
this.updating = true;
updateFuture = taskExecutor.addTask(new UpdateTask());
}
private boolean isTimeForAutoUpdate(LocalDateTime dateTime) {
if (!enableAutoUpdate) {
return false;
}
LocalTime time = dateTime.toLocalTime();
return DayOfWeek.from(dateTime) == dayForUpdate && time.isAfter(fromTime) && time.isBefore(toTime);
}
public LocalDateTime getLastCheckTime() {
return lastCheckTime;
}
private List createCheckUpdateParameters() {
List builder = createCommonParameters("check");
return builder;
}
private List createUpdateParameters() {
List builder = createCommonParameters("update");
addParameter(builder, "startTomcat");
addParameter(builder, "checkTomcat");
return builder;
}
private List createActualizingDeployerParameters(File tmpDir) {
List builder = createCommonParameters("actualize");
addParameter(builder, "moveDest", tmpDir.getAbsolutePath());
return builder;
}
private List createCollectDataParameters() {
return createCommonParameters("collect");
}
private List createCommonParameters(String mode) {
try {
File deployerCfg = wd.getFile(FolderType.CFG, DEPLOYER_CFG_FILE);
File logsFolder = wd.getFolder(FolderType.LOGS);
File reportDir = wd.getFolder(FolderType.REPORT);
List parameters = new ArrayList<>();
addParameter(parameters, "cfg", deployerCfg.getAbsolutePath());
addParameter(parameters, "log", logsFolder.getAbsolutePath());
addParameter(parameters, "report", reportDir.getAbsolutePath());
addParameter(parameters, "serverUrl", getServerURL());
addParameter(parameters, "mode", mode);
addParameter(parameters, "quiet");
return parameters;
} catch (FileNotFoundException | WorkspaceStructureException e) {
throw new EPSCommonException("Can't set parameters", e);
}
}
private String getServerURL() {
return PROTOCOL_PREFIX + settings.getHost() + ":" + settings.getPort();
}
private void addParameter(List parameters, String paramName) {
addParameter(parameters, paramName, null);
}
private void addParameter(List parameters, String paramName, String paramValue) {
Objects.requireNonNull(parameters, "'Builder' parameter");
Objects.requireNonNull(paramName, "'Parameter name' parameter");
parameters.add("-" + paramName);
if (paramValue != null) {
parameters.add(paramValue);
}
}
private void readDeployerConfiguration() {
XMLConfiguration deployerCfg = new XMLConfiguration();
try {
File configFile = wd.getFile(FolderType.CFG, DEPLOYER_CFG_FILE);
try (InputStream stream = new FileInputStream(configFile)) {
deployerCfg.load(stream);
}
} catch (ConfigurationException | WorkspaceSecurityException e) {
throw new EPSCommonException("Could not read [" + DEPLOYER_CFG_FILE + "] configuration file", e);
} catch (FileNotFoundException e) {
throw new EPSCommonException("Can't find deployer cfg file in SF workspaces", e);
} catch (Exception e) {
throw new EPSCommonException("Can't load deployer configuration", e);
}
if (settings != null && settings.getHost() == null) {
String updateServerURLString = deployerCfg.getString(SERVER_URL_PARAMETER, "");
try {
URL url = new URL(updateServerURLString);
settings.setHost(url.getHost());
settings.setPort(url.getPort());
} catch (MalformedURLException e) {
throw new EPSCommonException("Wrong URL format in deployer configuration: " + updateServerURLString, e);
}
}
}
public String getUpdateErrorMsg() {
return updateErrorMsg;
}
public boolean isUpdateRequire() {
return needUpdate(componentUpdateInfos);
}
public boolean isUpdating() {
return updating;
}
@Override
public synchronized void tearDown() {
if (getStatus() == ServiceStatus.Disconnected) {
return;
}
logger.info("Tear down");
cancelCheckUpdateTask();
if(updateFuture != null) {
updateFuture.cancel(true);
this.updateFuture = null;
}
this.componentUpdateInfos = EMPTY;
changeStatus(ServiceStatus.Disconnected);
logger.info("Update service disposed");
}
@Override
public boolean isConnected() {
return getStatus() == ServiceStatus.Connected;
}
@Override
public ServiceStatus getStatus() {
return serviceStatus.get();
}
@Override
public String getErrorMsg() {
return errorMsg;
}
private synchronized void cancelCheckUpdateTask() {
if(updateCheckerFuture != null) {
logger.info("Cancelling update checking task...");
updateCheckerFuture.cancel(true);
try {
updateCheckerFuture.get();
} catch (CancellationException e) {
logger.info("Update checker task was cancelled", e);
} catch (InterruptedException e) {
logger.info("Update checker task was interrupted", e);
} catch (ExecutionException e) {
logger.info("Update checker task finished with an error", e);
}
this.updateCheckerFuture = null;
} else {
logger.info("Update checking task was already cancelled");
}
}
private synchronized void startCheckUpdateTask() {
if (updateCheckerFuture == null) {
logger.info("Creating update checking task...");
this.updateCheckerFuture = taskExecutor.addRepeatedTask(new UpdateChecker(), settings.getCheckUpdateTimeout(),
settings.getCheckUpdateTimeout(), TimeUnit.valueOf(settings.getTimeUnit()));
} else {
logger.warn("Update checking task was already created. Can't create another one");
}
}
private void setError(Throwable t) {
changeStatus(ServiceStatus.Error);
StringBuilder error = new StringBuilder();
error.append(t.getMessage());
if (t.getCause() != null) {
error.append(StringUtils.SPACE)
.append("(")
.append(t.getCause().getMessage())
.append(")");
}
this.errorMsg = error.toString();
}
@Override
public void setSettings(IMapableSettings settings) {
this.settings = (UpdateServiceSettings) settings;
}
public UpdateServiceSettings getSettings() {
return settings;
}
public List getComponentUpdateInfos() {
return componentUpdateInfos.getNeedUpdate();
}
public List getRemovedComponents() {
return componentUpdateInfos.getRemoved();
}
public List getAddedComponents() {
return componentUpdateInfos.getAdded();
}
public List getCurrentComponents() {
return currentComponents;
}
private static UpdatedState parseComponentsInfo(String data, List currentComponents) throws IOException {
List collectedState = MAPPER.readValue(data, MAPPER.getTypeFactory().constructCollectionType(List.class, ComponentUpdateInfo.class));
return new UpdatedState(
calculateUpdatedComponents(currentComponents, collectedState),
calculateAddedComponents(currentComponents, collectedState),
calculateRemovedComponents(currentComponents, collectedState)
);
}
@NotNull
private static List calculateUpdatedComponents(List currentComponents, List componentUpdateInfos) {
List list = new ArrayList<>();
for (ComponentUpdateInfo cmp : componentUpdateInfos) {
if (currentComponents.stream().anyMatch(curCmp -> curCmp.getName().equals(cmp.getName()) && !curCmp.equals(cmp))) {
list.add(cmp);
}
}
return list;
}
@NotNull
private static List calculateRemovedComponents(List currentComponents, List componentUpdateInfos) {
List list = new ArrayList<>();
for (ComponentUpdateInfo cmp : currentComponents) {
if (!componentUpdateInfos.isEmpty()
&& componentUpdateInfos.stream().noneMatch(updateCmp -> updateCmp.getName().equals(cmp.getName()))) {
list.add(cmp);
}
}
return list;
}
@NotNull
private static List calculateAddedComponents(List currentComponents, List componentUpdateInfos) {
List list = new ArrayList<>();
for (ComponentUpdateInfo cmp : componentUpdateInfos) {
if (currentComponents.stream().noneMatch(curCmp -> curCmp.getName().equals(cmp.getName()))) {
list.add(cmp);
}
}
return list;
}
public boolean hasCriticalError() {
return getStatus() == ServiceStatus.Error
&& (StringUtils.isEmpty(deployerPath)
|| deployerFile == null);
}
private class UpdateChecker implements Runnable {
@Override
public void run() {
try {
logger.info("Scheduled run");
checkForUpdates(true);
} catch (UpdateInProgressException e) {
logger.error("Update in progress", e);
} catch (Exception e) {
logger.error(e.getMessage(), e);
setError(e);
}
}
}
private class UpdateTask implements Runnable {
@Override
public void run() {
try {
cancelCheckUpdateTask();
logger.info("Actualizing deployer...");
Path tempDirectory = Files.createTempDirectory("deployer");
String updateDeployerParameters = String.join(StringUtils.SPACE, createActualizingDeployerParameters(tempDirectory.toFile()));
logger.debug("Exec {} with params {}", deployerFile.getAbsolutePath(), updateDeployerParameters);
Process actualizeDeployer = Runtime.getRuntime().exec(String.format("%s %s", deployerFile.getAbsolutePath(), updateDeployerParameters));
int exitCode = actualizeDeployer.waitFor();
if (exitCode != 0) {
throw new EPSCommonException("Can't actualize deployer; Exit code: " + exitCode);
}
Path actualDeployerPath = tempDirectory.resolve(deployerFile.getName()).toAbsolutePath();
logger.info("Updating Sailfish...");
List updateParameters = createUpdateParameters();
logger.debug("Exec {} with params {}", actualDeployerPath, updateParameters);
File err = Files.createTempFile("err", null).toFile().getAbsoluteFile();
File out = Files.createTempFile("out", null).toFile().getAbsoluteFile();
logger.debug("Out: {}; Err: {}", out, err);
Process updateSailfish = new ProcessBuilder(
ArrayUtils.insert(0, updateParameters.toArray(new String[0]), actualDeployerPath.toString()))
.redirectError(err)
.redirectOutput(out)
.start();
exitCode = updateSailfish.waitFor();
if (exitCode != 0) {
throw new EPSCommonException("Problem during updating Sailfish; Exit code: " + exitCode);
}
} catch (InterruptedException e) {
logger.info("Update interrupted", e);
} catch (RuntimeException e) {
logger.error(e.getMessage(), e);
updateFailed(e);
} catch (IOException e) {
logger.error("Can't execute update", e);
updateFailed(e);
} finally {
UpdateService.this.updating = false;
}
}
private void updateFailed(Throwable e) {
String msg = "Last attempt to update Sailfish was failed: " + e.getMessage();
if (e.getCause() != null) {
msg += " (" + e.getCause().getMessage() + ")";
}
UpdateService.this.updateErrorMsg = msg;
startCheckUpdateTask();
}
}
}