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

[JENKINS-62880] Migrate RedisFingerprintStorage to use Descriptor Configuration #36

Merged
merged 26 commits into from
Jul 17, 2020
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ed41709
Descriptor implementation
stellargo Jul 9, 2020
f2d90af
Remove test fingerprint storage
stellargo Jul 9, 2020
b92d36d
Move configuration to descr implementation
stellargo Jul 9, 2020
f41bfd5
Move jedis pool to JedisPoolManager
stellargo Jul 10, 2020
221e00b
Decouple descriptor implementation and credentialHelper
stellargo Jul 10, 2020
bc7c04b
Remove extra tab
stellargo Jul 10, 2020
1d507c8
Fix default not showing up issue
stellargo Jul 10, 2020
aab9485
Fix the loading of singleton RedisFingerprintStorage
stellargo Jul 10, 2020
544ce2a
Remove redundant import
stellargo Jul 10, 2020
03cecb9
Remove redundant import
stellargo Jul 10, 2020
e7e06c6
Fixes the casc test
stellargo Jul 10, 2020
ae9cf6a
Add docstring
stellargo Jul 10, 2020
53b168b
Some fixes to web tests
stellargo Jul 10, 2020
08d2853
Fix all tests
stellargo Jul 13, 2020
9d0a75e
Remove comments
stellargo Jul 13, 2020
619b14b
Remove redundant imports
stellargo Jul 13, 2020
8bbbbc4
Bump incrementals version in Jenkinsfile
stellargo Jul 13, 2020
edfc7ec
Merge remote-tracking branch 'upstream/master' into descr
stellargo Jul 13, 2020
8e0f8fc
Bump incrementals and its related changes
stellargo Jul 14, 2020
a7c456c
Remove version form Jenkinsfile
stellargo Jul 14, 2020
7df7f5e
Refactor CredentialHelper
stellargo Jul 14, 2020
2902d3b
Remove symbol
stellargo Jul 15, 2020
0a3c046
Add @Restricted(NoExternalUse.class)
stellargo Jul 15, 2020
02449ec
Add localization
stellargo Jul 16, 2020
20ce336
Bump incrementals version
stellargo Jul 16, 2020
7e32099
Merge remote-tracking branch 'upstream/master' into descr
stellargo Jul 17, 2020
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
4 changes: 2 additions & 2 deletions Jenkinsfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
def configurations = [
[ platform: "linux", jdk: "8", jenkins: "2.242" ],
[ platform: "linux", jdk: "11", jenkins: "2.242", javaLevel: "8" ]
[ platform: "linux", jdk: "8" ],
[ platform: "linux", jdk: "11", javaLevel: "8" ]
]
buildPlugin(configurations: configurations)
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* The MIT License
*
* Copyright (c) 2020, Jenkins project contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.jenkins.plugins.redis;

import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
import com.cloudbees.plugins.credentials.matchers.IdMatcher;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.security.ACL;
import jenkins.model.Jenkins;

import java.util.Collections;
import java.util.List;

/**
* Helper class to support credential operations.
*/
public class CredentialHelper {

static @NonNull String getUsernameFromCredential(@CheckForNull StandardUsernamePasswordCredentials credential) {
if (credential == null) {
return "default";
}
String username = credential.getUsername();
if (username.equals("")) {
return "default";
}
return username;
}

static @NonNull String getPasswordFromCredential(@CheckForNull StandardUsernamePasswordCredentials credential) {
if (credential == null) {
return "";
}
return credential.getPassword().getPlainText();
}

static StandardUsernamePasswordCredentials getCredential(String id) {
stellargo marked this conversation as resolved.
Show resolved Hide resolved
StandardUsernamePasswordCredentials credential = null;
List<StandardUsernamePasswordCredentials> credentials = CredentialsProvider.lookupCredentials(
StandardUsernamePasswordCredentials.class, Jenkins.get(), ACL.SYSTEM, Collections.emptyList());
IdMatcher matcher = new IdMatcher(id);
for (StandardUsernamePasswordCredentials c : credentials) {
if (matcher.matches(c)) {
credential = c;
}
}
return credential;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* The MIT License
*
* Copyright (c) 2020, Jenkins project contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.jenkins.plugins.redis;

import edu.umd.cs.findbugs.annotations.NonNull;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.exceptions.JedisException;

/**
* Singleton Responsible for managing {@link JedisPool}. Creates the connection with Redis and manages resources.
*/
public enum JedisPoolManager {

INSTANCE;

private volatile JedisPool jedisPool;

void createJedisPoolFromConfig(RedisFingerprintStorage redisFingerprintStorage) {
createJedisPool(redisFingerprintStorage.getHost(), redisFingerprintStorage.getPort(),
redisFingerprintStorage.getConnectionTimeout(), redisFingerprintStorage.getSocketTimeout(),
redisFingerprintStorage.getUsername(), redisFingerprintStorage.getPassword(),
redisFingerprintStorage.getDatabase(), redisFingerprintStorage.getSsl());
}

private synchronized void createJedisPool(
String host, int port, int connectionTimeout, int socketTimeout, String username, String password,
int database, boolean ssl) {

if (jedisPool != null) {
jedisPool.close();
}
jedisPool = new JedisPool(new JedisPoolConfig(), host, port, connectionTimeout, socketTimeout, username,
password, database, "Jenkins", ssl);
}

@NonNull Jedis getJedis(RedisFingerprintStorage redisFingerprintStorage) throws JedisException {
if (jedisPool == null) {
createJedisPoolFromConfig(redisFingerprintStorage);
}
return jedisPool.getResource();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
package io.jenkins.plugins.redis;

import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;

Expand All @@ -40,11 +41,12 @@
import java.util.logging.Logger;
import java.util.logging.Level;

import org.jenkinsci.Symbol;
import org.jenkinsci.main.modules.instance_identity.InstanceIdentity;

import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisException;

Expand All @@ -55,36 +57,19 @@
*/

@Extension
@Symbol("redis")
stellargo marked this conversation as resolved.
Show resolved Hide resolved
public class RedisFingerprintStorage extends FingerprintStorage {

private final String instanceId;
private static final Logger LOGGER = Logger.getLogger(Fingerprint.class.getName());
private volatile JedisPool jedisPool;

public static RedisFingerprintStorage get() {
return ExtensionList.lookup(RedisFingerprintStorage.class).get(0);
return ExtensionList.lookupSingleton(RedisFingerprintStorage.class);
}

@DataBoundConstructor
public RedisFingerprintStorage() throws IOException {
instanceId = Util.getDigestOf(new ByteArrayInputStream(InstanceIdentity.get().getPublic().getEncoded()));
createJedisPoolFromConfig();
}

void createJedisPoolFromConfig() {
GlobalRedisConfiguration config = GlobalRedisConfiguration.get();
createJedisPool(config.getHost(), config.getPort(), config.getConnectionTimeout(), config.getSocketTimeout(),
config.getUsername(), config.getPassword(), config.getDatabase(), config.getSsl());
}

void createJedisPool(
String host, int port, int connectionTimeout, int socketTimeout, String username, String password,
int database, boolean ssl) {
jedisPool = new JedisPool(new JedisPoolConfig(), host, port, connectionTimeout, socketTimeout, username,
password, database, "Jenkins", ssl);
}

private @NonNull Jedis getJedis() throws JedisException{
return jedisPool.getResource();
}

/**
Expand All @@ -93,7 +78,8 @@ void createJedisPool(
public synchronized void save(Fingerprint fp) throws JedisException {
StringWriter writer = new StringWriter();
Fingerprint.getXStream().toXML(fp, writer);
try (Jedis jedis = getJedis()) {
JedisPoolManager jedisPoolManager = JedisPoolManager.INSTANCE;
try (Jedis jedis = jedisPoolManager.getJedis(this)) {
Transaction transaction = jedis.multi();
transaction.set(instanceId + fp.getHashString(), writer.toString());
transaction.sadd(instanceId, fp.getHashString());
Expand All @@ -109,8 +95,9 @@ public synchronized void save(Fingerprint fp) throws JedisException {
*/
public @CheckForNull Fingerprint load(@NonNull String id) throws IOException, JedisException {
String loadedData;
JedisPoolManager jedisPoolManager = JedisPoolManager.INSTANCE;

try (Jedis jedis = getJedis()) {
try (Jedis jedis = jedisPoolManager.getJedis(this)) {
loadedData = jedis.get(instanceId + id);
} catch (JedisException e) {
LOGGER.log(Level.WARNING, "Jedis failed in loading fingerprint: " + id, e);
Expand Down Expand Up @@ -138,7 +125,8 @@ public synchronized void save(Fingerprint fp) throws JedisException {
* Deletes the fingerprint with the given id.
*/
public void delete(@NonNull String id) throws JedisException {
try (Jedis jedis = getJedis()) {
JedisPoolManager jedisPoolManager = JedisPoolManager.INSTANCE;
try (Jedis jedis = jedisPoolManager.getJedis(this)) {
Transaction transaction = jedis.multi();
transaction.del(instanceId + id);
transaction.srem(instanceId, id);
Expand All @@ -153,12 +141,106 @@ public void delete(@NonNull String id) throws JedisException {
* Returns true if there's some data in the fingerprint database.
*/
public boolean isReady() {
try (Jedis jedis = getJedis()) {
JedisPoolManager jedisPoolManager = JedisPoolManager.INSTANCE;
try (Jedis jedis = jedisPoolManager.getJedis(this)) {
return jedis.smembers(instanceId).size() != 0;
} catch (JedisException e) {
LOGGER.log(Level.WARNING, "Failed to connect to Jedis", e);
throw e;
}
}

private String host = RedisFingerprintStorageDescriptor.DEFAULT_HOST;
afalko marked this conversation as resolved.
Show resolved Hide resolved
private int port = RedisFingerprintStorageDescriptor.DEFAULT_PORT;
private int database = RedisFingerprintStorageDescriptor.DEFAULT_DATABASE;
private boolean ssl = RedisFingerprintStorageDescriptor.DEFAULT_SSL;
private int connectionTimeout = RedisFingerprintStorageDescriptor.DEFAULT_CONNECTION_TIMEOUT;
private int socketTimeout = RedisFingerprintStorageDescriptor.DEFAULT_SOCKET_TIMEOUT;
private String credentialsId = RedisFingerprintStorageDescriptor.DEFAULT_CREDENTIALS_ID;

public String getHost() {
return host;
}

@DataBoundSetter
public void setHost(String host) {
afalko marked this conversation as resolved.
Show resolved Hide resolved
this.host = host;
JedisPoolManager.INSTANCE.createJedisPoolFromConfig(this);
}

public int getPort() {
return port;
}

@DataBoundSetter
public void setPort(int port) {
this.port = port;
JedisPoolManager.INSTANCE.createJedisPoolFromConfig(this);
}

public int getDatabase() {
return database;
}

@DataBoundSetter
public void setDatabase(int database) {
this.database = database;
JedisPoolManager.INSTANCE.createJedisPoolFromConfig(this);
}

public boolean getSsl() {
return this.ssl;
}

@DataBoundSetter
public void setSsl(boolean ssl) {
this.ssl = ssl;
JedisPoolManager.INSTANCE.createJedisPoolFromConfig(this);
}

public int getConnectionTimeout() {
return connectionTimeout;
}

@DataBoundSetter
public void setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
JedisPoolManager.INSTANCE.createJedisPoolFromConfig(this);
}

public int getSocketTimeout() {
return socketTimeout;
}

@DataBoundSetter
public void setSocketTimeout(int socketTimeout) {
this.socketTimeout = socketTimeout;
JedisPoolManager.INSTANCE.createJedisPoolFromConfig(this);
}

public String getCredentialsId() {
return credentialsId;
}

@DataBoundSetter
public void setCredentialsId(String credentialsId) {
this.credentialsId = credentialsId;
JedisPoolManager.INSTANCE.createJedisPoolFromConfig(this);
}

public @NonNull String getUsername() {
StandardUsernamePasswordCredentials credential = CredentialHelper.getCredential(credentialsId);
return CredentialHelper.getUsernameFromCredential(credential);
}

public @NonNull String getPassword() {
StandardUsernamePasswordCredentials credential = CredentialHelper.getCredential(credentialsId);
return CredentialHelper.getPasswordFromCredential(credential);
}

@Extension
public static class DescriptorImpl extends RedisFingerprintStorageDescriptor {

}

}
Loading