Skip to content

Commit

Permalink
Merge pull request #889 from Vlatombe/simplify-client-caching
Browse files Browse the repository at this point in the history
  • Loading branch information
Vlatombe committed Nov 12, 2020
2 parents 001696c + 016111f commit 0c237aa
Showing 1 changed file with 9 additions and 128 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package org.csanchez.jenkins.plugins.kubernetes;

import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import hudson.model.PeriodicWork;
import io.fabric8.kubernetes.client.HttpClientAware;
import okhttp3.Dispatcher;
import okhttp3.OkHttpClient;
import org.jenkinsci.plugins.kubernetes.auth.KubernetesAuthException;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
Expand All @@ -23,15 +21,10 @@

import hudson.Extension;
import hudson.XmlFile;
import hudson.model.AsyncPeriodicWork;
import hudson.model.Saveable;
import hudson.model.TaskListener;
import hudson.model.listeners.SaveableListener;
import io.fabric8.kubernetes.client.HttpClientAware;
import io.fabric8.kubernetes.client.KubernetesClient;
import jenkins.model.Jenkins;
import okhttp3.Dispatcher;
import okhttp3.OkHttpClient;

/**
* Manages the Kubernetes client creation per cloud
Expand All @@ -40,26 +33,6 @@ public class KubernetesClientProvider {

private static final Logger LOGGER = Logger.getLogger(KubernetesClientProvider.class.getName());

/**
* How many clouds can we connect to, default to 10
*/
private static final Integer CACHE_SIZE = Integer
.getInteger(KubernetesClientProvider.class.getPackage().getName() + ".clients.cacheSize", 10);

/**
* Time in seconds after which we will close the unused clients.
*
* Defaults to 5 minutes.
*/
private static final Long EXPIRED_CLIENTS_PURGE_TIME = Long.getLong(
KubernetesClientProvider.class.getPackage().getName() + ".clients.expiredClientsPurgeTime", TimeUnit.MINUTES.toSeconds(5));
/**
* How often to check if we need to close clients, default to {@link #EXPIRED_CLIENTS_PURGE_TIME}/2
*/
private static final Long EXPIRED_CLIENTS_PURGE_PERIOD = Long.getLong(
KubernetesClientProvider.class.getPackage().getName() + ".clients.expiredClientsPurgePeriod",
EXPIRED_CLIENTS_PURGE_TIME / 2);

/**
* Client expiration in seconds.
*
Expand All @@ -68,21 +41,14 @@ public class KubernetesClientProvider {
private static final long CACHE_EXPIRATION = Long.getLong(
KubernetesClientProvider.class.getPackage().getName() + ".clients.cacheExpiration", TimeUnit.MINUTES.toSeconds(10));

private static final Queue<Client> expiredClients = new ConcurrentLinkedQueue<>();

private static final Cache<String, Client> clients = CacheBuilder.newBuilder() //
.maximumSize(CACHE_SIZE) //
.expireAfterWrite(CACHE_EXPIRATION, TimeUnit.SECONDS) //
private static final Cache<String, Client> clients = CacheBuilder.newBuilder()
.expireAfterWrite(CACHE_EXPIRATION, TimeUnit.SECONDS)
.removalListener(rl -> {
LOGGER.log(Level.FINE, "{0} cache : Removing entry for {1}",
new Object[] { KubernetesClient.class.getSimpleName(), rl.getKey() });
Client client = (Client) rl.getValue();
if (client != null) {
client.expired = Instant.now();
expiredClients.add(client);
LOGGER.log(Level.FINE, () -> "Expiring Kubernetes client " + rl.getKey() + " " + client.client);
}

}) //
})
.build();

private KubernetesClientProvider() {
Expand Down Expand Up @@ -111,7 +77,6 @@ private static int getValidity(KubernetesCloud cloud) {
private static class Client {
private final KubernetesClient client;
private final int validity;
private Instant expired;

public Client(int validity, KubernetesClient client) {
this.client = client;
Expand All @@ -125,87 +90,6 @@ public KubernetesClient getClient() {
public int getValidity() {
return validity;
}

public Instant getExpired() {
return expired;
}
}

@Extension
public static class PurgeExpiredKubernetesClients extends AsyncPeriodicWork {

public PurgeExpiredKubernetesClients() {
super("Purge expired KubernetesClients");
}

@Override
public long getRecurrencePeriod() {
return TimeUnit.SECONDS.toMillis(EXPIRED_CLIENTS_PURGE_PERIOD);
}

@Override
protected Level getNormalLoggingLevel() {
return Level.FINEST;
}

@Override
protected void execute(TaskListener listener) {
closeExpiredClients();
}
}

/**
* Gracefully close expired clients
*
* @return whether some clients have been closed or not
*/
@Restricted(NoExternalUse.class) // testing only
public static boolean closeExpiredClients() {
boolean b = false;
if (expiredClients.isEmpty()) {
return b;
}
LOGGER.log(Level.FINE, "Closing {0} expired clients",
new Object[] { expiredClients.size() });
for (Iterator<Client> it = expiredClients.iterator(); it.hasNext();) {
Client expiredClient = it.next();
// only purge it if the EXPIRED_CLIENTS_PURGE time has elapsed
if (Instant.now().minus(EXPIRED_CLIENTS_PURGE_TIME, ChronoUnit.SECONDS)
.isBefore(expiredClient.getExpired())) {
break;
}
KubernetesClient client = expiredClient.client;
if (client instanceof HttpClientAware) {
if (gracefulClose(client, ((HttpClientAware) client).getHttpClient())) {
it.remove();
b = true;
}
} else {
LOGGER.log(Level.WARNING, "{0} is not {1}, forcing close",
new Object[] { client.toString(), HttpClientAware.class.getSimpleName() });
client.close();
it.remove();
b = true;
}
}
return b;
}

@Restricted(NoExternalUse.class) // testing only
public static boolean gracefulClose(KubernetesClient client, OkHttpClient httpClient) {
Dispatcher dispatcher = httpClient.dispatcher();
// Remove the client if there are no more users
int runningCallsCount = dispatcher.runningCallsCount();
int queuedCallsCount = dispatcher.queuedCallsCount();
if (runningCallsCount == 0 && queuedCallsCount == 0) {
LOGGER.log(Level.FINE, "Closing {0}", client.toString());
client.close();
return true;
} else {
LOGGER.log(Level.INFO, "Not closing {0}: there are still running ({1}) or queued ({2}) calls",
new Object[] { client.toString(), runningCallsCount, queuedCallsCount });
return false;
}
}

private static volatile int runningCallsCount;
Expand Down Expand Up @@ -234,16 +118,13 @@ public void onChange(Saveable o, XmlFile file) {
for (KubernetesCloud cloud : jenkins.clouds.getAll(KubernetesCloud.class)) {
String displayName = cloud.getDisplayName();
Client client = clients.getIfPresent(displayName);
if (client != null && client.getValidity() == getValidity(cloud)) {
if (client == null || client.getValidity() == getValidity(cloud)) {
cloudDisplayNames.remove(displayName);
} else {
LOGGER.log(Level.INFO, "Invalidating Kubernetes client: {0} {1}",
new Object[] { displayName, client });
}
}
// Remove missing / invalid clients
for (String displayName : cloudDisplayNames) {
LOGGER.log(Level.INFO, "Invalidating Kubernetes client: {0}", displayName);
LOGGER.log(Level.INFO, () -> "Invalidating Kubernetes client: " + displayName + clients.getIfPresent(displayName));
invalidate(displayName);
}
}
Expand Down

0 comments on commit 0c237aa

Please sign in to comment.