/*
*
* * Copyright 2000-2014 JetBrains s.r.o.
* *
* * 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 jetbrains.buildServer.clouds.base.tasks;
import com.intellij.openapi.diagnostic.Logger;
import java.util.*;
import jetbrains.buildServer.Used;
import jetbrains.buildServer.clouds.InstanceStatus;
import jetbrains.buildServer.clouds.base.AbstractCloudClient;
import jetbrains.buildServer.clouds.base.AbstractCloudImage;
import jetbrains.buildServer.clouds.base.AbstractCloudInstance;
import jetbrains.buildServer.clouds.base.connector.AbstractInstance;
import jetbrains.buildServer.clouds.base.connector.CloudApiConnector;
import jetbrains.buildServer.util.Disposable;
import org.jetbrains.annotations.NotNull;
/**
* @author Sergey.Pak
* Date: 7/22/2014
* Time: 1:52 PM
*/
public class UpdateInstancesTask< G extends AbstractCloudInstance<T>,
T extends AbstractCloudImage<G,?>,
F extends AbstractCloudClient<G, T, ?>
> implements Runnable {
private static final Logger LOG = Logger.getInstance(UpdateInstancesTask.class.getName());
private static final long STUCK_STATUS_TIME = 10*60*1000l; // 2 minutes;
@NotNull protected final CloudApiConnector<T, G> myConnector;
@NotNull protected final F myClient;
@Used("Tests")
private final long myStuckTime;
@Used("Tests")
private final boolean myRethrowException;
public UpdateInstancesTask(@NotNull final CloudApiConnector<T, G> connector,
@NotNull final F client) {
this(connector, client, STUCK_STATUS_TIME, false);
}
@Used("Tests")
public UpdateInstancesTask(@NotNull final CloudApiConnector<T, G> connector,
@NotNull final F client,
@Used("Tests")
final long stuckTimeMillis,
@Used("Tests")
final boolean rethrowException) {
myConnector = connector;
myClient = client;
myStuckTime = stuckTimeMillis;
myRethrowException = rethrowException;
}
public void run() {
final Map<InstanceStatus, List<String>> instancesByStatus = new HashMap<InstanceStatus, List<String>>();
try {
List<T> goodImages = new ArrayList<>();
final Collection<T> images = getImages();
for (final T image : images) {
image.updateErrors(myConnector.checkImage(image));
if (image.getErrorInfo() != null) {
continue;
}
goodImages.add(image);
}
final Map<T, Map<String, AbstractInstance>> groupedInstances = myConnector.fetchInstances(goodImages);
groupedInstances.forEach((img, instMap)->{
LOG.debug(String.format("Instances for [%s]:[%s]", img.getId(), String.join(",", instMap.keySet())));
});
for (T image : goodImages) {
Map<String, AbstractInstance> realInstances = groupedInstances.get(image);
if (realInstances == null){
realInstances = Collections.emptyMap();
}
for (String realInstanceName : realInstances.keySet()) {
final G instance = image.findInstanceById(realInstanceName);
final AbstractInstance realInstance = realInstances.get(realInstanceName);
if (instance == null) {
continue;
}
final InstanceStatus realInstanceStatus = realInstance.getInstanceStatus();
if (!instancesByStatus.containsKey(realInstanceStatus)){
instancesByStatus.put(realInstanceStatus, new ArrayList<String>());
}
instancesByStatus.get(realInstanceStatus).add(realInstanceName);
if ((isStatusPermanent(instance.getStatus()) || isStuck(instance))
&& isStatusPermanent(realInstanceStatus)
&& realInstanceStatus != instance.getStatus()) {
LOG.info(String.format("Updated instance '%s' status to %s based on API information", realInstanceName, realInstanceStatus));
instance.setStatus(realInstanceStatus);
}
}
final Collection<G> instances = image.getInstances();
final Set<String> instancesToRemove = new HashSet<>();
for (final G cloudInstance : instances) {
try {
final String instanceName = cloudInstance.getName();
final AbstractInstance instance = realInstances.get(instanceName);
if (instance == null) {
if (cloudInstance.getStatus() != InstanceStatus.SCHEDULED_TO_START && cloudInstance.getStatus() != InstanceStatus.STARTING) {
instancesToRemove.add(instanceName);
}
continue;
}
cloudInstance.updateErrors(myConnector.checkInstance(cloudInstance));
if (instance.getStartDate() != null) {
cloudInstance.setStartDate(instance.getStartDate());
}
if (instance.getIpAddress() != null) {
cloudInstance.setNetworkIdentify(instance.getIpAddress());
}
} catch (Exception ex){
LOG.debug("Error processing VM " + cloudInstance.getName() + ": " + ex.toString());
}
}
final Map<String, InstanceStatus> statuses = myConnector.getInstanceStatusesIfExists(instancesToRemove);
for (String instanceName : instancesToRemove) {
final InstanceStatus currentStatus = statuses.get(instanceName);
if (currentStatus == null) {
image.removeInstance(instanceName);
}
}
image.detectNewInstances(realInstances);
}
myClient.updateErrors();
} catch (Exception ex){
if (myRethrowException){
// for tests
throw new RuntimeException(ex);
}
LOG.warn(ex.toString(), ex);
} finally {
//logging here:
for (InstanceStatus instanceStatus : instancesByStatus.keySet()) {
LOG.debug(String.format("Instances in '%s' status: %s", instanceStatus.getText(), Arrays.toString(instancesByStatus.get(instanceStatus).toArray())));
}
}
}
@NotNull
protected Collection<T> getImages() {
return myClient.getImages();
}
private static boolean isStatusPermanent(InstanceStatus status){
return status == InstanceStatus.STOPPED || status == InstanceStatus.RUNNING;
}
private boolean isStuck(G instance){
return (System.currentTimeMillis() - instance.getStatusUpdateTime().getTime()) > myStuckTime &&
(instance.getStatus() == InstanceStatus.STOPPING
|| instance.getStatus() == InstanceStatus.STARTING
|| instance.getStatus() == InstanceStatus.SCHEDULED_TO_STOP
|| instance.getStatus() == InstanceStatus.SCHEDULED_TO_START
);
}
}