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

[WIP] New Elastic Agent API #42

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
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
Expand Up @@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.thoughtworks.gocd.elasticagent.azure;

import com.thoughtworks.gocd.elasticagent.azure.executors.ServerPingRequestExecutor;
Expand All @@ -22,22 +21,24 @@
import com.thoughtworks.gocd.elasticagent.azure.models.ServerInfo;
import com.thoughtworks.gocd.elasticagent.azure.models.StatusReport;
import com.thoughtworks.gocd.elasticagent.azure.requests.CreateAgentRequest;

import java.io.IOException;


/**
* Plugin implementors should implement these methods to interface to your cloud.
* This interface is merely a suggestion for a very simple plugin. You may change it to your needs.
* Plugin implementors should implement these methods to interface to your
* cloud. This interface is merely a suggestion for a very simple plugin. You
* may change it to your needs.
*/
public interface AgentInstances<T> {

/**
* This message is sent to request creation of an agent instance.
* Implementations may, at their discretion choose to not spin up an agent instance.
* Implementations may, at their discretion choose to not spin up an agent
* instance.
* <p>
* So that instances created are auto-registered with the server, the agent instance MUST have an
* <code>autoregister.properties</code> file.
* @param request the request object
* So that instances created are auto-registered with the server, the agent
* instance MUST have an <code>autoregister.properties</code> file.
*
* @param request the request object
* @param settings   the plugin settings object
* @param serverInfo the server info object
*/
Expand All @@ -46,47 +47,51 @@ public interface AgentInstances<T> {
/**
* This message is sent when the plugin needs to terminate the agent instance.
*
* @param agentId the elastic agent id
* @param agentId the elastic agent id
* @param settings the plugin settings object
*/
void terminate(String agentId, PluginSettings settings) throws Exception;

AzureInstance addTag(PluginSettings settings, String agentId, String tagName, String tagValue) throws IOException;

/**
* This message is sent from the {@link ServerPingRequestExecutor}
* to terminate instances that did not register with the server after a timeout. The timeout may be configurable and
* set via the {@link PluginSettings} instance that is passed in.
* This message is sent from the {@link ServerPingRequestExecutor} to
* terminate instances that did not register with the server after a timeout.
* The timeout may be configurable and set via the {@link PluginSettings}
* instance that is passed in.
*
* @param settings the plugin settings object
* @param agents the list of all the agents
* @param agents the list of all the agents
*/
void terminateUnregisteredInstances(PluginSettings settings, Agents agents) throws Exception;

/**
* This message is sent from the {@link ServerPingRequestExecutor}
* to filter out any new agents, that have registered before the timeout period. The timeout may be configurable and
* set via the {@link PluginSettings} instance that is passed in.
* This message is sent from the {@link ServerPingRequestExecutor} to filter
* out any new agents, that have registered before the timeout period. The
* timeout may be configurable and set via the {@link PluginSettings} instance
* that is passed in.
*
* @param settings the plugin settings object
* @param agents the list of all the agents
* @return a list of agent instances which were created after {@link PluginSettings#getAutoRegisterPeriod()} ago.
* @param agents the list of all the agents
* @return a list of agent instances which were created after
* {@link PluginSettings#getAutoRegisterPeriod()} ago.
*/
Agents instancesToBeDisabled(PluginSettings settings, Agents agents);

/**
* This message is sent after plugin initialization time so that the plugin may connect to the cloud provider
* and fetch a list of all instances that have been spun up by this plugin (before the server was shut down).
* This call should be should ideally remember if the agent instances are refreshed, and do nothing if instances
* were previously refreshed.
* This message is sent after plugin initialization time so that the plugin
* may connect to the cloud provider and fetch a list of all instances that
* have been spun up by this plugin (before the server was shut down). This
* call should be should ideally remember if the agent instances are
* refreshed, and do nothing if instances were previously refreshed.
*
* @param pluginRequest the plugin request object
*/
void refreshAll(PluginRequest pluginRequest) throws Exception;
void refreshAll(ClusterProfileProperties clusterProfileProperties) throws Exception;

/**
* This
* Returns an agent instance with the specified <code>id</code> or <code>null</code>, if the agent is not found.
* This Returns an agent instance with the specified <code>id</code> or
* <code>null</code>, if the agent is not found.
*
* @param agentId the elastic agent id
*/
Expand All @@ -113,11 +118,10 @@ public interface AgentInstances<T> {
* Get the status report of an agent instance
*
* @param pluginSettings The plugin settings object
* @param agentInstance The agent instance
* @param agentInstance The agent instance
* @return An AgentStatusReport object
*/
AgentStatusReport getAgentStatusReport(PluginSettings pluginSettings, T agentInstance);

void removeTag(PluginSettings settings, String agentId, String tagName) throws Exception;
}

Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.thoughtworks.gocd.elasticagent.azure;

import static com.thoughtworks.gocd.elasticagent.azure.AzurePlugin.LOG;
import com.thoughtworks.gocd.elasticagent.azure.client.GoCDAzureClient;
import com.thoughtworks.gocd.elasticagent.azure.client.GoCDAzureClientFactory;
import com.thoughtworks.gocd.elasticagent.azure.models.*;
import com.thoughtworks.gocd.elasticagent.azure.requests.CreateAgentRequest;
import com.thoughtworks.gocd.elasticagent.azure.utils.Util;
import org.joda.time.DateTime;
import org.joda.time.Period;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import static com.thoughtworks.gocd.elasticagent.azure.AzurePlugin.LOG;
import org.joda.time.DateTime;
import org.joda.time.Period;

public class AzureAgentInstances implements AgentInstances<AzureInstance> {

Expand Down Expand Up @@ -62,7 +59,7 @@ public AzureInstance create(CreateAgentRequest request, PluginSettings settings,
LOG.info(MessageFormat.format("Task is already scheduled on instance {0}.", instance.getName()));
return instance;
}
AzureInstance instanceByElasticProfile = findAvailableInstance(request.elasticProfile());
AzureInstance instanceByElasticProfile = findAvailableInstance(request.getClusterProfileProperties());
if (instanceByElasticProfile != null) {
LOG.info(MessageFormat.format("Instance {0} provisioned already with the same elastic profile.", instanceByElasticProfile.getName()));
return instanceByElasticProfile;
Expand All @@ -88,10 +85,10 @@ public AzureInstance addTag(PluginSettings settings, String agentId, String tagN
LOG.info("Adding Tag {} to Agent {} with value {}", tagName, agentId, tagValue);
GoCDAzureClient goCDAzureClient = clientFactory.initialize(settings);
Optional.ofNullable(instances.get(agentId))
.ifPresent(instance -> {
AzureInstance instanceWithTags = azureInstanceManager.addTag(goCDAzureClient, instance, tagName, tagValue);
register(instanceWithTags);
});
.ifPresent(instance -> {
AzureInstance instanceWithTags = azureInstanceManager.addTag(goCDAzureClient, instance, tagName, tagValue);
register(instanceWithTags);
});
return instances.get(agentId);
}

Expand All @@ -100,19 +97,19 @@ public void removeTag(PluginSettings settings, String agentId, String tagName) t
LOG.info("Removing Tag {} on Agent {}", tagName, agentId);
GoCDAzureClient goCDAzureClient = clientFactory.initialize(settings);
Optional.ofNullable(instances.get(agentId))
.ifPresent(instance -> {
AzureInstance instanceWithoutTag = azureInstanceManager.removeTag(goCDAzureClient, instance, tagName);
register(instanceWithoutTag);
});
.ifPresent(instance -> {
AzureInstance instanceWithoutTag = azureInstanceManager.removeTag(goCDAzureClient, instance, tagName);
register(instanceWithoutTag);
});
}

@Override
public void terminateUnregisteredInstances(PluginSettings settings, Agents agents) throws Exception {
List<AzureInstance> instancesToTerminate = unregisteredAfterTimeout(settings.getAutoRegisterPeriod(), agents);
if (!instancesToTerminate.isEmpty()) {
String instanceNames = String.join(",", instancesToTerminate.stream()
.map(AzureInstance::getName)
.collect(Collectors.toCollection(ArrayList::new)));
.map(AzureInstance::getName)
.collect(Collectors.toCollection(ArrayList::new)));
LOG.warn("Terminating instances that did not register " + instanceNames);
for (AzureInstance instance : instancesToTerminate) {
terminate(instance.getName(), settings);
Expand All @@ -139,10 +136,10 @@ private boolean isCreatedAfterAutoRegisterTimeout(PluginSettings settings, Azure
}

@Override
public void refreshAll(PluginRequest pluginRequest) throws Exception {
GoCDAzureClient goCDAzureClient = clientFactory.initialize(pluginRequest.getPluginSettings());
public void refreshAll(ClusterProfileProperties clusterProfileProperties) throws Exception {
GoCDAzureClient goCDAzureClient = clientFactory.initialize(clusterProfileProperties);
if (!refreshed) {
List<AzureInstance> instances = azureInstanceManager.listInstances(goCDAzureClient, pluginRequest.getServerInfo().getServerId());
List<AzureInstance> instances = azureInstanceManager.listInstances(goCDAzureClient, clusterProfileProperties.getResourceGroup());
instances.forEach(instance -> register(instance));
refreshed = true;
}
Expand All @@ -166,7 +163,7 @@ public AzureInstance find(JobIdentifier jobIdentifier) {
return instances.values().stream().filter((instance) -> instance.jobIdentifierMatches(jobIdentifier)).findFirst().orElse(null);
}

public AzureInstance findAvailableInstance(ElasticProfile elasticProfile) {
public AzureInstance findAvailableInstance(ClusterProfileProperties elasticProfile) {
return instances.values().stream().filter((instance) -> instance.canBeAssigned(elasticProfile)).findFirst().orElse(null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.thoughtworks.gocd.elasticagent.azure;

import com.microsoft.azure.management.compute.ImageReference;
import com.thoughtworks.gocd.elasticagent.azure.models.ElasticProfile;
import com.thoughtworks.gocd.elasticagent.azure.models.JobIdentifier;
import com.thoughtworks.gocd.elasticagent.azure.models.Platform;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.joda.time.DateTime;

import static com.thoughtworks.gocd.elasticagent.azure.vm.VMTags.*;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

import static com.thoughtworks.gocd.elasticagent.azure.vm.VMTags.*;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import org.joda.time.DateTime;

@Getter
@EqualsAndHashCode
public class AzureInstance {

private String name;
private String hostName;
private String id;
Expand All @@ -50,18 +48,18 @@ public class AzureInstance {
private Platform platform;

public AzureInstance(String name,
String hostName,
String id,
DateTime createdAt,
ImageReference imageReference,
String size,
String os,
Integer diskSize,
String provisioningState,
String powerState,
String resourceGroupName,
String primaryNetworkInterface,
Map<String, String> tags, Platform platform) {
String hostName,
String id,
DateTime createdAt,
ImageReference imageReference,
String size,
String os,
Integer diskSize,
String provisioningState,
String powerState,
String resourceGroupName,
String primaryNetworkInterface,
Map<String, String> tags, Platform platform) {
this.name = name;
this.hostName = hostName;
this.id = id;
Expand All @@ -82,7 +80,7 @@ public Boolean jobIdentifierMatches(JobIdentifier identifier) {
return getJobIdentifierHash().equals(identifier.hash());
}

public Boolean elasticProfileMatches(ElasticProfile elasticProfile){
public Boolean elasticProfileMatches(ElasticProfile elasticProfile) {
return getElasticProfileHash().equals(elasticProfile.hash());
}

Expand All @@ -98,8 +96,8 @@ public JobState getJobState() {
return isAssigned() ? JobState.Assigned : JobState.Unassigned;
}

public boolean canBeAssigned(ElasticProfile elasticProfile) {
return getElasticProfileHash().equals(elasticProfile.hash()) && !isAssigned() && (neverAssigned() || !isIdleAfterIdleTimeout());
public boolean canBeAssigned(ClusterProfileProperties clusterProfileProperties) {
return getElasticProfileHash().equals(clusterProfileProperties.hash()) && !isAssigned() && (neverAssigned() || !isIdleAfterIdleTimeout());
}

public boolean isIdleAfterIdleTimeout() {
Expand Down Expand Up @@ -130,14 +128,14 @@ private String getJobIdentifierHash() {
return Optional.ofNullable(this.tags.get(JOB_IDENTIFIER_TAG_KEY)).orElse(String.valueOf(UUID.randomUUID()));
}

private int getIdleTimeout(){
private int getIdleTimeout() {
String idleTimeout = this.tags.get(IDLE_TIMEOUT);
return idleTimeout != null ? Integer.valueOf(idleTimeout) : 0;
}

private DateTime idleSince(){
private DateTime idleSince() {
DateTime lastJobRunTime = getLastJobRunTime();
return lastJobRunTime !=null ? lastJobRunTime : createdAt;
return lastJobRunTime != null ? lastJobRunTime : createdAt;
}

public enum JobState {
Expand Down
Loading
Loading