-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
[FIXED JENKINS-54770] Prevent multiple instances of KubernetesClient #397
[FIXED JENKINS-54770] Prevent multiple instances of KubernetesClient #397
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me after comments are addressed
CACHE_TTL = Integer.getInteger("org.csanchez.jenkins.plugins.kubernetes.clients.cacheTtl", 60); | ||
clients = CacheBuilder.newBuilder() | ||
.maximumSize(CACHE_SIZE) | ||
.expireAfterWrite(CACHE_TTL, TimeUnit.MINUTES) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a listener on the cache with removalListener()
to close all connections hold by a client when the client is evicted from the cache? The listener would basically just call KubernetesDefaultClient.close()
see https://google.github.io/guava/releases/16.0/api/docs/com/google/common/cache/CacheBuilder.html#removalListener(com.google.common.cache.RemovalListener) and https://github.com/fabric8io/kubernetes-client/blob/master/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/BaseClient.java#L76
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was address by the next commit
final Client c = clients.getIfPresent(cloudName); | ||
if (c != null && validity == c.getValidity()) { | ||
return c.getClient(); | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now we are closing connections in the listener, probably we should close also if c!=null && validity != c.getValidity() before creating a new KubernetesClient
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be good to close existing client, because onRemoval
could be called later in time
Notifies the listener that a removal occurred at some point in the past.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some comments, looks almost ready
Thanks!
.maximumSize(CACHE_SIZE) | ||
.expireAfterWrite(CACHE_TTL, TimeUnit.MINUTES) | ||
.removalListener((RemovalListener<String, Client>) removalNotification -> { | ||
removalNotification.getValue().getClient().close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A notification of the removal of a single entry. The key and/or value may be null if they were already garbage collected.
so you need to check for null
final Client c = clients.getIfPresent(cloudName); | ||
if (c != null && validity == c.getValidity()) { | ||
return c.getClient(); | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it would be good to close existing client, because onRemoval
could be called later in time
Notifies the listener that a removal occurred at some point in the past.
@@ -46,6 +46,7 @@ | |||
/** | |||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a> | |||
*/ | |||
@Deprecated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why deprecate it if you are still using it in a new method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we don't want to have a public class creating KubernetesClients from outside the (protected) Provider. In the future, we will at least reduce the visibility of the factoryAdapter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, you should add @Restricted(NoExternalUse.class)
to prevent any actual usage outside the plugin, and also add a short comment to motivate the deprecation to potential users.
|
||
LOGGER.log(Level.FINE, "Building connection to Kubernetes {0} URL {1} namespace {2}", | ||
new String[] { getDisplayName(), serverUrl, namespace }); | ||
client = new KubernetesFactoryAdapter(serverUrl, namespace, serverCertificate, credentialsId, skipTlsVerify, | ||
connectTimeout, readTimeout, maxRequestsPerHost).createClient(); | ||
KubernetesClient client = KubernetesClientProvider.createClient(name, serverUrl, namespace, serverCertificate, credentialsId, skipTlsVerify, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT I would just use KubernetesClientProvider.createClient(this)
instead of passing 8 parameters
|
||
static KubernetesClient createClient(final String cloudName, final String serviceAddress, final String namespace, @CheckForNull final String caCertData, | ||
@CheckForNull final String credentials, final boolean skipTlsVerify, final int connectTimeout, final int readTimeout, final int maxRequestsPerHost) throws NoSuchAlgorithmException, UnrecoverableKeyException, | ||
KeyStoreException, IOException, CertificateEncodingException, ExecutionException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExecutionException
is not used afaics
@@ -46,6 +46,7 @@ | |||
/** | |||
* @author <a href="mailto:nicolas.deloof@gmail.com">Nicolas De Loof</a> | |||
*/ | |||
@Deprecated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, you should add @Restricted(NoExternalUse.class)
to prevent any actual usage outside the plugin, and also add a short comment to motivate the deprecation to potential users.
@@ -421,12 +421,12 @@ public void setPodRetention(PodRetention podRetention) { | |||
*/ | |||
@SuppressFBWarnings({ "IS2_INCONSISTENT_SYNC", "DC_DOUBLECHECK" }) | |||
public KubernetesClient connect() throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, | |||
IOException, CertificateEncodingException { | |||
IOException, CertificateEncodingException, ExecutionException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExecutionException
is not required (here, and in other references across the PR)
@@ -643,6 +646,7 @@ public FormValidation doTestConnection(@QueryParameter String name, @QueryParame | |||
|
|||
// test listing pods | |||
client.pods().list(); | |||
client.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the try-with-resources construct instead. https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
The current implementation tries to avoid the creation of a new
KubernetesClient
each time a connection is requested.The implementation avoids the
KubernetesClient
to be recreated when saving the configuration in the case that any change was performed at Cloud level.CC @escoem @Vlatombe @carlossg