Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added channel close listener and interruption of the pipelines in case Aquarium was the cause #26

Merged
merged 1 commit into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.adobe.ci.aquarium.net;

import com.adobe.ci.aquarium.fish.client.model.ApplicationStatus;
import hudson.model.TaskListener;
import jenkins.model.CauseOfInterruption;

public class AquariumCauseOfInterruption extends CauseOfInterruption {
private static final long serialVersionUID = 1L;

private final ApplicationStatus status;
private final String description;

public AquariumCauseOfInterruption(ApplicationStatus status, String description) {
this.status = status;
this.description = description;
}

public ApplicationStatus getStatus() {
return status;
}
public String getDescription() {
return description;
}

@Override
public String getShortDescription() {
return "Interrupted by Aquarium, Application State: " + status.toString() + ": " + description;
}

@Override
public void print(TaskListener listener) {
listener.getLogger().println(getShortDescription());
}

@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
AquariumCauseOfInterruption that = (AquariumCauseOfInterruption) o;
return status.equals(that.status) && description.equals(that.description);
}

@Override
public int hashCode() {
return (status.toString()+description).hashCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.adobe.ci.aquarium.net;

import java.io.IOException;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.adobe.ci.aquarium.fish.client.model.ApplicationState;
import com.adobe.ci.aquarium.fish.client.model.ApplicationStatus;
import hudson.model.Executor;
import hudson.model.Result;
import hudson.remoting.Channel;

/**
* Class to listen for events in the computer channel and take measures in case anything goes wrong
*/
public class AquariumChannelListener extends Channel.Listener {

private static final Logger LOG = Logger.getLogger(AquariumChannelListener.class.getName());

AquariumComputer computer;

public AquariumChannelListener(AquariumComputer computer) {
this.computer = computer;
}

@Override
public void onClosed(Channel channel, IOException cause) {
// The channel was closed - let's find out the reason
AquariumCloud cloud;
if( computer == null || computer.getNode() == null ) {
// There is no node, so nothing is wrong
LOG.log(Level.FINE, "Channel is closed on computer: " + computer + " cause: " + cause);
return;
}
String app_uid = computer.getAppInfo().getString("ApplicationUID");
if( app_uid.isEmpty() ) {
LOG.log(Level.SEVERE, "Unable to locate ApplicationUID for computer: " + computer);
return;
}
ApplicationState state;
try {
state = computer.getNode().getAquariumCloud().getClient().applicationStateGet(UUID.fromString(app_uid));
} catch (Exception e) {
LOG.log(Level.SEVERE, "Unable to request ApplicationState for ApplicationUID " + app_uid + " reason: " + e);
return;
}
LOG.log(Level.WARNING, "Channel is closed on Computer: " + computer + " with ApplicationUID: " + app_uid + ", State: " + state.getStatus() + ": " + state.getDescription() + ", Cause: " + cause);
computer.getListener().getLogger().println("AquariumChannelListener remote disconnected: ApplicationUID: " + app_uid + ", State: " + state.getStatus() + ": " + state.getDescription() + ", Cause: " + cause);

if( state.getStatus() == ApplicationStatus.ALLOCATED ) {
// Aborting all the executors since the Application was deallocated
for (Executor exec : computer.getAllExecutors()) {
exec.interrupt(Result.ABORTED, new AquariumCauseOfInterruption(state.getStatus(), state.getDescription()));
}
}
}
}
17 changes: 6 additions & 11 deletions src/main/java/com/adobe/ci/aquarium/net/AquariumCloud.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import jenkins.model.Jenkins;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.plaincredentials.FileCredentials;
import org.jetbrains.annotations.NotNull;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
Expand All @@ -49,8 +50,6 @@

import static org.apache.commons.lang.StringUtils.isEmpty;

class ServiceInstance {}

public class AquariumCloud extends Cloud {

private static final Logger LOG = Logger.getLogger(AquariumCloud.class.getName());
Expand Down Expand Up @@ -150,6 +149,7 @@ public void setLabelMappings(@CheckForNull List<LabelMapping> labels) {

@Override
public boolean canProvision(Label label) {
LOG.log(Level.FINEST, "Can provision label? " + label.toString());
try {
// Update the cache if time has come
if( this.labelsCachedUpdateTime < System.currentTimeMillis() ) {
Expand All @@ -176,7 +176,7 @@ public void updateLabelsCache() throws Exception {
out.add(LabelAtom.get(label.getName()));
});

if( out.size() == 0 ) {
if( out.isEmpty() ) {
LOG.log(Level.WARNING, "Cluster contains no labels - empty list was returned");
}

Expand Down Expand Up @@ -264,10 +264,6 @@ public String toString() {
return "AquariumCloud {Name='" + name + "'}";
}

public ServiceInstance getSI() {
return new ServiceInstance();
}

public void onTerminate(AquariumSlave slave) {
}

Expand All @@ -279,6 +275,7 @@ public DescriptorImpl getDescriptor() {
@Extension
public static final class DescriptorImpl extends Descriptor<Cloud> {

@NotNull
@Override
public String getDisplayName() {
return "Aquarium";
Expand Down Expand Up @@ -340,8 +337,7 @@ public ListBoxModel doFillCredentialsIdItems(@AncestorInPath ItemGroup context,
ACL.SYSTEM,
context,
StandardCredentials.class,
serverUrl != null ? URIRequirementBuilder.fromUri(serverUrl).build()
: Collections.EMPTY_LIST,
serverUrl != null ? URIRequirementBuilder.fromUri(serverUrl).build() : Collections.emptyList(),
CredentialsMatchers.anyOf(
CredentialsMatchers.instanceOf(StandardUsernamePasswordCredentials.class)
)
Expand All @@ -359,8 +355,7 @@ public ListBoxModel doFillCaCredentialsIdItems(@AncestorInPath ItemGroup context
ACL.SYSTEM,
context,
StandardCredentials.class,
serverUrl != null ? URIRequirementBuilder.fromUri(serverUrl).build()
: Collections.EMPTY_LIST,
serverUrl != null ? URIRequirementBuilder.fromUri(serverUrl).build() : Collections.emptyList(),
CredentialsMatchers.anyOf(
CredentialsMatchers.instanceOf(FileCredentials.class)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.jenkinsci.plugins.workflow.support.steps.ExecutorStepExecution.PlaceholderTask;
import org.jetbrains.annotations.NotNull;

import java.io.PrintStream;
import java.util.logging.Level;
Expand Down Expand Up @@ -120,12 +121,13 @@ public String toString() {
return String.format("AquariumComputer name: %s slave: %s", getName(), getNode());
}

@NotNull
@Override
public ACL getACL() {
final ACL base = super.getACL();
return new ACL() {
@Override
public boolean hasPermission(Authentication a, Permission permission) {
public boolean hasPermission(@NotNull Authentication a, @NotNull Permission permission) {
return permission == Computer.CONFIGURE ? false : base.hasPermission(a,permission);
}
};
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/com/adobe/ci/aquarium/net/AquariumLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public synchronized void launch(SlaveComputer computer, TaskListener listener) {
node.setApplicationUID(app.getUID());

// Notify computer log that the request for Application was sent
listener.getLogger().println("Aquarium Application was requested: " + app.getUID() + " with Label: " + label.getName() + "#" + label.getVersion());
listener.getLogger().println("Aquarium Application was requested: " + app.getUID() + " with Label: " + label.getName() + ":" + label.getVersion());
JSONObject app_info = new JSONObject();
app_info.put("ApplicationUID", app.getUID().toString());
app_info.put("LabelName", label.getName());
Expand Down Expand Up @@ -166,6 +166,15 @@ public synchronized void launch(SlaveComputer computer, TaskListener listener) {
// agent termination if no workload was assigned to it.
node.setRetentionStrategy(new OnceRetentionStrategy(5));

// Adding listener to the channel to catch any kind of interruptions
if( computer.getChannel() != null ) {
computer.getChannel().addListener(new AquariumChannelListener(comp));
} else {
LOG.log(Level.WARNING, "Unable to set channel listener since channel is null");
}
// Print data again because was cleaned when agent connected
listener.getLogger().println("Aquarium Application: " + app.getUID() + " with Label: " + label.getName() + ":" + label.getVersion());
listener.getLogger().println("Aquarium LabelDefinition: " + label.getDefinitions().get(res.getDefinitionIndex()));
computer.setAcceptingTasks(true);
launched = true;

Expand Down
Loading