initial merge of license
Original commit: elastic/x-pack-elasticsearch@d520d6be57
This commit is contained in:
parent
082406f364
commit
dbb9c5d918
36
README.md
36
README.md
|
@ -1,36 +0,0 @@
|
||||||
elasticsearch-license
|
|
||||||
=====================
|
|
||||||
|
|
||||||
Elasticsearch Licensing core, tools and plugin
|
|
||||||
|
|
||||||
## Core
|
|
||||||
|
|
||||||
Contains core data structures, utilities used by **Licensor** and **Plugin**.
|
|
||||||
|
|
||||||
See `core/` and `core-shaded/`
|
|
||||||
|
|
||||||
## Licensor
|
|
||||||
|
|
||||||
Contains a collection of tools to generate key-pairs, licenses and validate licenses.
|
|
||||||
|
|
||||||
See `licensor/`
|
|
||||||
|
|
||||||
see [wiki] (https://github.com/elasticsearch/elasticsearch-license/wiki) for documentation on
|
|
||||||
[Licensing Tools Usage & Reference] (https://github.com/elasticsearch/elasticsearch-license/wiki/License-Tools-Usage-&-Reference)
|
|
||||||
|
|
||||||
## Plugin
|
|
||||||
|
|
||||||
**NOTE**: The license plugin has to be packaged with the right public key when being deployed to public repositories in maven
|
|
||||||
or uploaded to s3. Use `-Dkeys.path=<PATH_TO_KEY_DIR>` with maven command to package the plugin with a specified key.
|
|
||||||
|
|
||||||
See `plugin/`
|
|
||||||
|
|
||||||
see [Getting Started] (https://github.com/elasticsearch/elasticsearch-license/blob/master/docs/getting-started.asciidoc) to install license plugin.
|
|
||||||
|
|
||||||
see [Licensing REST APIs] (https://github.com/elasticsearch/elasticsearch-license/blob/master/docs/license.asciidoc)
|
|
||||||
to use the license plugin from an elasticsearch deployment.
|
|
||||||
|
|
||||||
see [wiki] (https://github.com/elasticsearch/elasticsearch-license/wiki) for documentation on
|
|
||||||
- [License Plugin Consumer Interface] (https://github.com/elasticsearch/elasticsearch-license/wiki/License---Consumer-Interface)
|
|
||||||
- [License Plugin Release Process] (https://github.com/elasticsearch/elasticsearch-license/wiki/Plugin-Release-Process)
|
|
||||||
- [License Plugin Design] (https://github.com/elasticsearch/elasticsearch-license/wiki/License-Plugin--Design)
|
|
17
core/pom.xml
17
core/pom.xml
|
@ -1,17 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>elasticsearch-license</artifactId>
|
|
||||||
<groupId>org.elasticsearch</groupId>
|
|
||||||
<version>2.0.0.beta1-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<properties>
|
|
||||||
<license.basedir combine.self="override">${project.parent.basedir}</license.basedir>
|
|
||||||
<!-- we aren't really a plugin... -->
|
|
||||||
<skip.integ.tests>true</skip.integ.tests>
|
|
||||||
</properties>
|
|
||||||
<artifactId>elasticsearch-license-core</artifactId>
|
|
||||||
</project>
|
|
|
@ -1,245 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.core;
|
|
||||||
|
|
||||||
|
|
||||||
import org.elasticsearch.common.Base64;
|
|
||||||
|
|
||||||
import javax.crypto.*;
|
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.*;
|
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
|
||||||
import java.security.spec.X509EncodedKeySpec;
|
|
||||||
|
|
||||||
public class CryptUtils {
|
|
||||||
private static final int minimumPadding = 20;
|
|
||||||
private static final byte[] salt = {
|
|
||||||
(byte) 0xA9, (byte) 0xA2, (byte) 0xB5, (byte) 0xDE,
|
|
||||||
(byte) 0x2A, (byte) 0x8A, (byte) 0x9A, (byte) 0xE6
|
|
||||||
};
|
|
||||||
private static final int iterationCount = 1024;
|
|
||||||
private static final int aesKeyLength = 128;
|
|
||||||
private static final String keyAlgorithm = "RSA";
|
|
||||||
private static final String passHashAlgorithm = "SHA-512";
|
|
||||||
private static final String DEFAULT_PASS_PHRASE = "elasticsearch-license";
|
|
||||||
|
|
||||||
private static final SecureRandom random = new SecureRandom();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read encrypted private key file content with default pass phrase
|
|
||||||
*/
|
|
||||||
public static PrivateKey readEncryptedPrivateKey(byte[] fileContents) {
|
|
||||||
try {
|
|
||||||
return readEncryptedPrivateKey(fileContents, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read encrypted public key file content with default pass phrase
|
|
||||||
*/
|
|
||||||
public static PublicKey readEncryptedPublicKey(byte[] fileContents) {
|
|
||||||
try {
|
|
||||||
return readEncryptedPublicKey(fileContents, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns encrypted public key file content with default pass phrase
|
|
||||||
*/
|
|
||||||
public static byte[] writeEncryptedPublicKey(PublicKey publicKey) {
|
|
||||||
try {
|
|
||||||
return writeEncryptedPublicKey(publicKey, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns encrypted private key file content with default pass phrase
|
|
||||||
*/
|
|
||||||
public static byte[] writeEncryptedPrivateKey(PrivateKey privateKey) {
|
|
||||||
try {
|
|
||||||
return writeEncryptedPrivateKey(privateKey, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read encrypted private key file content with provided <code>passPhrase</code>
|
|
||||||
*/
|
|
||||||
public static PrivateKey readEncryptedPrivateKey(byte[] fileContents, char[] passPhrase) {
|
|
||||||
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(decrypt(fileContents, passPhrase));
|
|
||||||
try {
|
|
||||||
return KeyFactory.getInstance(keyAlgorithm).generatePrivate(privateKeySpec);
|
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read encrypted public key file content with provided <code>passPhrase</code>
|
|
||||||
*/
|
|
||||||
public static PublicKey readEncryptedPublicKey(byte[] fileContents, char[] passPhrase) {
|
|
||||||
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(decrypt(fileContents, passPhrase));
|
|
||||||
try {
|
|
||||||
return KeyFactory.getInstance(CryptUtils.keyAlgorithm).generatePublic(publicKeySpec);
|
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns encrypted public key file content with provided <code>passPhrase</code>
|
|
||||||
*/
|
|
||||||
public static byte[] writeEncryptedPublicKey(PublicKey publicKey, char[] passPhrase) {
|
|
||||||
X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
|
|
||||||
return encrypt(encodedKeySpec.getEncoded(), passPhrase);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns encrypted private key file content with provided <code>passPhrase</code>
|
|
||||||
*/
|
|
||||||
public static byte[] writeEncryptedPrivateKey(PrivateKey privateKey, char[] passPhrase) {
|
|
||||||
PKCS8EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
|
|
||||||
return encrypt(encodedKeySpec.getEncoded(), passPhrase);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypts provided <code>data</code> with <code>DEFAULT_PASS_PHRASE</code>
|
|
||||||
*/
|
|
||||||
public static byte[] encrypt(byte[] data) {
|
|
||||||
try {
|
|
||||||
return encrypt(data, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypts provided <code>encryptedData</code> with <code>DEFAULT_PASS_PHRASE</code>
|
|
||||||
*/
|
|
||||||
public static byte[] decrypt(byte[] encryptedData) {
|
|
||||||
try {
|
|
||||||
return decrypt(encryptedData, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypts provided <code>data</code> with <code>passPhrase</code>
|
|
||||||
*/
|
|
||||||
public static byte[] encrypt(byte[] data, char[] passPhrase) {
|
|
||||||
try {
|
|
||||||
final Cipher encryptionCipher = getEncryptionCipher(getSecretKey(passPhrase));
|
|
||||||
return encryptionCipher.doFinal(pad(data, minimumPadding));
|
|
||||||
} catch (InvalidKeySpecException | IllegalBlockSizeException | BadPaddingException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypts provided <code>encryptedData</code> with <code>passPhrase</code>
|
|
||||||
*/
|
|
||||||
private static byte[] decrypt(byte[] encryptedData, char[] passPhrase) {
|
|
||||||
try {
|
|
||||||
final Cipher cipher = getDecryptionCipher(getSecretKey(passPhrase));
|
|
||||||
return unPad(cipher.doFinal(encryptedData));
|
|
||||||
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SecretKey getSecretKey(char[] passPhrase) throws InvalidKeySpecException {
|
|
||||||
try {
|
|
||||||
PBEKeySpec keySpec = new PBEKeySpec(passPhrase, salt, iterationCount, aesKeyLength);
|
|
||||||
|
|
||||||
byte[] shortKey = SecretKeyFactory.getInstance("PBEWithSHA1AndDESede").
|
|
||||||
generateSecret(keySpec).getEncoded();
|
|
||||||
|
|
||||||
byte[] intermediaryKey = new byte[aesKeyLength / 8];
|
|
||||||
for (int i = 0, j = 0; i < aesKeyLength / 8; i++) {
|
|
||||||
intermediaryKey[i] = shortKey[j];
|
|
||||||
if (++j == shortKey.length)
|
|
||||||
j = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SecretKeySpec(intermediaryKey, "AES");
|
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Cipher getEncryptionCipher(SecretKey secretKey) {
|
|
||||||
return getCipher(Cipher.ENCRYPT_MODE, secretKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Cipher getDecryptionCipher(SecretKey secretKey) {
|
|
||||||
return getCipher(Cipher.DECRYPT_MODE, secretKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Cipher getCipher(int mode, SecretKey secretKey) {
|
|
||||||
try {
|
|
||||||
Cipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
|
|
||||||
cipher.init(mode, secretKey, random);
|
|
||||||
return cipher;
|
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] pad(byte[] bytes, int length) {
|
|
||||||
if (bytes.length >= length) {
|
|
||||||
byte[] out = new byte[bytes.length + 1];
|
|
||||||
System.arraycopy(bytes, 0, out, 0, bytes.length);
|
|
||||||
out[bytes.length] = (byte) 1;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] out = new byte[length + 1];
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
for (; i < bytes.length; i++)
|
|
||||||
out[i] = bytes[i];
|
|
||||||
|
|
||||||
int padded = length - i;
|
|
||||||
|
|
||||||
// fill the rest with random bytes
|
|
||||||
byte[] fill = new byte[padded - 1];
|
|
||||||
random.nextBytes(fill);
|
|
||||||
System.arraycopy(fill, 0, out, i, padded - 1);
|
|
||||||
|
|
||||||
out[length] = (byte) (padded + 1);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] unPad(byte[] bytes) {
|
|
||||||
int padded = (int) bytes[bytes.length - 1];
|
|
||||||
int targetLength = bytes.length - padded;
|
|
||||||
|
|
||||||
byte[] out = new byte[targetLength];
|
|
||||||
|
|
||||||
System.arraycopy(bytes, 0, out, 0, targetLength);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static char[] hashPassPhrase(String passPhrase) throws NoSuchAlgorithmException {
|
|
||||||
final byte[] passBytes = passPhrase.getBytes(StandardCharsets.UTF_8);
|
|
||||||
final byte[] digest = MessageDigest.getInstance(passHashAlgorithm).digest(passBytes);
|
|
||||||
return new String(Base64.encodeBytesToBytes(digest), StandardCharsets.UTF_8).toCharArray();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.core;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
|
|
||||||
import org.elasticsearch.common.joda.Joda;
|
|
||||||
import org.joda.time.MutableDateTime;
|
|
||||||
import org.joda.time.format.DateTimeFormatter;
|
|
||||||
import org.joda.time.format.ISODateTimeFormat;
|
|
||||||
|
|
||||||
public class DateUtils {
|
|
||||||
|
|
||||||
private final static FormatDateTimeFormatter formatDateOnlyFormatter = Joda.forPattern("yyyy-MM-dd");
|
|
||||||
|
|
||||||
private final static DateTimeFormatter dateOnlyFormatter = formatDateOnlyFormatter.parser().withZoneUTC();
|
|
||||||
|
|
||||||
private final static DateTimeFormatter dateTimeFormatter = ISODateTimeFormat.dateTime().withZoneUTC();
|
|
||||||
|
|
||||||
public static long endOfTheDay(String date) {
|
|
||||||
try {
|
|
||||||
// Try parsing using complete date/time format
|
|
||||||
return dateTimeFormatter.parseDateTime(date).getMillis();
|
|
||||||
} catch (IllegalArgumentException ex) {
|
|
||||||
// Fall back to the date only format
|
|
||||||
MutableDateTime dateTime = dateOnlyFormatter.parseMutableDateTime(date);
|
|
||||||
dateTime.millisOfDay().set(dateTime.millisOfDay().getMaximumValue());
|
|
||||||
return dateTime.getMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long beginningOfTheDay(String date) {
|
|
||||||
try {
|
|
||||||
// Try parsing using complete date/time format
|
|
||||||
return dateTimeFormatter.parseDateTime(date).getMillis();
|
|
||||||
} catch (IllegalArgumentException ex) {
|
|
||||||
// Fall back to the date only format
|
|
||||||
return dateOnlyFormatter.parseDateTime(date).getMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,433 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.core;
|
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
|
||||||
import org.elasticsearch.ElasticsearchParseException;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilderString;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data structure for license. Use {@link Builder} to build a license.
|
|
||||||
* Provides serialization/deserialization & validation methods for license object
|
|
||||||
*/
|
|
||||||
public class License implements ToXContent {
|
|
||||||
public final static int VERSION_START = 1;
|
|
||||||
public final static int VERSION_CURRENT = VERSION_START;
|
|
||||||
|
|
||||||
private final String uid;
|
|
||||||
private final String issuer;
|
|
||||||
private final String issuedTo;
|
|
||||||
private final long issueDate;
|
|
||||||
private final String type;
|
|
||||||
private final String subscriptionType;
|
|
||||||
private final String feature;
|
|
||||||
private final String signature;
|
|
||||||
private final long expiryDate;
|
|
||||||
private final int maxNodes;
|
|
||||||
|
|
||||||
private License(String uid, String issuer, String issuedTo, long issueDate, String type,
|
|
||||||
String subscriptionType, String feature, String signature, long expiryDate, int maxNodes) {
|
|
||||||
this.uid = uid;
|
|
||||||
this.issuer = issuer;
|
|
||||||
this.issuedTo = issuedTo;
|
|
||||||
this.issueDate = issueDate;
|
|
||||||
this.type = type;
|
|
||||||
this.subscriptionType = subscriptionType;
|
|
||||||
this.feature = feature;
|
|
||||||
this.signature = signature;
|
|
||||||
this.expiryDate = expiryDate;
|
|
||||||
this.maxNodes = maxNodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a unique identifier for a license
|
|
||||||
*/
|
|
||||||
public String uid() {
|
|
||||||
return uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return type of the license [trial, subscription, internal]
|
|
||||||
*/
|
|
||||||
public String type() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return subscription type of the license [none, silver, gold, platinum]
|
|
||||||
*/
|
|
||||||
public String subscriptionType() {
|
|
||||||
return subscriptionType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the issueDate in milliseconds
|
|
||||||
*/
|
|
||||||
public long issueDate() {
|
|
||||||
return issueDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the featureType for the license [shield, marvel]
|
|
||||||
*/
|
|
||||||
public String feature() {
|
|
||||||
return feature;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the expiry date in milliseconds
|
|
||||||
*/
|
|
||||||
public long expiryDate() {
|
|
||||||
return expiryDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the maximum number of nodes this license has been issued for
|
|
||||||
*/
|
|
||||||
public int maxNodes() {
|
|
||||||
return maxNodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a string representing the entity this licenses has been issued to
|
|
||||||
*/
|
|
||||||
public String issuedTo() {
|
|
||||||
return issuedTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a string representing the entity responsible for issuing this license (internal)
|
|
||||||
*/
|
|
||||||
public String issuer() {
|
|
||||||
return issuer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a string representing the signature of the license used for license verification
|
|
||||||
*/
|
|
||||||
public String signature() {
|
|
||||||
return signature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validate() {
|
|
||||||
if (issuer == null) {
|
|
||||||
throw new IllegalStateException("issuer can not be null");
|
|
||||||
} else if (issuedTo == null) {
|
|
||||||
throw new IllegalStateException("issuedTo can not be null");
|
|
||||||
} else if (issueDate == -1) {
|
|
||||||
throw new IllegalStateException("issueDate has to be set");
|
|
||||||
} else if (type == null) {
|
|
||||||
throw new IllegalStateException("type can not be null");
|
|
||||||
} else if (subscriptionType == null) {
|
|
||||||
throw new IllegalStateException("subscriptionType can not be null");
|
|
||||||
} else if (uid == null) {
|
|
||||||
throw new IllegalStateException("uid can not be null");
|
|
||||||
} else if (feature == null) {
|
|
||||||
throw new IllegalStateException("at least one feature has to be enabled");
|
|
||||||
} else if (signature == null) {
|
|
||||||
throw new IllegalStateException("signature can not be null");
|
|
||||||
} else if (maxNodes == -1) {
|
|
||||||
throw new IllegalStateException("maxNodes has to be set");
|
|
||||||
} else if (expiryDate == -1) {
|
|
||||||
throw new IllegalStateException("expiryDate has to be set");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static License readLicense(StreamInput in) throws IOException {
|
|
||||||
int version = in.readVInt(); // Version for future extensibility
|
|
||||||
if (version > VERSION_CURRENT) {
|
|
||||||
throw new ElasticsearchException("Unknown license version found, please upgrade all nodes to the latest elasticsearch-license plugin");
|
|
||||||
}
|
|
||||||
Builder builder = builder();
|
|
||||||
builder.uid(in.readString());
|
|
||||||
builder.type(in.readString());
|
|
||||||
builder.subscriptionType(in.readString());
|
|
||||||
builder.issueDate(in.readLong());
|
|
||||||
builder.feature(in.readString());
|
|
||||||
builder.expiryDate(in.readLong());
|
|
||||||
builder.maxNodes(in.readInt());
|
|
||||||
builder.issuedTo(in.readString());
|
|
||||||
builder.issuer(in.readString());
|
|
||||||
builder.signature(in.readOptionalString());
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
out.writeVInt(VERSION_CURRENT);
|
|
||||||
out.writeString(uid);
|
|
||||||
out.writeString(type);
|
|
||||||
out.writeString(subscriptionType);
|
|
||||||
out.writeLong(issueDate);
|
|
||||||
out.writeString(feature);
|
|
||||||
out.writeLong(expiryDate);
|
|
||||||
out.writeInt(maxNodes);
|
|
||||||
out.writeString(issuedTo);
|
|
||||||
out.writeString(issuer);
|
|
||||||
out.writeOptionalString(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
|
||||||
boolean licenseSpecMode = params.paramAsBoolean(Licenses.LICENSE_SPEC_VIEW_MODE, false);
|
|
||||||
boolean restViewMode = params.paramAsBoolean(Licenses.REST_VIEW_MODE, false);
|
|
||||||
boolean previouslyHumanReadable = builder.humanReadable();
|
|
||||||
if (licenseSpecMode && restViewMode) {
|
|
||||||
throw new IllegalArgumentException("can have either " + Licenses.REST_VIEW_MODE + " or " + Licenses.LICENSE_SPEC_VIEW_MODE);
|
|
||||||
} else if (restViewMode) {
|
|
||||||
if (!previouslyHumanReadable) {
|
|
||||||
builder.humanReadable(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
builder.startObject();
|
|
||||||
if (restViewMode) {
|
|
||||||
String status = "active";
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (issueDate > now) {
|
|
||||||
status = "invalid";
|
|
||||||
} else if (expiryDate < now) {
|
|
||||||
status = "expired";
|
|
||||||
}
|
|
||||||
builder.field(XFields.STATUS, status);
|
|
||||||
}
|
|
||||||
builder.field(XFields.UID, uid);
|
|
||||||
builder.field(XFields.TYPE, type);
|
|
||||||
builder.field(XFields.SUBSCRIPTION_TYPE, subscriptionType);
|
|
||||||
builder.dateValueField(XFields.ISSUE_DATE_IN_MILLIS, XFields.ISSUE_DATE, issueDate);
|
|
||||||
builder.field(XFields.FEATURE, feature);
|
|
||||||
builder.dateValueField(XFields.EXPIRY_DATE_IN_MILLIS, XFields.EXPIRY_DATE, expiryDate);
|
|
||||||
builder.field(XFields.MAX_NODES, maxNodes);
|
|
||||||
builder.field(XFields.ISSUED_TO, issuedTo);
|
|
||||||
builder.field(XFields.ISSUER, issuer);
|
|
||||||
if (!licenseSpecMode && !restViewMode && signature != null) {
|
|
||||||
builder.field(XFields.SIGNATURE, signature);
|
|
||||||
}
|
|
||||||
builder.endObject();
|
|
||||||
if (restViewMode) {
|
|
||||||
builder.humanReadable(previouslyHumanReadable);
|
|
||||||
}
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final static class Fields {
|
|
||||||
static final String STATUS = "status";
|
|
||||||
static final String UID = "uid";
|
|
||||||
static final String TYPE = "type";
|
|
||||||
static final String SUBSCRIPTION_TYPE = "subscription_type";
|
|
||||||
static final String ISSUE_DATE_IN_MILLIS = "issue_date_in_millis";
|
|
||||||
static final String ISSUE_DATE = "issue_date";
|
|
||||||
static final String FEATURE = "feature";
|
|
||||||
static final String EXPIRY_DATE_IN_MILLIS = "expiry_date_in_millis";
|
|
||||||
static final String EXPIRY_DATE = "expiry_date";
|
|
||||||
static final String MAX_NODES = "max_nodes";
|
|
||||||
static final String ISSUED_TO = "issued_to";
|
|
||||||
static final String ISSUER = "issuer";
|
|
||||||
static final String SIGNATURE = "signature";
|
|
||||||
}
|
|
||||||
|
|
||||||
private final static class XFields {
|
|
||||||
static final XContentBuilderString STATUS = new XContentBuilderString(Fields.STATUS);
|
|
||||||
static final XContentBuilderString UID = new XContentBuilderString(Fields.UID);
|
|
||||||
static final XContentBuilderString TYPE = new XContentBuilderString(Fields.TYPE);
|
|
||||||
static final XContentBuilderString SUBSCRIPTION_TYPE = new XContentBuilderString(Fields.SUBSCRIPTION_TYPE);
|
|
||||||
static final XContentBuilderString ISSUE_DATE_IN_MILLIS = new XContentBuilderString(Fields.ISSUE_DATE_IN_MILLIS);
|
|
||||||
static final XContentBuilderString ISSUE_DATE = new XContentBuilderString(Fields.ISSUE_DATE);
|
|
||||||
static final XContentBuilderString FEATURE = new XContentBuilderString(Fields.FEATURE);
|
|
||||||
static final XContentBuilderString EXPIRY_DATE_IN_MILLIS = new XContentBuilderString(Fields.EXPIRY_DATE_IN_MILLIS);
|
|
||||||
static final XContentBuilderString EXPIRY_DATE = new XContentBuilderString(Fields.EXPIRY_DATE);
|
|
||||||
static final XContentBuilderString MAX_NODES = new XContentBuilderString(Fields.MAX_NODES);
|
|
||||||
static final XContentBuilderString ISSUED_TO = new XContentBuilderString(Fields.ISSUED_TO);
|
|
||||||
static final XContentBuilderString ISSUER = new XContentBuilderString(Fields.ISSUER);
|
|
||||||
static final XContentBuilderString SIGNATURE = new XContentBuilderString(Fields.SIGNATURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long parseDate(XContentParser parser, String description, boolean endOfTheDay) throws IOException {
|
|
||||||
if (parser.currentToken() == XContentParser.Token.VALUE_NUMBER) {
|
|
||||||
return parser.longValue();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
if (endOfTheDay) {
|
|
||||||
return DateUtils.endOfTheDay(parser.text());
|
|
||||||
} else {
|
|
||||||
return DateUtils.beginningOfTheDay(parser.text());
|
|
||||||
}
|
|
||||||
} catch (IllegalArgumentException ex) {
|
|
||||||
throw new ElasticsearchParseException("invalid " + description + " date format " + parser.text());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder builder() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
private String uid;
|
|
||||||
private String issuer;
|
|
||||||
private String issuedTo;
|
|
||||||
private long issueDate = -1;
|
|
||||||
private String type;
|
|
||||||
private String subscriptionType = "none";
|
|
||||||
private String feature;
|
|
||||||
private String signature;
|
|
||||||
private long expiryDate = -1;
|
|
||||||
private int maxNodes = -1;
|
|
||||||
|
|
||||||
|
|
||||||
public Builder uid(String uid) {
|
|
||||||
this.uid = uid;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder issuer(String issuer) {
|
|
||||||
this.issuer = issuer;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder issuedTo(String issuedTo) {
|
|
||||||
this.issuedTo = issuedTo;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder issueDate(long issueDate) {
|
|
||||||
this.issueDate = issueDate;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder type(String type) {
|
|
||||||
this.type = type;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder subscriptionType(String subscriptionType) {
|
|
||||||
this.subscriptionType = subscriptionType;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder feature(String feature) {
|
|
||||||
this.feature = feature;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder expiryDate(long expiryDate) {
|
|
||||||
this.expiryDate = expiryDate;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder maxNodes(int maxNodes) {
|
|
||||||
this.maxNodes = maxNodes;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder signature(String signature) {
|
|
||||||
if (signature != null) {
|
|
||||||
this.signature = signature;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder fromLicenseSpec(License license, String signature) {
|
|
||||||
return uid(license.uid())
|
|
||||||
.issuedTo(license.issuedTo())
|
|
||||||
.issueDate(license.issueDate())
|
|
||||||
.type(license.type())
|
|
||||||
.subscriptionType(license.subscriptionType())
|
|
||||||
.feature(license.feature())
|
|
||||||
.maxNodes(license.maxNodes())
|
|
||||||
.expiryDate(license.expiryDate())
|
|
||||||
.issuer(license.issuer())
|
|
||||||
.signature(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder fromXContent(XContentParser parser) throws IOException {
|
|
||||||
XContentParser.Token token = parser.currentToken();
|
|
||||||
if (token == XContentParser.Token.START_OBJECT) {
|
|
||||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
|
||||||
if (token == XContentParser.Token.FIELD_NAME) {
|
|
||||||
String currentFieldName = parser.currentName();
|
|
||||||
token = parser.nextToken();
|
|
||||||
if (token.isValue()) {
|
|
||||||
if (Fields.UID.equals(currentFieldName)) {
|
|
||||||
uid(parser.text());
|
|
||||||
} else if (Fields.TYPE.equals(currentFieldName)) {
|
|
||||||
type(parser.text());
|
|
||||||
} else if (Fields.SUBSCRIPTION_TYPE.equals(currentFieldName)) {
|
|
||||||
subscriptionType(parser.text());
|
|
||||||
} else if (Fields.ISSUE_DATE.equals(currentFieldName)) {
|
|
||||||
issueDate(parseDate(parser, "issue", false));
|
|
||||||
} else if (Fields.ISSUE_DATE_IN_MILLIS.equals(currentFieldName)) {
|
|
||||||
issueDate(parser.longValue());
|
|
||||||
} else if (Fields.FEATURE.equals(currentFieldName)) {
|
|
||||||
feature(parser.text());
|
|
||||||
} else if (Fields.EXPIRY_DATE.equals(currentFieldName)) {
|
|
||||||
expiryDate(parseDate(parser, "expiration", true));
|
|
||||||
} else if (Fields.EXPIRY_DATE_IN_MILLIS.equals(currentFieldName)) {
|
|
||||||
expiryDate(parser.longValue());
|
|
||||||
} else if (Fields.MAX_NODES.equals(currentFieldName)) {
|
|
||||||
maxNodes(parser.intValue());
|
|
||||||
} else if (Fields.ISSUED_TO.equals(currentFieldName)) {
|
|
||||||
issuedTo(parser.text());
|
|
||||||
} else if (Fields.ISSUER.equals(currentFieldName)) {
|
|
||||||
issuer(parser.text());
|
|
||||||
} else if (Fields.SIGNATURE.equals(currentFieldName)) {
|
|
||||||
signature(parser.text());
|
|
||||||
}
|
|
||||||
// Ignore unknown elements - might be new version of license
|
|
||||||
} else if (token == XContentParser.Token.START_ARRAY) {
|
|
||||||
// It was probably created by newer version - ignoring
|
|
||||||
parser.skipChildren();
|
|
||||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
|
||||||
// It was probably created by newer version - ignoring
|
|
||||||
parser.skipChildren();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new ElasticsearchParseException("failed to parse licenses expected a license object");
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public License build() {
|
|
||||||
return new License(uid, issuer, issuedTo, issueDate, type,
|
|
||||||
subscriptionType, feature, signature, expiryDate, maxNodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder validate() {
|
|
||||||
if (issuer == null) {
|
|
||||||
throw new IllegalStateException("issuer can not be null");
|
|
||||||
} else if (issuedTo == null) {
|
|
||||||
throw new IllegalStateException("issuedTo can not be null");
|
|
||||||
} else if (issueDate == -1) {
|
|
||||||
throw new IllegalStateException("issueDate has to be set");
|
|
||||||
} else if (type == null) {
|
|
||||||
throw new IllegalStateException("type can not be null");
|
|
||||||
} else if (subscriptionType == null) {
|
|
||||||
throw new IllegalStateException("subscriptionType can not be null");
|
|
||||||
} else if (uid == null) {
|
|
||||||
throw new IllegalStateException("uid can not be null");
|
|
||||||
} else if (feature == null) {
|
|
||||||
throw new IllegalStateException("at least one feature has to be enabled");
|
|
||||||
} else if (signature == null) {
|
|
||||||
throw new IllegalStateException("signature can not be null");
|
|
||||||
} else if (maxNodes == -1) {
|
|
||||||
throw new IllegalStateException("maxNodes has to be set");
|
|
||||||
} else if (expiryDate == -1) {
|
|
||||||
throw new IllegalStateException("expiryDate has to be set");
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,159 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.core;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.Base64;
|
|
||||||
import org.elasticsearch.common.io.Streams;
|
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.Signature;
|
|
||||||
import java.security.SignatureException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Responsible for verifying signed licenses
|
|
||||||
* The signed licenses are expected to have signatures with the appropriate spec
|
|
||||||
* (see {@link org.elasticsearch.license.core.LicenseVerifier})
|
|
||||||
* along with the appropriate encrypted public key
|
|
||||||
*/
|
|
||||||
public class LicenseVerifier {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies Licenses using {@link #verifyLicense(License, byte[])} using the public key from the
|
|
||||||
* resources
|
|
||||||
*/
|
|
||||||
public static boolean verifyLicenses(final Collection<License> licenses) {
|
|
||||||
final byte[] encryptedPublicKeyData = getPublicKeyContentFromResource("/public.key");
|
|
||||||
try {
|
|
||||||
for (License license : licenses) {
|
|
||||||
if (!verifyLicense(license, encryptedPublicKeyData)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} finally {
|
|
||||||
Arrays.fill(encryptedPublicKeyData, (byte) 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies Licenses using {@link #verifyLicense(License, byte[])} using the provided public key
|
|
||||||
*/
|
|
||||||
public static boolean verifyLicenses(final Collection<License> licenses, final Path publicKeyPath) throws IOException {
|
|
||||||
final byte[] encryptedPublicKeyData = Files.readAllBytes(publicKeyPath);
|
|
||||||
try {
|
|
||||||
for (License license : licenses) {
|
|
||||||
if (!verifyLicense(license, encryptedPublicKeyData)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} finally {
|
|
||||||
Arrays.fill(encryptedPublicKeyData, (byte) 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean verifyLicense(final License license) {
|
|
||||||
final byte[] encryptedPublicKeyData = getPublicKeyContentFromResource("/public.key");
|
|
||||||
try {
|
|
||||||
return verifyLicense(license, encryptedPublicKeyData);
|
|
||||||
} finally {
|
|
||||||
Arrays.fill(encryptedPublicKeyData, (byte) 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* verifies the license content with the signature and ensures that an expected public key is used
|
|
||||||
* @param license to verify
|
|
||||||
* @return true if valid, false otherwise
|
|
||||||
*/
|
|
||||||
public static boolean verifyLicense(final License license, final byte[] encryptedPublicKeyData) {
|
|
||||||
LicenseSignature licenseSignature = null;
|
|
||||||
try {
|
|
||||||
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
|
|
||||||
license.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(Licenses.LICENSE_SPEC_VIEW_MODE, "true")));
|
|
||||||
licenseSignature = parseSignature(license.signature());
|
|
||||||
if(!verifyContent(encryptedPublicKeyData, contentBuilder.bytes().toBytes(), licenseSignature.contentSignature)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final byte[] hash = Base64.encodeBytesToBytes(encryptedPublicKeyData);
|
|
||||||
return Arrays.equals(hash, licenseSignature.hash);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
} finally {
|
|
||||||
if (licenseSignature != null) {
|
|
||||||
licenseSignature.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean verifyContent(byte[] encryptedPublicKeyData, byte[] data, byte[] contentSignature) {
|
|
||||||
try {
|
|
||||||
Signature rsa = Signature.getInstance("SHA512withRSA");
|
|
||||||
rsa.initVerify(CryptUtils.readEncryptedPublicKey(encryptedPublicKeyData));
|
|
||||||
rsa.update(data);
|
|
||||||
return rsa.verify(contentSignature);
|
|
||||||
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] getPublicKeyContentFromResource(String resource) {
|
|
||||||
try {
|
|
||||||
return Streams.copyToBytesFromClasspath(resource);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new IllegalStateException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signature structure:
|
|
||||||
* | VERSION | MAGIC | PUB_KEY_DIGEST | SIGNED_LICENSE_CONTENT |
|
|
||||||
*/
|
|
||||||
private static LicenseSignature parseSignature(String signature) throws IOException {
|
|
||||||
byte[] signatureBytes = Base64.decode(signature);
|
|
||||||
ByteBuffer byteBuffer = ByteBuffer.wrap(signatureBytes);
|
|
||||||
int version = byteBuffer.getInt();
|
|
||||||
int magicLen = byteBuffer.getInt();
|
|
||||||
byte[] magic = new byte[magicLen];
|
|
||||||
byteBuffer.get(magic);
|
|
||||||
int hashLen = byteBuffer.getInt();
|
|
||||||
byte[] hash = new byte[hashLen];
|
|
||||||
byteBuffer.get(hash);
|
|
||||||
int signedContentLen = byteBuffer.getInt();
|
|
||||||
byte[] signedContent = new byte[signedContentLen];
|
|
||||||
byteBuffer.get(signedContent);
|
|
||||||
return new LicenseSignature(version, hash, signedContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LicenseSignature {
|
|
||||||
private final int version;
|
|
||||||
private final byte[] hash;
|
|
||||||
private final byte[] contentSignature;
|
|
||||||
|
|
||||||
private LicenseSignature(int version, byte[] hash, byte[] contentSignature) {
|
|
||||||
this.version = version;
|
|
||||||
this.hash = hash;
|
|
||||||
this.contentSignature = contentSignature;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clear() {
|
|
||||||
Arrays.fill(hash, (byte)0);
|
|
||||||
Arrays.fill(contentSignature, (byte)0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,149 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.core;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import org.elasticsearch.ElasticsearchParseException;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.common.xcontent.*;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class for operating on a collection of {@link License}s.
|
|
||||||
* Provides serialization/deserialization methods and reduce
|
|
||||||
* operations
|
|
||||||
*/
|
|
||||||
public final class Licenses {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* XContent param name to deserialize license(s) with
|
|
||||||
* an additional <code>status</code> field, indicating whether a
|
|
||||||
* particular license is 'active' or 'expired' and no signature
|
|
||||||
* and in a human readable format
|
|
||||||
*/
|
|
||||||
public static final String REST_VIEW_MODE = "rest_view";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* XContent param name to deserialize license(s) with
|
|
||||||
* no signature
|
|
||||||
*/
|
|
||||||
public static final String LICENSE_SPEC_VIEW_MODE = "license_spec_view";
|
|
||||||
|
|
||||||
private final static class Fields {
|
|
||||||
static final String LICENSES = "licenses";
|
|
||||||
}
|
|
||||||
|
|
||||||
private final static class XFields {
|
|
||||||
static final XContentBuilderString LICENSES = new XContentBuilderString(Fields.LICENSES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Licenses() {}
|
|
||||||
|
|
||||||
public static void toXContent(Collection<License> licenses, XContentBuilder builder, ToXContent.Params params) throws IOException {
|
|
||||||
builder.startObject();
|
|
||||||
builder.startArray(XFields.LICENSES);
|
|
||||||
for (License license : licenses) {
|
|
||||||
license.toXContent(builder, params);
|
|
||||||
}
|
|
||||||
builder.endArray();
|
|
||||||
builder.endObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<License> fromSource(String content) throws IOException {
|
|
||||||
return fromSource(content.getBytes(StandardCharsets.UTF_8), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<License> fromSource(byte[] bytes) throws IOException {
|
|
||||||
return fromXContent(XContentFactory.xContent(bytes).createParser(bytes), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<License> fromSource(byte[] bytes, boolean verify) throws IOException {
|
|
||||||
return fromXContent(XContentFactory.xContent(bytes).createParser(bytes), verify);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<License> fromXContent(XContentParser parser, boolean verify) throws IOException {
|
|
||||||
List<License> licenses = new ArrayList<>();
|
|
||||||
if (parser.nextToken() == XContentParser.Token.START_OBJECT) {
|
|
||||||
if (parser.nextToken() == XContentParser.Token.FIELD_NAME) {
|
|
||||||
String currentFieldName = parser.currentName();
|
|
||||||
if (Fields.LICENSES.equals(currentFieldName)) {
|
|
||||||
if (parser.nextToken() == XContentParser.Token.START_ARRAY) {
|
|
||||||
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
|
|
||||||
License.Builder builder = License.builder().fromXContent(parser);
|
|
||||||
if (verify) {
|
|
||||||
builder.validate();
|
|
||||||
}
|
|
||||||
licenses.add(builder.build());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new ElasticsearchParseException("failed to parse licenses expected an array of licenses");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Ignore all other fields - might be created with new version
|
|
||||||
} else {
|
|
||||||
throw new ElasticsearchParseException("failed to parse licenses expected field");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new ElasticsearchParseException("failed to parse licenses expected start object");
|
|
||||||
}
|
|
||||||
return licenses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<License> readFrom(StreamInput in) throws IOException {
|
|
||||||
int size = in.readVInt();
|
|
||||||
List<License> licenses = new ArrayList<>(size);
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
licenses.add(License.readLicense(in));
|
|
||||||
}
|
|
||||||
return licenses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void writeTo(List<License> licenses, StreamOutput out) throws IOException {
|
|
||||||
out.writeVInt(licenses.size());
|
|
||||||
for (License license : licenses) {
|
|
||||||
license.writeTo(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a set of {@link License}s, reduces the set to one license per feature.
|
|
||||||
* Uses {@link #putIfAppropriate(java.util.Map, License)} to reduce.
|
|
||||||
*
|
|
||||||
* @param licensesSet a set of licenses to be reduced
|
|
||||||
* @return a map of (feature, license)
|
|
||||||
*/
|
|
||||||
public static ImmutableMap<String, License> reduceAndMap(Set<License> licensesSet) {
|
|
||||||
Map<String, License> map = new HashMap<>(licensesSet.size());
|
|
||||||
for (License license : licensesSet) {
|
|
||||||
putIfAppropriate(map, license);
|
|
||||||
}
|
|
||||||
return ImmutableMap.copyOf(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds or updates the <code>license</code> to <code>licenseMap</code> if the <code>license</code>
|
|
||||||
* has not expired already and if the <code>license</code> has a later expiry date from any
|
|
||||||
* existing licenses in <code>licenseMap</code> for the same feature
|
|
||||||
*
|
|
||||||
* @param licenseMap a map of (feature, license)
|
|
||||||
* @param license a new license to be added to <code>licenseMap</code>
|
|
||||||
*/
|
|
||||||
private static void putIfAppropriate(Map<String, License> licenseMap, License license) {
|
|
||||||
final String featureType = license.feature();
|
|
||||||
if (licenseMap.containsKey(featureType)) {
|
|
||||||
final License previousLicense = licenseMap.get(featureType);
|
|
||||||
if (license.expiryDate() > previousLicense.expiryDate()) {
|
|
||||||
licenseMap.put(featureType, license);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
licenseMap.put(featureType, license);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,115 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.core;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import org.elasticsearch.common.xcontent.*;
|
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static org.hamcrest.core.IsEqual.equalTo;
|
|
||||||
import static org.hamcrest.core.IsNull.notNullValue;
|
|
||||||
import static org.hamcrest.core.IsNull.nullValue;
|
|
||||||
|
|
||||||
public class LicenseSerializationTests extends ElasticsearchTestCase {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSimpleIssueExpiryDate() throws Exception {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
String issueDate = TestUtils.dateMathString("now", now);
|
|
||||||
String expiryDate = TestUtils.dateMathString("now+10d/d", now);
|
|
||||||
String licenseSpecs = TestUtils.generateLicenseSpecString(Arrays.asList(new TestUtils.LicenseSpec("shield", issueDate, expiryDate)));
|
|
||||||
Set<License> licensesOutput = new HashSet<>(Licenses.fromSource(licenseSpecs.getBytes(StandardCharsets.UTF_8), false));
|
|
||||||
License generatedLicense = licensesOutput.iterator().next();
|
|
||||||
|
|
||||||
assertThat(licensesOutput.size(), equalTo(1));
|
|
||||||
assertThat(generatedLicense.issueDate(), equalTo(DateUtils.beginningOfTheDay(issueDate)));
|
|
||||||
assertThat(generatedLicense.expiryDate(), equalTo(DateUtils.endOfTheDay(expiryDate)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMultipleIssueExpiryDate() throws Exception {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
String shieldIssueDate = TestUtils.dateMathString("now", now);
|
|
||||||
String shieldExpiryDate = TestUtils.dateMathString("now+30d/d", now);
|
|
||||||
String marvelIssueDate = TestUtils.dateMathString("now", now);
|
|
||||||
String marvelExpiryDate = TestUtils.dateMathString("now+60d/d", now);
|
|
||||||
String licenseSpecs = TestUtils.generateLicenseSpecString(Arrays.asList(new TestUtils.LicenseSpec("shield", shieldIssueDate, shieldExpiryDate)));
|
|
||||||
String licenseSpecs1 = TestUtils.generateLicenseSpecString(Arrays.asList(new TestUtils.LicenseSpec("marvel", marvelIssueDate, marvelExpiryDate)));
|
|
||||||
Set<License> licensesOutput = new HashSet<>();
|
|
||||||
licensesOutput.addAll(Licenses.fromSource(licenseSpecs.getBytes(StandardCharsets.UTF_8), false));
|
|
||||||
licensesOutput.addAll(Licenses.fromSource(licenseSpecs1.getBytes(StandardCharsets.UTF_8), false));
|
|
||||||
assertThat(licensesOutput.size(), equalTo(2));
|
|
||||||
for (License license : licensesOutput) {
|
|
||||||
assertThat(license.issueDate(), equalTo(DateUtils.beginningOfTheDay((license.feature().equals("shield")) ? shieldIssueDate : marvelIssueDate)));
|
|
||||||
assertThat(license.expiryDate(), equalTo(DateUtils.endOfTheDay((license.feature().equals("shield")) ? shieldExpiryDate : marvelExpiryDate)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLicensesFields() throws Exception {
|
|
||||||
Map<String, TestUtils.LicenseSpec> licenseSpecs = new HashMap<>();
|
|
||||||
for (int i = 0; i < randomIntBetween(1, 5); i++) {
|
|
||||||
TestUtils.LicenseSpec randomLicenseSpec = TestUtils.generateRandomLicenseSpec();
|
|
||||||
licenseSpecs.put(randomLicenseSpec.feature, randomLicenseSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayList<TestUtils.LicenseSpec> specs = new ArrayList<>(licenseSpecs.values());
|
|
||||||
String licenseSpecsSource = TestUtils.generateLicenseSpecString(specs);
|
|
||||||
Set<License> licensesOutput = new HashSet<>(Licenses.fromSource(licenseSpecsSource.getBytes(StandardCharsets.UTF_8), false));
|
|
||||||
assertThat(licensesOutput.size(), equalTo(licenseSpecs.size()));
|
|
||||||
|
|
||||||
for (License license : licensesOutput) {
|
|
||||||
TestUtils.LicenseSpec spec = licenseSpecs.get(license.feature());
|
|
||||||
assertThat(spec, notNullValue());
|
|
||||||
TestUtils.assertLicenseSpec(spec, license);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLicenseRestView() throws Exception {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
String expiredLicenseExpiryDate = TestUtils.dateMathString("now-1d/d", now);
|
|
||||||
String validLicenseIssueDate = TestUtils.dateMathString("now-10d/d", now);
|
|
||||||
String invalidLicenseIssueDate = TestUtils.dateMathString("now+1d/d", now);
|
|
||||||
String validLicenseExpiryDate = TestUtils.dateMathString("now+2d/d", now);
|
|
||||||
|
|
||||||
Set<License> licenses = TestUtils.generateLicenses(Arrays.asList(new TestUtils.LicenseSpec("expired_feature", validLicenseIssueDate, expiredLicenseExpiryDate)
|
|
||||||
, new TestUtils.LicenseSpec("valid_feature", validLicenseIssueDate, validLicenseExpiryDate),
|
|
||||||
new TestUtils.LicenseSpec("invalid_feature", invalidLicenseIssueDate, validLicenseExpiryDate)));
|
|
||||||
|
|
||||||
assertThat(licenses.size(), equalTo(3));
|
|
||||||
for (License license : licenses) {
|
|
||||||
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
|
|
||||||
license.toXContent(builder, new ToXContent.MapParams(ImmutableMap.of(Licenses.REST_VIEW_MODE, "true")));
|
|
||||||
builder.flush();
|
|
||||||
Map<String, Object> map = XContentHelper.convertToMap(builder.bytesStream().bytes(), false).v2();
|
|
||||||
|
|
||||||
// should have an extra status field, human readable issue_data and expiry_date
|
|
||||||
assertThat(map.get("status"), notNullValue());
|
|
||||||
assertThat(map.get("issue_date"), notNullValue());
|
|
||||||
assertThat(map.get("expiry_date"), notNullValue());
|
|
||||||
if (license.feature().equals("valid_feature")) {
|
|
||||||
assertThat((String) map.get("status"), equalTo("active"));
|
|
||||||
} else if (license.feature().equals("expired_feature")) {
|
|
||||||
assertThat((String) map.get("status"), equalTo("expired"));
|
|
||||||
} else if (license.feature().equals("invalid_feature")) {
|
|
||||||
assertThat((String) map.get("status"), equalTo("invalid"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (License license : licenses) {
|
|
||||||
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
|
|
||||||
license.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
|
||||||
builder.flush();
|
|
||||||
Map<String, Object> map = XContentHelper.convertToMap(builder.bytesStream().bytes(), false).v2();
|
|
||||||
assertThat(map.get("status"), nullValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,224 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.core;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.joda.DateMathParser;
|
|
||||||
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
|
|
||||||
import org.elasticsearch.common.joda.Joda;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.hamcrest.MatcherAssert;
|
|
||||||
import org.joda.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
import static com.carrotsearch.randomizedtesting.RandomizedTest.*;
|
|
||||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
|
||||||
import static org.elasticsearch.test.ElasticsearchTestCase.randomFrom;
|
|
||||||
import static org.hamcrest.core.IsEqual.equalTo;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
public class TestUtils {
|
|
||||||
|
|
||||||
private final static FormatDateTimeFormatter formatDateTimeFormatter = Joda.forPattern("yyyy-MM-dd");
|
|
||||||
private final static DateMathParser dateMathParser = new DateMathParser(formatDateTimeFormatter);
|
|
||||||
private final static DateTimeFormatter dateTimeFormatter = formatDateTimeFormatter.printer();
|
|
||||||
|
|
||||||
public static void isSame(Set<License> firstLicenses, Set<License> secondLicenses) {
|
|
||||||
|
|
||||||
// we do the verifyAndBuild to make sure we weed out any expired licenses
|
|
||||||
final Map<String, License> licenses1 = Licenses.reduceAndMap(firstLicenses);
|
|
||||||
final Map<String, License> licenses2 = Licenses.reduceAndMap(secondLicenses);
|
|
||||||
|
|
||||||
// check if the effective licenses have the same feature set
|
|
||||||
assertThat(licenses1.size(), equalTo(licenses2.size()));
|
|
||||||
|
|
||||||
// for every feature license, check if all the attributes are the same
|
|
||||||
for (String featureType : licenses1.keySet()) {
|
|
||||||
License license1 = licenses1.get(featureType);
|
|
||||||
License license2 = licenses2.get(featureType);
|
|
||||||
isSame(license1, license2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void isSame(License license1, License license2) {
|
|
||||||
assertThat(license1.uid(), equalTo(license2.uid()));
|
|
||||||
assertThat(license1.feature(), equalTo(license2.feature()));
|
|
||||||
assertThat(license1.subscriptionType(), equalTo(license2.subscriptionType()));
|
|
||||||
assertThat(license1.type(), equalTo(license2.type()));
|
|
||||||
assertThat(license1.issuedTo(), equalTo(license2.issuedTo()));
|
|
||||||
assertThat(license1.signature(), equalTo(license2.signature()));
|
|
||||||
assertThat(license1.expiryDate(), equalTo(license2.expiryDate()));
|
|
||||||
assertThat(license1.issueDate(), equalTo(license2.issueDate()));
|
|
||||||
assertThat(license1.maxNodes(), equalTo(license2.maxNodes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String dateMathString(String time, final long now) {
|
|
||||||
return dateTimeFormatter.print(dateMathParser.parse(time, new Callable<Long>() {
|
|
||||||
@Override
|
|
||||||
public Long call() throws Exception {
|
|
||||||
return now;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long dateMath(String time, final long now) {
|
|
||||||
return dateMathParser.parse(time, new Callable<Long>() {
|
|
||||||
@Override
|
|
||||||
public Long call() throws Exception {
|
|
||||||
return now;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LicenseSpec generateRandomLicenseSpec() {
|
|
||||||
boolean datesInMillis = randomBoolean();
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
String uid = UUID.randomUUID().toString();
|
|
||||||
String feature = "feature__" + randomInt();
|
|
||||||
String issuer = "issuer__" + randomInt();
|
|
||||||
String issuedTo = "issuedTo__" + randomInt();
|
|
||||||
String type = randomFrom("subscription", "internal", "development");
|
|
||||||
String subscriptionType = randomFrom("none", "gold", "silver", "platinum");
|
|
||||||
int maxNodes = randomIntBetween(5, 100);
|
|
||||||
if (datesInMillis) {
|
|
||||||
long issueDateInMillis = dateMath("now", now);
|
|
||||||
long expiryDateInMillis = dateMath("now+10d/d", now);
|
|
||||||
return new LicenseSpec(uid, feature, issueDateInMillis, expiryDateInMillis, type, subscriptionType, issuedTo, issuer, maxNodes);
|
|
||||||
} else {
|
|
||||||
String issueDate = dateMathString("now", now);
|
|
||||||
String expiryDate = dateMathString("now+10d/d", now);
|
|
||||||
return new LicenseSpec(uid, feature, issueDate, expiryDate, type, subscriptionType, issuedTo, issuer, maxNodes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String generateLicenseSpecString(List<LicenseSpec> licenseSpecs) throws IOException {
|
|
||||||
XContentBuilder licenses = jsonBuilder();
|
|
||||||
licenses.startObject();
|
|
||||||
licenses.startArray("licenses");
|
|
||||||
for (LicenseSpec licenseSpec : licenseSpecs) {
|
|
||||||
licenses.startObject()
|
|
||||||
.field("uid", licenseSpec.uid)
|
|
||||||
.field("type", licenseSpec.type)
|
|
||||||
.field("subscription_type", licenseSpec.subscriptionType)
|
|
||||||
.field("issued_to", licenseSpec.issuedTo)
|
|
||||||
.field("issuer", licenseSpec.issuer)
|
|
||||||
.field("feature", licenseSpec.feature)
|
|
||||||
.field("max_nodes", licenseSpec.maxNodes);
|
|
||||||
|
|
||||||
if (licenseSpec.issueDate != null) {
|
|
||||||
licenses.field("issue_date", licenseSpec.issueDate);
|
|
||||||
} else {
|
|
||||||
licenses.field("issue_date_in_millis", licenseSpec.issueDateInMillis);
|
|
||||||
}
|
|
||||||
if (licenseSpec.expiryDate != null) {
|
|
||||||
licenses.field("expiry_date", licenseSpec.expiryDate);
|
|
||||||
} else {
|
|
||||||
licenses.field("expiry_date_in_millis", licenseSpec.expiryDateInMillis);
|
|
||||||
}
|
|
||||||
licenses.endObject();
|
|
||||||
}
|
|
||||||
licenses.endArray();
|
|
||||||
licenses.endObject();
|
|
||||||
return licenses.string();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Set<License> generateLicenses(List<LicenseSpec> licenseSpecs) {
|
|
||||||
Set<License> unSignedLicenses = new HashSet<>();
|
|
||||||
for (TestUtils.LicenseSpec spec : licenseSpecs) {
|
|
||||||
License.Builder builder = License.builder()
|
|
||||||
.uid(spec.uid)
|
|
||||||
.feature(spec.feature)
|
|
||||||
.type(spec.type)
|
|
||||||
.subscriptionType(spec.subscriptionType)
|
|
||||||
.issuedTo(spec.issuedTo)
|
|
||||||
.issuer(spec.issuer)
|
|
||||||
.maxNodes(spec.maxNodes);
|
|
||||||
|
|
||||||
if (spec.expiryDate != null) {
|
|
||||||
builder.expiryDate(DateUtils.endOfTheDay(spec.expiryDate));
|
|
||||||
} else {
|
|
||||||
builder.expiryDate(spec.expiryDateInMillis);
|
|
||||||
}
|
|
||||||
if (spec.issueDate != null) {
|
|
||||||
builder.issueDate(DateUtils.beginningOfTheDay(spec.issueDate));
|
|
||||||
} else {
|
|
||||||
builder.issueDate(spec.issueDateInMillis);
|
|
||||||
}
|
|
||||||
unSignedLicenses.add(builder.build());
|
|
||||||
}
|
|
||||||
return unSignedLicenses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void assertLicenseSpec(LicenseSpec spec, License license) {
|
|
||||||
MatcherAssert.assertThat(license.uid(), equalTo(spec.uid));
|
|
||||||
MatcherAssert.assertThat(license.feature(), equalTo(spec.feature));
|
|
||||||
MatcherAssert.assertThat(license.issuedTo(), equalTo(spec.issuedTo));
|
|
||||||
MatcherAssert.assertThat(license.issuer(), equalTo(spec.issuer));
|
|
||||||
MatcherAssert.assertThat(license.type(), equalTo(spec.type));
|
|
||||||
MatcherAssert.assertThat(license.subscriptionType(), equalTo(spec.subscriptionType));
|
|
||||||
MatcherAssert.assertThat(license.maxNodes(), equalTo(spec.maxNodes));
|
|
||||||
if (spec.issueDate != null) {
|
|
||||||
MatcherAssert.assertThat(license.issueDate(), equalTo(DateUtils.beginningOfTheDay(spec.issueDate)));
|
|
||||||
} else {
|
|
||||||
MatcherAssert.assertThat(license.issueDate(), equalTo(spec.issueDateInMillis));
|
|
||||||
}
|
|
||||||
if (spec.expiryDate != null) {
|
|
||||||
MatcherAssert.assertThat(license.expiryDate(), equalTo(DateUtils.endOfTheDay(spec.expiryDate)));
|
|
||||||
} else {
|
|
||||||
MatcherAssert.assertThat(license.expiryDate(), equalTo(spec.expiryDateInMillis));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class LicenseSpec {
|
|
||||||
public final String feature;
|
|
||||||
public final String issueDate;
|
|
||||||
public final long issueDateInMillis;
|
|
||||||
public final String expiryDate;
|
|
||||||
public final long expiryDateInMillis;
|
|
||||||
public final String uid;
|
|
||||||
public final String type;
|
|
||||||
public final String subscriptionType;
|
|
||||||
public final String issuedTo;
|
|
||||||
public final String issuer;
|
|
||||||
public final int maxNodes;
|
|
||||||
|
|
||||||
public LicenseSpec(String feature, String issueDate, String expiryDate) {
|
|
||||||
this(UUID.randomUUID().toString(), feature, issueDate, expiryDate, "trial", "none", "customer", "elasticsearch", 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LicenseSpec(String uid, String feature, long issueDateInMillis, long expiryDateInMillis, String type,
|
|
||||||
String subscriptionType, String issuedTo, String issuer, int maxNodes) {
|
|
||||||
this.feature = feature;
|
|
||||||
this.issueDateInMillis = issueDateInMillis;
|
|
||||||
this.issueDate = null;
|
|
||||||
this.expiryDateInMillis = expiryDateInMillis;
|
|
||||||
this.expiryDate = null;
|
|
||||||
this.uid = uid;
|
|
||||||
this.type = type;
|
|
||||||
this.subscriptionType = subscriptionType;
|
|
||||||
this.issuedTo = issuedTo;
|
|
||||||
this.issuer = issuer;
|
|
||||||
this.maxNodes = maxNodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LicenseSpec(String uid, String feature, String issueDate, String expiryDate, String type,
|
|
||||||
String subscriptionType, String issuedTo, String issuer, int maxNodes) {
|
|
||||||
this.feature = feature;
|
|
||||||
this.issueDate = issueDate;
|
|
||||||
this.issueDateInMillis = -1;
|
|
||||||
this.expiryDate = expiryDate;
|
|
||||||
this.expiryDateInMillis = -1;
|
|
||||||
this.uid = uid;
|
|
||||||
this.type = type;
|
|
||||||
this.subscriptionType = subscriptionType;
|
|
||||||
this.issuedTo = issuedTo;
|
|
||||||
this.issuer = issuer;
|
|
||||||
this.maxNodes = maxNodes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
es.logger.level=INFO
|
|
||||||
log4j.rootLogger=${es.logger.level}, out
|
|
||||||
|
|
||||||
log4j.logger.org.apache.http=INFO, out
|
|
||||||
log4j.additivity.org.apache.http=false
|
|
||||||
|
|
||||||
log4j.logger.org.elasticsearch.license=TRACE
|
|
||||||
|
|
||||||
log4j.appender.out=org.apache.log4j.ConsoleAppender
|
|
||||||
log4j.appender.out.layout=org.apache.log4j.PatternLayout
|
|
||||||
log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n
|
|
Binary file not shown.
|
@ -1,3 +0,0 @@
|
||||||
ýŽÇqÝnęÄĚgŠśwM}Ťą‡UiKŠ•0âbÖ2Řşqö]â쇴ŻÖĎĂcĚ+IŇđÔ &IJ†fÉ~ßlj <09>ş]d™}o§OčľId®Č
|
|
||||||
5A(ěµ´^ŘöW©DŤŞJµë}ů-Oîë?u N5ľŰvpŰ{’Ľ˛Áôśát–ť¤7ůřĂę#˛Vqöó»ktwm’Ś]ĎLőŁz"| Q‹lźňQđsâ>ů<}Ź[Á2ÖÓŕZÖ|5‹ŻŤĘĘ7%ŘęD
|
|
||||||
Yĺ‘xn:ĽlúLČćHň˘«Ë2<C38B>źHvEEWÇ\¦H:“6Žh9 [!š…Űć©Š¤+;Ö.w7Cě©_|ŢÓŞĎÁ*ń§D`<60>Ú?‚ůxU/3>xUÓ“+ č
|
|
|
@ -1,15 +0,0 @@
|
||||||
ELASTICSEARCH CONFIDENTIAL
|
|
||||||
__________________
|
|
||||||
|
|
||||||
[2014] Elasticsearch Incorporated
|
|
||||||
All Rights Reserved.
|
|
||||||
|
|
||||||
NOTICE: All information contained herein is, and remains
|
|
||||||
the property of Elasticsearch Incorporated and its suppliers,
|
|
||||||
if any. The intellectual and technical concepts contained
|
|
||||||
herein are proprietary to Elasticsearch Incorporated
|
|
||||||
and its suppliers and may be covered by U.S. and Foreign Patents,
|
|
||||||
patents in process, and are protected by trade secret or copyright law.
|
|
||||||
Dissemination of this information or reproduction of this material
|
|
||||||
is strictly forbidden unless prior written permission is obtained
|
|
||||||
from Elasticsearch Incorporated.
|
|
|
@ -1,13 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<additionalHeaders>
|
|
||||||
<javadoc_style>
|
|
||||||
<firstLine>/*</firstLine>
|
|
||||||
<beforeEachLine> * </beforeEachLine>
|
|
||||||
<endLine> */</endLine>
|
|
||||||
<!--skipLine></skipLine-->
|
|
||||||
<firstLineDetectionPattern>(\s|\t)*/\*.*$</firstLineDetectionPattern>
|
|
||||||
<lastLineDetectionPattern>.*\*/(\s|\t)*$</lastLineDetectionPattern>
|
|
||||||
<allowBlankLines>false</allowBlankLines>
|
|
||||||
<isMultiline>true</isMultiline>
|
|
||||||
</javadoc_style>
|
|
||||||
</additionalHeaders>
|
|
|
@ -1,55 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
# or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
# you may not use this file except in compliance with the Elastic License.
|
|
||||||
|
|
||||||
CDPATH=""
|
|
||||||
SCRIPT="$0"
|
|
||||||
|
|
||||||
# SCRIPT may be an arbitrarily deep series of symlinks. Loop until we have the concrete path.
|
|
||||||
while [ -h "$SCRIPT" ] ; do
|
|
||||||
ls=`ls -ld "$SCRIPT"`
|
|
||||||
# Drop everything prior to ->
|
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
|
||||||
SCRIPT="$link"
|
|
||||||
else
|
|
||||||
SCRIPT=`dirname "$SCRIPT"`/"$link"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# determine license home
|
|
||||||
LICENSE_HOME=`dirname "$SCRIPT"`/..
|
|
||||||
|
|
||||||
# make LICENSE_HOME absolute
|
|
||||||
LICENSE_HOME=`cd "$LICENSE_HOME"; pwd`
|
|
||||||
|
|
||||||
# setup classpath
|
|
||||||
LICENSE_CLASSPATH=$LICENSE_CLASSPATH:$LICENSE_HOME/lib/${project.artifactId}-${project.version}-exec.jar:$LICENSE_HOME/lib/*
|
|
||||||
|
|
||||||
if [ -x "$JAVA_HOME/bin/java" ]; then
|
|
||||||
JAVA=$JAVA_HOME/bin/java
|
|
||||||
else
|
|
||||||
JAVA=`which java`
|
|
||||||
fi
|
|
||||||
|
|
||||||
# real getopt cannot be used because we need to hand options over to the KeyPairGeneratorTool
|
|
||||||
while [ $# -gt 0 ]; do
|
|
||||||
case $1 in
|
|
||||||
-D*=*)
|
|
||||||
properties="$properties $1"
|
|
||||||
;;
|
|
||||||
-D*)
|
|
||||||
var=$1
|
|
||||||
shift
|
|
||||||
properties="$properties $var=$1"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
args="$args $1"
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
|
|
||||||
exec "$JAVA" $JAVA_OPTS -Xmx64m -Xms16m $properties -cp "$LICENSE_CLASSPATH" org.elasticsearch.license.licensor.tools.KeyPairGeneratorTool $args
|
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
# or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
# you may not use this file except in compliance with the Elastic License.
|
|
||||||
|
|
||||||
CDPATH=""
|
|
||||||
SCRIPT="$0"
|
|
||||||
|
|
||||||
# SCRIPT may be an arbitrarily deep series of symlinks. Loop until we have the concrete path.
|
|
||||||
while [ -h "$SCRIPT" ] ; do
|
|
||||||
ls=`ls -ld "$SCRIPT"`
|
|
||||||
# Drop everything prior to ->
|
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
|
||||||
SCRIPT="$link"
|
|
||||||
else
|
|
||||||
SCRIPT=`dirname "$SCRIPT"`/"$link"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# determine license home
|
|
||||||
LICENSE_HOME=`dirname "$SCRIPT"`/..
|
|
||||||
|
|
||||||
# make LICENSE_HOME absolute
|
|
||||||
LICENSE_HOME=`cd "$LICENSE_HOME"; pwd`
|
|
||||||
|
|
||||||
# setup classpath
|
|
||||||
LICENSE_CLASSPATH=$LICENSE_CLASSPATH:$LICENSE_HOME/lib/${project.artifactId}-${project.version}-exec.jar:$LICENSE_HOME/lib/*
|
|
||||||
|
|
||||||
if [ -x "$JAVA_HOME/bin/java" ]; then
|
|
||||||
JAVA=$JAVA_HOME/bin/java
|
|
||||||
else
|
|
||||||
JAVA=`which java`
|
|
||||||
fi
|
|
||||||
|
|
||||||
# real getopt cannot be used because we need to hand options over to the LicenseGeneratorTool
|
|
||||||
while [ $# -gt 0 ]; do
|
|
||||||
case $1 in
|
|
||||||
-D*=*)
|
|
||||||
properties="$properties $1"
|
|
||||||
;;
|
|
||||||
-D*)
|
|
||||||
var=$1
|
|
||||||
shift
|
|
||||||
properties="$properties $var=$1"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
args="$args $1"
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
|
|
||||||
exec "$JAVA" $JAVA_OPTS -Xmx64m -Xms16m $properties -cp "$LICENSE_CLASSPATH" org.elasticsearch.license.licensor.tools.LicenseGeneratorTool $args
|
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
# or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
# you may not use this file except in compliance with the Elastic License.
|
|
||||||
|
|
||||||
CDPATH=""
|
|
||||||
SCRIPT="$0"
|
|
||||||
|
|
||||||
# SCRIPT may be an arbitrarily deep series of symlinks. Loop until we have the concrete path.
|
|
||||||
while [ -h "$SCRIPT" ] ; do
|
|
||||||
ls=`ls -ld "$SCRIPT"`
|
|
||||||
# Drop everything prior to ->
|
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
|
||||||
SCRIPT="$link"
|
|
||||||
else
|
|
||||||
SCRIPT=`dirname "$SCRIPT"`/"$link"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# determine license home
|
|
||||||
LICENSE_HOME=`dirname "$SCRIPT"`/..
|
|
||||||
|
|
||||||
# make LICENSE_HOME absolute
|
|
||||||
LICENSE_HOME=`cd "$LICENSE_HOME"; pwd`
|
|
||||||
|
|
||||||
# setup classpath
|
|
||||||
LICENSE_CLASSPATH=$LICENSE_CLASSPATH:$LICENSE_HOME/lib/${project.artifactId}-${project.version}-exec.jar:$LICENSE_HOME/lib/*
|
|
||||||
|
|
||||||
if [ -x "$JAVA_HOME/bin/java" ]; then
|
|
||||||
JAVA=$JAVA_HOME/bin/java
|
|
||||||
else
|
|
||||||
JAVA=`which java`
|
|
||||||
fi
|
|
||||||
|
|
||||||
# real getopt cannot be used because we need to hand options over to the LicenseVerificationTool
|
|
||||||
while [ $# -gt 0 ]; do
|
|
||||||
case $1 in
|
|
||||||
-D*=*)
|
|
||||||
properties="$properties $1"
|
|
||||||
;;
|
|
||||||
-D*)
|
|
||||||
var=$1
|
|
||||||
shift
|
|
||||||
properties="$properties $var=$1"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
args="$args $1"
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
|
|
||||||
exec "$JAVA" $JAVA_OPTS -Xmx64m -Xms16m $properties -cp "$LICENSE_CLASSPATH" org.elasticsearch.license.licensor.tools.LicenseVerificationTool $args
|
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>elasticsearch-license</artifactId>
|
|
||||||
<groupId>org.elasticsearch</groupId>
|
|
||||||
<version>2.0.0.beta1-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>elasticsearch-license-licensor</artifactId>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<license.basedir combine.self="override">${project.parent.basedir}</license.basedir>
|
|
||||||
<elasticsearch.assembly.descriptor>${basedir}/src/main/assemblies/exec.xml</elasticsearch.assembly.descriptor>
|
|
||||||
<elasticsearch.assembly.appendId>true</elasticsearch.assembly.appendId>
|
|
||||||
<!-- we aren't really a plugin... -->
|
|
||||||
<skip.integ.tests>true</skip.integ.tests>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.elasticsearch</groupId>
|
|
||||||
<artifactId>elasticsearch-license-core</artifactId>
|
|
||||||
<version>2.0.0.beta1-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>for-exec</id>
|
|
||||||
<phase>prepare-package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>jar</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<classifier>exec</classifier>
|
|
||||||
<excludes>
|
|
||||||
<!-- exclude public key -->
|
|
||||||
<exclude>public.key</exclude>
|
|
||||||
</excludes>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1 +0,0 @@
|
||||||
{"licenses":[{"uid": "893361dc-9749-4997-93cb-802e3d7fa4a8", "type":"internal","subscription_type":"none","issued_to":"issuedTo","issuer":"issuer","issue_date":"2014-09-29","expiry_date":"2015-08-29","feature":"shield","max_nodes":1}]}
|
|
|
@ -1,47 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<assembly>
|
|
||||||
<id>exec</id>
|
|
||||||
<formats>
|
|
||||||
<format>zip</format>
|
|
||||||
</formats>
|
|
||||||
<includeBaseDirectory>true</includeBaseDirectory>
|
|
||||||
<fileSets>
|
|
||||||
<fileSet>
|
|
||||||
<filtered>true</filtered>
|
|
||||||
<directory>bin</directory>
|
|
||||||
<outputDirectory>bin</outputDirectory>
|
|
||||||
<fileMode>0755</fileMode>
|
|
||||||
<directoryMode>0755</directoryMode>
|
|
||||||
<lineEnding>unix</lineEnding>
|
|
||||||
<includes>
|
|
||||||
<include>key-pair-generator</include>
|
|
||||||
<include>license-generator</include>
|
|
||||||
<include>verify-license</include>
|
|
||||||
</includes>
|
|
||||||
</fileSet>
|
|
||||||
</fileSets>
|
|
||||||
<dependencySets>
|
|
||||||
<dependencySet>
|
|
||||||
<outputDirectory>/lib</outputDirectory>
|
|
||||||
<useProjectArtifact>false</useProjectArtifact>
|
|
||||||
<useProjectAttachments>true</useProjectAttachments>
|
|
||||||
<useTransitiveFiltering>true</useTransitiveFiltering>
|
|
||||||
<includes>
|
|
||||||
<include>org.elasticsearch:elasticsearch-license-licensor:*:exec</include>
|
|
||||||
<include>org.elasticsearch:elasticsearch</include>
|
|
||||||
</includes>
|
|
||||||
<excludes>
|
|
||||||
<exclude>org.apache.lucene:*</exclude>
|
|
||||||
</excludes>
|
|
||||||
</dependencySet>
|
|
||||||
<dependencySet>
|
|
||||||
<outputDirectory>/lib</outputDirectory>
|
|
||||||
<useProjectArtifact>false</useProjectArtifact>
|
|
||||||
<useProjectAttachments>true</useProjectAttachments>
|
|
||||||
<useTransitiveFiltering>true</useTransitiveFiltering>
|
|
||||||
<includes>
|
|
||||||
<include>org.apache.lucene:lucene-core</include>
|
|
||||||
</includes>
|
|
||||||
</dependencySet>
|
|
||||||
</dependencySets>
|
|
||||||
</assembly>
|
|
|
@ -1,104 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.licensor;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import org.elasticsearch.common.Base64;
|
|
||||||
import org.elasticsearch.common.SuppressForbidden;
|
|
||||||
import org.elasticsearch.common.io.PathUtils;
|
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.core.Licenses;
|
|
||||||
import org.elasticsearch.license.core.CryptUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.security.*;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Responsible for generating a license signature according to
|
|
||||||
* the signature spec and sign it with the provided encrypted private key
|
|
||||||
*/
|
|
||||||
@SuppressForbidden(reason = "can we avoid bare string paths and resolve from Environment or similar?")
|
|
||||||
public class LicenseSigner {
|
|
||||||
|
|
||||||
private final static int MAGIC_LENGTH = 13;
|
|
||||||
|
|
||||||
private final Path publicKeyPath;
|
|
||||||
|
|
||||||
private final Path privateKeyPath;
|
|
||||||
|
|
||||||
public LicenseSigner(final String privateKeyPath, final String publicKeyPath) {
|
|
||||||
this(PathUtils.get(privateKeyPath), PathUtils.get(publicKeyPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
public LicenseSigner(final Path privateKeyPath, final Path publicKeyPath) {
|
|
||||||
this.publicKeyPath = publicKeyPath;
|
|
||||||
this.privateKeyPath = privateKeyPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public ImmutableSet<License> sign(Set<License> licenseSpecs) throws IOException {
|
|
||||||
final ImmutableSet.Builder<License> builder = ImmutableSet.builder();
|
|
||||||
for (License licenseSpec : licenseSpecs) {
|
|
||||||
builder.add(sign(licenseSpec));
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a signature for the <code>licenseSpec</code>.
|
|
||||||
* Signature structure:
|
|
||||||
* | VERSION | MAGIC | PUB_KEY_DIGEST | SIGNED_LICENSE_CONTENT |
|
|
||||||
*
|
|
||||||
* @return a signed License
|
|
||||||
* @throws java.io.IOException
|
|
||||||
*/
|
|
||||||
public License sign(License licenseSpec) throws IOException {
|
|
||||||
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
|
|
||||||
licenseSpec.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(Licenses.LICENSE_SPEC_VIEW_MODE, "true")));
|
|
||||||
|
|
||||||
final byte[] signedContent = sign(contentBuilder.bytes().toBytes(), privateKeyPath);
|
|
||||||
final byte[] magic = new byte[MAGIC_LENGTH];
|
|
||||||
SecureRandom random = new SecureRandom();
|
|
||||||
random.nextBytes(magic);
|
|
||||||
final byte[] hash = Base64.encodeBytesToBytes(Files.readAllBytes(publicKeyPath));
|
|
||||||
assert hash != null;
|
|
||||||
byte[] bytes = new byte[4 + 4 + MAGIC_LENGTH + 4 + hash.length + 4 + signedContent.length];
|
|
||||||
|
|
||||||
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
|
|
||||||
byteBuffer.putInt(License.VERSION_CURRENT)
|
|
||||||
.putInt(magic.length)
|
|
||||||
.put(magic)
|
|
||||||
.putInt(hash.length)
|
|
||||||
.put(hash)
|
|
||||||
.putInt(signedContent.length)
|
|
||||||
.put(signedContent);
|
|
||||||
|
|
||||||
return License.builder()
|
|
||||||
.fromLicenseSpec(licenseSpec, Base64.encodeBytes(bytes))
|
|
||||||
.validate()
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] sign(byte[] data, Path privateKeyPath) {
|
|
||||||
try {
|
|
||||||
final Signature rsa = Signature.getInstance("SHA512withRSA");
|
|
||||||
rsa.initSign(CryptUtils.readEncryptedPrivateKey(Files.readAllBytes(privateKeyPath)));
|
|
||||||
rsa.update(data);
|
|
||||||
return rsa.sign();
|
|
||||||
} catch (InvalidKeyException | IOException | NoSuchAlgorithmException | SignatureException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.licensor.tools;
|
|
||||||
|
|
||||||
import org.apache.commons.cli.CommandLine;
|
|
||||||
import org.elasticsearch.common.cli.CliTool;
|
|
||||||
import org.elasticsearch.common.cli.CliToolConfig;
|
|
||||||
import org.elasticsearch.common.cli.Terminal;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.env.Environment;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.KeyPairGenerator;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
|
|
||||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
|
|
||||||
import static org.elasticsearch.common.cli.CliToolConfig.config;
|
|
||||||
import static org.elasticsearch.license.core.CryptUtils.writeEncryptedPrivateKey;
|
|
||||||
import static org.elasticsearch.license.core.CryptUtils.writeEncryptedPublicKey;
|
|
||||||
|
|
||||||
public class KeyPairGeneratorTool extends CliTool {
|
|
||||||
|
|
||||||
public static final String NAME = "key-pair-generator";
|
|
||||||
private static final CliToolConfig CONFIG = config("licensor", KeyPairGeneratorTool.class)
|
|
||||||
.cmds(KeyGenerator.CMD)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
public KeyPairGeneratorTool() {
|
|
||||||
super(CONFIG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Command parse(String s, CommandLine commandLine) throws Exception {
|
|
||||||
return KeyGenerator.parse(terminal, commandLine, env);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class KeyGenerator extends Command {
|
|
||||||
|
|
||||||
private static final CliToolConfig.Cmd CMD = cmd(NAME, KeyGenerator.class)
|
|
||||||
.options(
|
|
||||||
option("pub", "publicKeyPath").required(true).hasArg(true),
|
|
||||||
option("pri", "privateKeyPath").required(true).hasArg(true)
|
|
||||||
).build();
|
|
||||||
|
|
||||||
public final Path publicKeyPath;
|
|
||||||
public final Path privateKeyPath;
|
|
||||||
|
|
||||||
protected KeyGenerator(Terminal terminal, Path publicKeyPath, Path privateKeyPath) {
|
|
||||||
super(terminal);
|
|
||||||
this.privateKeyPath = privateKeyPath;
|
|
||||||
this.publicKeyPath = publicKeyPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Command parse(Terminal terminal, CommandLine commandLine, Environment environment) {
|
|
||||||
Path publicKeyPath = environment.homeFile().resolve(commandLine.getOptionValue("publicKeyPath"));
|
|
||||||
Path privateKeyPath = environment.homeFile().resolve(commandLine.getOptionValue("privateKeyPath"));
|
|
||||||
|
|
||||||
if (Files.exists(privateKeyPath)) {
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, privateKeyPath + " already exists");
|
|
||||||
} else if (Files.exists(publicKeyPath)) {
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, publicKeyPath + " already exists");
|
|
||||||
}
|
|
||||||
return new KeyGenerator(terminal, publicKeyPath, privateKeyPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
|
||||||
KeyPair keyPair = generateKeyPair(privateKeyPath, publicKeyPath);
|
|
||||||
terminal.println(Terminal.Verbosity.VERBOSE, "generating key pair [public key: " + publicKeyPath + ", private key: " + privateKeyPath + "]");
|
|
||||||
return (keyPair != null) ? ExitStatus.OK : ExitStatus.CANT_CREATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static KeyPair generateKeyPair(Path privateKeyPath, Path publicKeyPath) throws IOException, NoSuchAlgorithmException {
|
|
||||||
SecureRandom random = new SecureRandom();
|
|
||||||
|
|
||||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
|
||||||
keyGen.initialize(2048, random);
|
|
||||||
KeyPair keyPair = keyGen.generateKeyPair();
|
|
||||||
|
|
||||||
saveKeyPairToFiles(keyPair, privateKeyPath, publicKeyPath);
|
|
||||||
return keyPair;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void saveKeyPairToFiles(KeyPair keyPair, Path privateKeyPath, Path publicKeyPath) throws IOException {
|
|
||||||
Files.write(privateKeyPath, writeEncryptedPrivateKey(keyPair.getPrivate()));
|
|
||||||
Files.write(publicKeyPath, writeEncryptedPublicKey(keyPair.getPublic()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
|
||||||
int status = new KeyPairGeneratorTool().execute(args);
|
|
||||||
System.exit(status);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,126 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.licensor.tools;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import org.apache.commons.cli.CommandLine;
|
|
||||||
import org.elasticsearch.common.cli.CliTool;
|
|
||||||
import org.elasticsearch.common.cli.CliToolConfig;
|
|
||||||
import org.elasticsearch.common.cli.Terminal;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
|
||||||
import org.elasticsearch.env.Environment;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.core.Licenses;
|
|
||||||
import org.elasticsearch.license.licensor.LicenseSigner;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
|
|
||||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
|
|
||||||
import static org.elasticsearch.common.cli.CliToolConfig.config;
|
|
||||||
|
|
||||||
public class LicenseGeneratorTool extends CliTool {
|
|
||||||
public static final String NAME = "license-generator";
|
|
||||||
|
|
||||||
private static final CliToolConfig CONFIG = config("licensor", LicenseGeneratorTool.class)
|
|
||||||
.cmds(LicenseGenerator.CMD)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
public LicenseGeneratorTool() {
|
|
||||||
super(CONFIG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Command parse(String s, CommandLine commandLine) throws Exception {
|
|
||||||
return LicenseGenerator.parse(terminal, commandLine, env);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class LicenseGenerator extends Command {
|
|
||||||
|
|
||||||
private static final CliToolConfig.Cmd CMD = cmd(NAME, LicenseGenerator.class)
|
|
||||||
.options(
|
|
||||||
option("pub", "publicKeyPath").required(true).hasArg(true),
|
|
||||||
option("pri", "privateKeyPath").required(true).hasArg(true),
|
|
||||||
option("l", "license").required(false).hasArg(true),
|
|
||||||
option("lf", "licenseFile").required(false).hasArg(true)
|
|
||||||
).build();
|
|
||||||
|
|
||||||
public final Set<License> licenseSpecs;
|
|
||||||
public final Path publicKeyFilePath;
|
|
||||||
public final Path privateKeyFilePath;
|
|
||||||
|
|
||||||
public LicenseGenerator(Terminal terminal, Path publicKeyFilePath, Path privateKeyFilePath, Set<License> licenseSpecs) {
|
|
||||||
super(terminal);
|
|
||||||
this.licenseSpecs = licenseSpecs;
|
|
||||||
this.privateKeyFilePath = privateKeyFilePath;
|
|
||||||
this.publicKeyFilePath = publicKeyFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Command parse(Terminal terminal, CommandLine commandLine, Environment environment) throws IOException {
|
|
||||||
Path publicKeyPath = environment.homeFile().resolve(commandLine.getOptionValue("publicKeyPath"));
|
|
||||||
Path privateKeyPath = environment.homeFile().resolve(commandLine.getOptionValue("privateKeyPath"));
|
|
||||||
String[] licenseSpecSources = commandLine.getOptionValues("license");
|
|
||||||
String[] licenseSpecSourceFiles = commandLine.getOptionValues("licenseFile");
|
|
||||||
|
|
||||||
if (!Files.exists(privateKeyPath)) {
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, privateKeyPath + " does not exist");
|
|
||||||
} else if (!Files.exists(publicKeyPath)) {
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, publicKeyPath + " does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<License> licenseSpecs = new HashSet<>();
|
|
||||||
if (licenseSpecSources != null) {
|
|
||||||
for (String licenseSpec : licenseSpecSources) {
|
|
||||||
licenseSpecs.addAll(Licenses.fromSource(licenseSpec.getBytes(StandardCharsets.UTF_8), false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (licenseSpecSourceFiles != null) {
|
|
||||||
for (String licenseSpecFilePath : licenseSpecSourceFiles) {
|
|
||||||
Path licenseSpecPath = environment.homeFile().resolve(licenseSpecFilePath);
|
|
||||||
if (!Files.exists(licenseSpecPath)) {
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, licenseSpecFilePath + " does not exist");
|
|
||||||
}
|
|
||||||
licenseSpecs.addAll(Licenses.fromSource(Files.readAllBytes(licenseSpecPath), false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (licenseSpecs.size() == 0) {
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, "no license spec provided");
|
|
||||||
}
|
|
||||||
return new LicenseGenerator(terminal, publicKeyPath, privateKeyPath, licenseSpecs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
|
||||||
|
|
||||||
// sign
|
|
||||||
ImmutableSet<License> signedLicences = new LicenseSigner(privateKeyFilePath, publicKeyFilePath).sign(licenseSpecs);
|
|
||||||
|
|
||||||
// dump
|
|
||||||
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
|
|
||||||
Licenses.toXContent(signedLicences, builder, ToXContent.EMPTY_PARAMS);
|
|
||||||
builder.flush();
|
|
||||||
terminal.print(builder.string());
|
|
||||||
|
|
||||||
return ExitStatus.OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
|
||||||
int status = new LicenseGeneratorTool().execute(args);
|
|
||||||
System.exit(status);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,126 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.licensor.tools;
|
|
||||||
|
|
||||||
import org.apache.commons.cli.CommandLine;
|
|
||||||
import org.elasticsearch.common.cli.CliTool;
|
|
||||||
import org.elasticsearch.common.cli.CliToolConfig;
|
|
||||||
import org.elasticsearch.common.cli.Terminal;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
|
||||||
import org.elasticsearch.env.Environment;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.core.Licenses;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
|
|
||||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
|
|
||||||
import static org.elasticsearch.common.cli.CliToolConfig.config;
|
|
||||||
|
|
||||||
public class LicenseVerificationTool extends CliTool {
|
|
||||||
public static final String NAME = "verify-license";
|
|
||||||
|
|
||||||
private static final CliToolConfig CONFIG = config("licensor", LicenseVerificationTool.class)
|
|
||||||
.cmds(LicenseVerifier.CMD)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
public LicenseVerificationTool() {
|
|
||||||
super(CONFIG);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Command parse(String s, CommandLine commandLine) throws Exception {
|
|
||||||
return LicenseVerifier.parse(terminal, commandLine, env);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class LicenseVerifier extends Command {
|
|
||||||
|
|
||||||
private static final CliToolConfig.Cmd CMD = cmd(NAME, LicenseVerifier.class)
|
|
||||||
.options(
|
|
||||||
option("pub", "publicKeyPath").required(true).hasArg(true),
|
|
||||||
option("l", "license").required(false).hasArg(true),
|
|
||||||
option("lf", "licenseFile").required(false).hasArg(true)
|
|
||||||
).build();
|
|
||||||
|
|
||||||
public final Set<License> licenses;
|
|
||||||
public final Path publicKeyPath;
|
|
||||||
|
|
||||||
public LicenseVerifier(Terminal terminal, Set<License> licenses, Path publicKeyPath) {
|
|
||||||
super(terminal);
|
|
||||||
this.licenses = licenses;
|
|
||||||
this.publicKeyPath = publicKeyPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Command parse(Terminal terminal, CommandLine commandLine, Environment environment) throws IOException {
|
|
||||||
String publicKeyPathString = commandLine.getOptionValue("publicKeyPath");
|
|
||||||
String[] licenseSources = commandLine.getOptionValues("license");
|
|
||||||
String[] licenseSourceFiles = commandLine.getOptionValues("licenseFile");
|
|
||||||
|
|
||||||
Set<License> licenses = new HashSet<>();
|
|
||||||
if (licenseSources != null) {
|
|
||||||
for (String licenseSpec : licenseSources) {
|
|
||||||
licenses.addAll(Licenses.fromSource(licenseSpec.getBytes(StandardCharsets.UTF_8)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (licenseSourceFiles != null) {
|
|
||||||
for (String licenseFilePath : licenseSourceFiles) {
|
|
||||||
Path licensePath = environment.homeFile().resolve(licenseFilePath);
|
|
||||||
if (!Files.exists(licensePath)) {
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, licenseFilePath + " does not exist");
|
|
||||||
}
|
|
||||||
licenses.addAll(Licenses.fromSource(Files.readAllBytes(licensePath)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (licenses.size() == 0) {
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, "no license provided");
|
|
||||||
}
|
|
||||||
|
|
||||||
Path publicKeyPath = environment.homeFile().resolve(publicKeyPathString);
|
|
||||||
if (!Files.exists(publicKeyPath)) {
|
|
||||||
return exitCmd(ExitStatus.USAGE, terminal, publicKeyPath + " does not exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LicenseVerifier(terminal, licenses, publicKeyPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
|
||||||
|
|
||||||
// verify
|
|
||||||
Map<String, License> effectiveLicenses = Licenses.reduceAndMap(licenses);
|
|
||||||
|
|
||||||
if (!org.elasticsearch.license.core.LicenseVerifier.verifyLicenses(effectiveLicenses.values(), publicKeyPath)) {
|
|
||||||
terminal.println("Invalid License(s)!");
|
|
||||||
return ExitStatus.DATA_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dump effective licences
|
|
||||||
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
|
|
||||||
Licenses.toXContent(effectiveLicenses.values(), builder, ToXContent.EMPTY_PARAMS);
|
|
||||||
builder.flush();
|
|
||||||
terminal.print(builder.string());
|
|
||||||
|
|
||||||
return ExitStatus.OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
|
||||||
int status = new LicenseVerificationTool().execute(args);
|
|
||||||
System.exit(status);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
NAME
|
|
||||||
|
|
||||||
key-pair-generator - generates a key pair with RSA 2048-bit security
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
|
|
||||||
key-pair-generator -pub publicKeyPath -pri privateKeyPath
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
|
|
||||||
This tool generates and saves a key pair to the provided publicKeyPath
|
|
||||||
and privateKeyPath. The tool checks the existence of the provided key paths
|
|
||||||
and will not override if any existing keys are found.
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
|
|
||||||
-h,--help Shows this message
|
|
||||||
|
|
||||||
-pub,--publicKeyPath <path> Save the generated public key to path
|
|
||||||
|
|
||||||
-pri,--privateKeyPath <path> Save the generated private key to path
|
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
NAME
|
|
||||||
|
|
||||||
license-generator - generates signed elasticsearch license(s) for a given license spec(s)
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
|
|
||||||
license-generator -l licenseSpec -pub publicKeyPath -pri privateKeyPath
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
|
|
||||||
This tool generate elasticsearch license(s) for the provided license spec(s). The tool
|
|
||||||
can take arbitrary number of `--license` and/or `--licenseFile` to generate corrosponding
|
|
||||||
signed license(s).
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
|
|
||||||
-h,--help Shows this message
|
|
||||||
|
|
||||||
-l,--license <license spec> License spec to generate a signed license from
|
|
||||||
|
|
||||||
-lf,--licenseFile <path> Path to a license spec file
|
|
||||||
|
|
||||||
-pub,--publicKeyPath <path> Path to public key to be used
|
|
||||||
|
|
||||||
-pri,--privateKeyPath <path> Path to private key to be used
|
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
NAME
|
|
||||||
|
|
||||||
verify-license - verifies the integrity of elasticsearch signed license(s)
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
|
|
||||||
verify-license -l signedLicense -pub publicKeyPath
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
|
|
||||||
This tool assumes the configured public key to be the same as that of the production license plugin public key.
|
|
||||||
The tool can take arbitrary number of `--license` and/or `--licenseFile` for verifying signed license(s). If any
|
|
||||||
of the provided license(s) are invalid, the tool will error out, otherwise it will output a effective licenses file.
|
|
||||||
|
|
||||||
Effective Licenses:
|
|
||||||
A set of licenses that only has one effective sub-license for every feature provided through the input license file.
|
|
||||||
Where effective sub-licenses are identified as the sub-licenses with the latest `expiry_date` for a `feature`
|
|
||||||
and the sub-license has not already expired.
|
|
||||||
|
|
||||||
OPTIONS
|
|
||||||
|
|
||||||
-h,--help Shows this message
|
|
||||||
|
|
||||||
-l,--license <signed license> signed license(s) string
|
|
||||||
|
|
||||||
-lf,--licenseFile <path> Path to signed license(s) file
|
|
||||||
|
|
||||||
-pub,--publicKeyPath <path> Path to public key to verify against
|
|
|
@ -1,97 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.licensor;/*
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.license.core.DateUtils;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public abstract class AbstractLicensingTestBase extends ElasticsearchTestCase {
|
|
||||||
|
|
||||||
protected String pubKeyPath = null;
|
|
||||||
protected String priKeyPath = null;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() throws Exception {
|
|
||||||
pubKeyPath = getResourcePath("/public.key");
|
|
||||||
priKeyPath = getResourcePath("/private.key");
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void cleanUp() {
|
|
||||||
pubKeyPath = null;
|
|
||||||
priKeyPath = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Set<License> generateSignedLicenses(List<TestUtils.LicenseSpec> licenseSpecs, String pubKeyPath, String priKeyPath) throws Exception {
|
|
||||||
LicenseSigner signer = new LicenseSigner(priKeyPath, pubKeyPath);
|
|
||||||
Set<License> unSignedLicenses = new HashSet<>();
|
|
||||||
for (TestUtils.LicenseSpec spec : licenseSpecs) {
|
|
||||||
License.Builder builder = License.builder()
|
|
||||||
.uid(spec.uid)
|
|
||||||
.feature(spec.feature)
|
|
||||||
.type(spec.type)
|
|
||||||
.subscriptionType(spec.subscriptionType)
|
|
||||||
.issuedTo(spec.issuedTo)
|
|
||||||
.issuer(spec.issuer)
|
|
||||||
.maxNodes(spec.maxNodes);
|
|
||||||
|
|
||||||
if (spec.expiryDate != null) {
|
|
||||||
builder.expiryDate(DateUtils.endOfTheDay(spec.expiryDate));
|
|
||||||
} else {
|
|
||||||
builder.expiryDate(spec.expiryDateInMillis);
|
|
||||||
}
|
|
||||||
if (spec.issueDate != null) {
|
|
||||||
builder.issueDate(DateUtils.beginningOfTheDay(spec.issueDate));
|
|
||||||
} else {
|
|
||||||
builder.issueDate(spec.issueDateInMillis);
|
|
||||||
}
|
|
||||||
unSignedLicenses.add(builder.build());
|
|
||||||
}
|
|
||||||
return signer.sign(unSignedLicenses);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static License generateSignedLicense(String feature, TimeValue expiryDuration, String pubKeyPath, String priKeyPath) throws Exception {
|
|
||||||
return generateSignedLicense(feature, -1, expiryDuration, pubKeyPath, priKeyPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static License generateSignedLicense(String feature, long issueDate, TimeValue expiryDuration, String pubKeyPath, String priKeyPath) throws Exception {
|
|
||||||
long issue = (issueDate != -1l) ? issueDate : System.currentTimeMillis();
|
|
||||||
final License licenseSpec = License.builder()
|
|
||||||
.uid(UUID.randomUUID().toString())
|
|
||||||
.feature(feature)
|
|
||||||
.expiryDate(issue + expiryDuration.getMillis())
|
|
||||||
.issueDate(issue)
|
|
||||||
.type("subscription")
|
|
||||||
.subscriptionType("gold")
|
|
||||||
.issuedTo("customer")
|
|
||||||
.issuer("elasticsearch")
|
|
||||||
.maxNodes(5)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
LicenseSigner signer = new LicenseSigner(priKeyPath, pubKeyPath);
|
|
||||||
return signer.sign(licenseSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTestPriKeyPath() throws Exception {
|
|
||||||
return getResourcePath("/private.key");
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTestPubKeyPath() throws Exception {
|
|
||||||
return getResourcePath("/public.key");
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getResourcePath(String resource) throws Exception {
|
|
||||||
return getDataPath(resource).toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.licensor;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.core.LicenseVerifier;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
|
|
||||||
public class LicenseVerificationTests extends AbstractLicensingTestBase {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGeneratedLicenses() throws Exception {
|
|
||||||
License shieldLicense = generateSignedLicense("shield", TimeValue.timeValueHours(2 * 24), pubKeyPath, priKeyPath);
|
|
||||||
assertThat(LicenseVerifier.verifyLicense(shieldLicense), equalTo(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMultipleFeatureLicenses() throws Exception {
|
|
||||||
License shieldLicense = generateSignedLicense("shield", TimeValue.timeValueHours(2 * 24), pubKeyPath, priKeyPath);
|
|
||||||
License marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(2 * 24), pubKeyPath, priKeyPath);
|
|
||||||
|
|
||||||
assertThat(LicenseVerifier.verifyLicenses(Arrays.asList(shieldLicense, marvelLicense)), equalTo(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLicenseTampering() throws Exception {
|
|
||||||
License license = generateSignedLicense("shield", TimeValue.timeValueHours(2), pubKeyPath, priKeyPath);
|
|
||||||
|
|
||||||
final License tamperedLicense = License.builder()
|
|
||||||
.fromLicenseSpec(license, license.signature())
|
|
||||||
.expiryDate(license.expiryDate() + 10 * 24 * 60 * 60 * 1000l)
|
|
||||||
.validate()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
assertThat(LicenseVerifier.verifyLicense(tamperedLicense), equalTo(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRandomLicenseVerification() throws Exception {
|
|
||||||
int n = randomIntBetween(5, 15);
|
|
||||||
List<TestUtils.LicenseSpec> licenseSpecs = new ArrayList<>();
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
licenseSpecs.add(TestUtils.generateRandomLicenseSpec());
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<License> generatedLicenses = generateSignedLicenses(licenseSpecs, pubKeyPath, priKeyPath);
|
|
||||||
assertThat(generatedLicenses.size(), equalTo(n));
|
|
||||||
|
|
||||||
for (License generatedLicense: generatedLicenses) {
|
|
||||||
assertThat(LicenseVerifier.verifyLicense(generatedLicense), equalTo(true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,213 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.licensor;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.joda.DateMathParser;
|
|
||||||
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
|
|
||||||
import org.elasticsearch.common.joda.Joda;
|
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
|
||||||
import org.elasticsearch.license.core.DateUtils;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.core.Licenses;
|
|
||||||
import org.hamcrest.MatcherAssert;
|
|
||||||
import org.joda.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
import static com.carrotsearch.randomizedtesting.RandomizedTest.*;
|
|
||||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
|
||||||
import static org.elasticsearch.test.ElasticsearchTestCase.randomFrom;
|
|
||||||
import static org.hamcrest.core.IsEqual.equalTo;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
public class TestUtils {
|
|
||||||
|
|
||||||
public static final String PUBLIC_KEY_RESOURCE = "/public.key";
|
|
||||||
public static final String PRIVATE_KEY_RESOURCE = "/private.key";
|
|
||||||
|
|
||||||
private final static FormatDateTimeFormatter formatDateTimeFormatter = Joda.forPattern("yyyy-MM-dd");
|
|
||||||
private final static DateMathParser dateMathParser = new DateMathParser(formatDateTimeFormatter);
|
|
||||||
private final static DateTimeFormatter dateTimeFormatter = formatDateTimeFormatter.printer();
|
|
||||||
|
|
||||||
public static void isSame(Set<License> firstLicenses, Set<License> secondLicenses) {
|
|
||||||
|
|
||||||
// we do the verifyAndBuild to make sure we weed out any expired licenses
|
|
||||||
final Map<String, License> licenses1 = Licenses.reduceAndMap(firstLicenses);
|
|
||||||
final Map<String, License> licenses2 = Licenses.reduceAndMap(secondLicenses);
|
|
||||||
|
|
||||||
// check if the effective licenses have the same feature set
|
|
||||||
assertThat(licenses1.size(), equalTo(licenses2.size()));
|
|
||||||
|
|
||||||
// for every feature license, check if all the attributes are the same
|
|
||||||
for (String featureType : licenses1.keySet()) {
|
|
||||||
License license1 = licenses1.get(featureType);
|
|
||||||
License license2 = licenses2.get(featureType);
|
|
||||||
isSame(license1, license2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void isSame(License license1, License license2) {
|
|
||||||
assertThat(license1.uid(), equalTo(license2.uid()));
|
|
||||||
assertThat(license1.feature(), equalTo(license2.feature()));
|
|
||||||
assertThat(license1.subscriptionType(), equalTo(license2.subscriptionType()));
|
|
||||||
assertThat(license1.type(), equalTo(license2.type()));
|
|
||||||
assertThat(license1.issuedTo(), equalTo(license2.issuedTo()));
|
|
||||||
assertThat(license1.signature(), equalTo(license2.signature()));
|
|
||||||
assertThat(license1.expiryDate(), equalTo(license2.expiryDate()));
|
|
||||||
assertThat(license1.issueDate(), equalTo(license2.issueDate()));
|
|
||||||
assertThat(license1.maxNodes(), equalTo(license2.maxNodes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String dumpLicense(License license) throws Exception {
|
|
||||||
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
|
|
||||||
Licenses.toXContent(Collections.singletonList(license), builder, ToXContent.EMPTY_PARAMS);
|
|
||||||
builder.flush();
|
|
||||||
return builder.string();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String dateMathString(String time, final long now) {
|
|
||||||
return dateTimeFormatter.print(dateMathParser.parse(time, new Callable<Long>() {
|
|
||||||
@Override
|
|
||||||
public Long call() throws Exception {
|
|
||||||
return now;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long dateMath(String time, final long now) {
|
|
||||||
return dateMathParser.parse(time, new Callable<Long>() {
|
|
||||||
@Override
|
|
||||||
public Long call() throws Exception {
|
|
||||||
return now;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LicenseSpec generateRandomLicenseSpec() {
|
|
||||||
boolean datesInMillis = randomBoolean();
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
String uid = UUID.randomUUID().toString();
|
|
||||||
String feature = "feature__" + randomInt();
|
|
||||||
String issuer = "issuer__" + randomInt();
|
|
||||||
String issuedTo = "issuedTo__" + randomInt();
|
|
||||||
String type = randomFrom("subscription", "internal", "development");
|
|
||||||
String subscriptionType = randomFrom("none", "gold", "silver", "platinum");
|
|
||||||
int maxNodes = randomIntBetween(5, 100);
|
|
||||||
if (datesInMillis) {
|
|
||||||
long issueDateInMillis = dateMath("now", now);
|
|
||||||
long expiryDateInMillis = dateMath("now+10d/d", now);
|
|
||||||
return new LicenseSpec(uid, feature, issueDateInMillis, expiryDateInMillis, type, subscriptionType, issuedTo, issuer, maxNodes);
|
|
||||||
} else {
|
|
||||||
String issueDate = dateMathString("now", now);
|
|
||||||
String expiryDate = dateMathString("now+10d/d", now);
|
|
||||||
return new LicenseSpec(uid, feature, issueDate, expiryDate, type, subscriptionType, issuedTo, issuer, maxNodes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String generateLicenseSpecString(List<LicenseSpec> licenseSpecs) throws IOException {
|
|
||||||
XContentBuilder licenses = jsonBuilder();
|
|
||||||
licenses.startObject();
|
|
||||||
licenses.startArray("licenses");
|
|
||||||
for (LicenseSpec licenseSpec : licenseSpecs) {
|
|
||||||
licenses.startObject()
|
|
||||||
.field("uid", licenseSpec.uid)
|
|
||||||
.field("type", licenseSpec.type)
|
|
||||||
.field("subscription_type", licenseSpec.subscriptionType)
|
|
||||||
.field("issued_to", licenseSpec.issuedTo)
|
|
||||||
.field("issuer", licenseSpec.issuer)
|
|
||||||
.field("feature", licenseSpec.feature)
|
|
||||||
.field("max_nodes", licenseSpec.maxNodes);
|
|
||||||
|
|
||||||
if (licenseSpec.issueDate != null) {
|
|
||||||
licenses.field("issue_date", licenseSpec.issueDate);
|
|
||||||
} else {
|
|
||||||
licenses.field("issue_date_in_millis", licenseSpec.issueDateInMillis);
|
|
||||||
}
|
|
||||||
if (licenseSpec.expiryDate != null) {
|
|
||||||
licenses.field("expiry_date", licenseSpec.expiryDate);
|
|
||||||
} else {
|
|
||||||
licenses.field("expiry_date_in_millis", licenseSpec.expiryDateInMillis);
|
|
||||||
}
|
|
||||||
licenses.endObject();
|
|
||||||
}
|
|
||||||
licenses.endArray();
|
|
||||||
licenses.endObject();
|
|
||||||
return licenses.string();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void assertLicenseSpec(LicenseSpec spec, License license) {
|
|
||||||
MatcherAssert.assertThat(license.uid(), equalTo(spec.uid));
|
|
||||||
MatcherAssert.assertThat(license.feature(), equalTo(spec.feature));
|
|
||||||
MatcherAssert.assertThat(license.issuedTo(), equalTo(spec.issuedTo));
|
|
||||||
MatcherAssert.assertThat(license.issuer(), equalTo(spec.issuer));
|
|
||||||
MatcherAssert.assertThat(license.type(), equalTo(spec.type));
|
|
||||||
MatcherAssert.assertThat(license.subscriptionType(), equalTo(spec.subscriptionType));
|
|
||||||
MatcherAssert.assertThat(license.maxNodes(), equalTo(spec.maxNodes));
|
|
||||||
if (spec.issueDate != null) {
|
|
||||||
MatcherAssert.assertThat(license.issueDate(), equalTo(DateUtils.beginningOfTheDay(spec.issueDate)));
|
|
||||||
} else {
|
|
||||||
MatcherAssert.assertThat(license.issueDate(), equalTo(spec.issueDateInMillis));
|
|
||||||
}
|
|
||||||
if (spec.expiryDate != null) {
|
|
||||||
MatcherAssert.assertThat(license.expiryDate(), equalTo(DateUtils.endOfTheDay(spec.expiryDate)));
|
|
||||||
} else {
|
|
||||||
MatcherAssert.assertThat(license.expiryDate(), equalTo(spec.expiryDateInMillis));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class LicenseSpec {
|
|
||||||
public final String feature;
|
|
||||||
public final String issueDate;
|
|
||||||
public final long issueDateInMillis;
|
|
||||||
public final String expiryDate;
|
|
||||||
public final long expiryDateInMillis;
|
|
||||||
public final String uid;
|
|
||||||
public final String type;
|
|
||||||
public final String subscriptionType;
|
|
||||||
public final String issuedTo;
|
|
||||||
public final String issuer;
|
|
||||||
public final int maxNodes;
|
|
||||||
|
|
||||||
public LicenseSpec(String feature, String issueDate, String expiryDate) {
|
|
||||||
this(UUID.randomUUID().toString(), feature, issueDate, expiryDate, "trial", "none", "customer", "elasticsearch", 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LicenseSpec(String uid, String feature, long issueDateInMillis, long expiryDateInMillis, String type,
|
|
||||||
String subscriptionType, String issuedTo, String issuer, int maxNodes) {
|
|
||||||
this.feature = feature;
|
|
||||||
this.issueDateInMillis = issueDateInMillis;
|
|
||||||
this.issueDate = null;
|
|
||||||
this.expiryDateInMillis = expiryDateInMillis;
|
|
||||||
this.expiryDate = null;
|
|
||||||
this.uid = uid;
|
|
||||||
this.type = type;
|
|
||||||
this.subscriptionType = subscriptionType;
|
|
||||||
this.issuedTo = issuedTo;
|
|
||||||
this.issuer = issuer;
|
|
||||||
this.maxNodes = maxNodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LicenseSpec(String uid, String feature, String issueDate, String expiryDate, String type,
|
|
||||||
String subscriptionType, String issuedTo, String issuer, int maxNodes) {
|
|
||||||
this.feature = feature;
|
|
||||||
this.issueDate = issueDate;
|
|
||||||
this.issueDateInMillis = -1;
|
|
||||||
this.expiryDate = expiryDate;
|
|
||||||
this.expiryDateInMillis = -1;
|
|
||||||
this.uid = uid;
|
|
||||||
this.type = type;
|
|
||||||
this.subscriptionType = subscriptionType;
|
|
||||||
this.issuedTo = issuedTo;
|
|
||||||
this.issuer = issuer;
|
|
||||||
this.maxNodes = maxNodes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.licensor.tools;
|
|
||||||
|
|
||||||
import org.apache.commons.cli.MissingOptionException;
|
|
||||||
import org.elasticsearch.common.cli.CliToolTestCase;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.env.Environment;
|
|
||||||
import org.elasticsearch.license.licensor.tools.KeyPairGeneratorTool.KeyGenerator;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
import static org.elasticsearch.common.cli.CliTool.Command;
|
|
||||||
import static org.elasticsearch.common.cli.CliTool.ExitStatus;
|
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
|
||||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
|
||||||
import static org.hamcrest.core.IsEqual.equalTo;
|
|
||||||
|
|
||||||
public class KeyPairGenerationToolTests extends CliToolTestCase {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParsingMissingPath() throws Exception {
|
|
||||||
KeyPairGeneratorTool keyPairGeneratorTool = new KeyPairGeneratorTool();
|
|
||||||
Path tempFile = createTempFile();
|
|
||||||
try {
|
|
||||||
keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME, args(
|
|
||||||
"--privateKeyPath " + tempFile.toAbsolutePath()));
|
|
||||||
fail("no public key path provided");
|
|
||||||
} catch (MissingOptionException e) {
|
|
||||||
assertThat(e.getMessage(), containsString("pub"));
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME, args(
|
|
||||||
"--publicKeyPath " + tempFile.toAbsolutePath()));
|
|
||||||
fail("no private key path provided");
|
|
||||||
} catch (MissingOptionException e) {
|
|
||||||
assertThat(e.getMessage(), containsString("pri"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParsingNeverOverrideKey() throws Exception {
|
|
||||||
KeyPairGeneratorTool keyPairGeneratorTool = new KeyPairGeneratorTool();
|
|
||||||
Path tempFile = createTempFile();
|
|
||||||
Path tempFile2 = createTempFile();
|
|
||||||
String nonExistentFilePath = tempFile2.toAbsolutePath().toString();
|
|
||||||
Files.delete(tempFile2);
|
|
||||||
assertThat(Files.exists(tempFile2), equalTo(false));
|
|
||||||
|
|
||||||
Command command = keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME, new String[] {"--privateKeyPath", tempFile.toAbsolutePath().toString(),
|
|
||||||
"--publicKeyPath", nonExistentFilePath });
|
|
||||||
|
|
||||||
assertThat(command, instanceOf(Command.Exit.class));
|
|
||||||
Command.Exit exitCommand = (Command.Exit) command;
|
|
||||||
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
|
|
||||||
|
|
||||||
command = keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME, new String[] {"--publicKeyPath", tempFile.toAbsolutePath().toString(),
|
|
||||||
"--privateKeyPath", nonExistentFilePath });
|
|
||||||
|
|
||||||
assertThat(command, instanceOf(Command.Exit.class));
|
|
||||||
exitCommand = (Command.Exit) command;
|
|
||||||
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testToolSimple() throws Exception {
|
|
||||||
KeyPairGeneratorTool keyPairGeneratorTool = new KeyPairGeneratorTool();
|
|
||||||
Path publicKeyFilePath = createTempFile().toAbsolutePath();
|
|
||||||
Path privateKeyFilePath = createTempFile().toAbsolutePath();
|
|
||||||
Settings settings = Settings.builder().put("path.home", createTempDir("KeyPairGenerationToolTests")).build();
|
|
||||||
|
|
||||||
Files.delete(publicKeyFilePath);
|
|
||||||
Files.delete(privateKeyFilePath);
|
|
||||||
assertThat(Files.exists(publicKeyFilePath), equalTo(false));
|
|
||||||
assertThat(Files.exists(privateKeyFilePath), equalTo(false));
|
|
||||||
|
|
||||||
Command command = keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME, new String[] { "--privateKeyPath", privateKeyFilePath.toString(),
|
|
||||||
"--publicKeyPath", publicKeyFilePath.toString() });
|
|
||||||
|
|
||||||
assertThat(command, instanceOf(KeyGenerator.class));
|
|
||||||
KeyGenerator keyGenerator = (KeyGenerator) command;
|
|
||||||
assertThat(keyGenerator.privateKeyPath, equalTo(privateKeyFilePath));
|
|
||||||
assertThat(keyGenerator.publicKeyPath, equalTo(publicKeyFilePath));
|
|
||||||
|
|
||||||
assertThat(Files.exists(publicKeyFilePath), equalTo(false));
|
|
||||||
assertThat(Files.exists(privateKeyFilePath), equalTo(false));
|
|
||||||
|
|
||||||
assertThat(keyGenerator.execute(settings, new Environment(settings)), equalTo(ExitStatus.OK));
|
|
||||||
assertThat(Files.exists(publicKeyFilePath), equalTo(true));
|
|
||||||
assertThat(Files.exists(privateKeyFilePath), equalTo(true));
|
|
||||||
|
|
||||||
Files.delete(publicKeyFilePath);
|
|
||||||
Files.delete(privateKeyFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,197 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.licensor.tools;
|
|
||||||
|
|
||||||
import org.apache.commons.cli.MissingOptionException;
|
|
||||||
import org.elasticsearch.common.cli.CliToolTestCase;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.env.Environment;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.core.Licenses;
|
|
||||||
import org.elasticsearch.license.licensor.TestUtils;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static org.elasticsearch.common.cli.CliTool.Command;
|
|
||||||
import static org.elasticsearch.common.cli.CliTool.ExitStatus;
|
|
||||||
import static org.elasticsearch.license.licensor.tools.LicenseGeneratorTool.LicenseGenerator;
|
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
|
||||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
|
||||||
import static org.hamcrest.core.IsEqual.equalTo;
|
|
||||||
|
|
||||||
public class LicenseGenerationToolTests extends CliToolTestCase {
|
|
||||||
|
|
||||||
protected Path pubKeyPath = null;
|
|
||||||
protected Path priKeyPath = null;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() throws Exception {
|
|
||||||
logger.error("project.basedir [{}]", System.getProperty("project.basedir"));
|
|
||||||
pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
|
|
||||||
priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParsingNonExistentKeyFile() throws Exception {
|
|
||||||
TestUtils.LicenseSpec inputLicenseSpec = TestUtils.generateRandomLicenseSpec();
|
|
||||||
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
|
|
||||||
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
|
|
||||||
new String[] {"--license", TestUtils.generateLicenseSpecString(Arrays.asList(inputLicenseSpec)),
|
|
||||||
"--publicKeyPath", pubKeyPath.toString().concat("invalid"),
|
|
||||||
"--privateKeyPath", priKeyPath.toString() });
|
|
||||||
|
|
||||||
assertThat(command, instanceOf(Command.Exit.class));
|
|
||||||
Command.Exit exitCommand = (Command.Exit) command;
|
|
||||||
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
|
|
||||||
|
|
||||||
command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
|
|
||||||
new String[] {"--license", TestUtils.generateLicenseSpecString(Arrays.asList(inputLicenseSpec)),
|
|
||||||
"--privateKeyPath", priKeyPath.toString().concat("invalid"),
|
|
||||||
"--publicKeyPath", pubKeyPath.toString() });
|
|
||||||
|
|
||||||
assertThat(command, instanceOf(Command.Exit.class));
|
|
||||||
exitCommand = (Command.Exit) command;
|
|
||||||
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParsingMissingLicenseSpec() throws Exception {
|
|
||||||
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
|
|
||||||
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
|
|
||||||
new String[] { "--publicKeyPath", pubKeyPath.toString(),
|
|
||||||
"--privateKeyPath", priKeyPath.toString() });
|
|
||||||
|
|
||||||
assertThat(command, instanceOf(Command.Exit.class));
|
|
||||||
Command.Exit exitCommand = (Command.Exit) command;
|
|
||||||
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParsingMissingArgs() throws Exception {
|
|
||||||
TestUtils.LicenseSpec inputLicenseSpec = TestUtils.generateRandomLicenseSpec();
|
|
||||||
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
|
|
||||||
boolean pubKeyMissing = randomBoolean();
|
|
||||||
try {
|
|
||||||
licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
|
|
||||||
new String[] { "--license", TestUtils.generateLicenseSpecString(Arrays.asList(inputLicenseSpec)),
|
|
||||||
((pubKeyMissing) ? "--privateKeyPath" : "--publicKeyPath"),
|
|
||||||
((pubKeyMissing) ? priKeyPath.toString() : pubKeyPath.toString()) });
|
|
||||||
fail("missing argument: " + ((pubKeyMissing) ? "publicKeyPath" : "privateKeyPath") + " should throw an exception");
|
|
||||||
} catch (MissingOptionException e) {
|
|
||||||
assertThat(e.getMessage(), containsString((pubKeyMissing) ? "pub" : "pri"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParsingSimple() throws Exception {
|
|
||||||
TestUtils.LicenseSpec inputLicenseSpec = TestUtils.generateRandomLicenseSpec();
|
|
||||||
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
|
|
||||||
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
|
|
||||||
new String[]{"--license", TestUtils.generateLicenseSpecString(Arrays.asList(inputLicenseSpec)),
|
|
||||||
"--publicKeyPath", pubKeyPath.toString(),
|
|
||||||
"--privateKeyPath", priKeyPath.toString() });
|
|
||||||
|
|
||||||
assertThat(command, instanceOf(LicenseGenerator.class));
|
|
||||||
LicenseGenerator licenseGenerator = (LicenseGenerator) command;
|
|
||||||
assertThat(licenseGenerator.publicKeyFilePath, equalTo(pubKeyPath));
|
|
||||||
assertThat(licenseGenerator.privateKeyFilePath, equalTo(priKeyPath));
|
|
||||||
assertThat(licenseGenerator.licenseSpecs.size(), equalTo(1));
|
|
||||||
License outputLicenseSpec = licenseGenerator.licenseSpecs.iterator().next();
|
|
||||||
|
|
||||||
TestUtils.assertLicenseSpec(inputLicenseSpec, outputLicenseSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParsingLicenseFile() throws Exception {
|
|
||||||
TestUtils.LicenseSpec inputLicenseSpec = TestUtils.generateRandomLicenseSpec();
|
|
||||||
Path tempFile = createTempFile();
|
|
||||||
Files.write(tempFile, TestUtils.generateLicenseSpecString(Arrays.asList(inputLicenseSpec)).getBytes(StandardCharsets.UTF_8));
|
|
||||||
|
|
||||||
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
|
|
||||||
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
|
|
||||||
new String[] { "--licenseFile", tempFile.toAbsolutePath().toString(),
|
|
||||||
"--publicKeyPath", pubKeyPath.toString(),
|
|
||||||
"--privateKeyPath", priKeyPath.toString() });
|
|
||||||
|
|
||||||
assertThat(command, instanceOf(LicenseGenerator.class));
|
|
||||||
LicenseGenerator licenseGenerator = (LicenseGenerator) command;
|
|
||||||
assertThat(licenseGenerator.publicKeyFilePath, equalTo(pubKeyPath));
|
|
||||||
assertThat(licenseGenerator.privateKeyFilePath, equalTo(priKeyPath));
|
|
||||||
assertThat(licenseGenerator.licenseSpecs.size(), equalTo(1));
|
|
||||||
License outputLicenseSpec = licenseGenerator.licenseSpecs.iterator().next();
|
|
||||||
|
|
||||||
TestUtils.assertLicenseSpec(inputLicenseSpec, outputLicenseSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParsingMultipleLicense() throws Exception {
|
|
||||||
int n = randomIntBetween(2, 5);
|
|
||||||
Map<String, TestUtils.LicenseSpec> inputLicenseSpecs = new HashMap<>();
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
TestUtils.LicenseSpec licenseSpec = TestUtils.generateRandomLicenseSpec();
|
|
||||||
inputLicenseSpecs.put(licenseSpec.feature, licenseSpec);
|
|
||||||
}
|
|
||||||
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
|
|
||||||
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
|
|
||||||
new String[] { "--license", TestUtils.generateLicenseSpecString(new ArrayList<>(inputLicenseSpecs.values())),
|
|
||||||
"--publicKeyPath", pubKeyPath.toString(),
|
|
||||||
"--privateKeyPath", priKeyPath.toString() });
|
|
||||||
|
|
||||||
assertThat(command, instanceOf(LicenseGenerator.class));
|
|
||||||
LicenseGenerator licenseGenerator = (LicenseGenerator) command;
|
|
||||||
assertThat(licenseGenerator.publicKeyFilePath, equalTo(pubKeyPath));
|
|
||||||
assertThat(licenseGenerator.privateKeyFilePath, equalTo(priKeyPath));
|
|
||||||
assertThat(licenseGenerator.licenseSpecs.size(), equalTo(inputLicenseSpecs.size()));
|
|
||||||
|
|
||||||
for (License outputLicenseSpec : licenseGenerator.licenseSpecs) {
|
|
||||||
TestUtils.LicenseSpec inputLicenseSpec = inputLicenseSpecs.get(outputLicenseSpec.feature());
|
|
||||||
assertThat(inputLicenseSpec, notNullValue());
|
|
||||||
TestUtils.assertLicenseSpec(inputLicenseSpec, outputLicenseSpec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTool() throws Exception {
|
|
||||||
int n = randomIntBetween(1, 5);
|
|
||||||
Map<String, TestUtils.LicenseSpec> inputLicenseSpecs = new HashMap<>();
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
TestUtils.LicenseSpec licenseSpec = TestUtils.generateRandomLicenseSpec();
|
|
||||||
inputLicenseSpecs.put(licenseSpec.feature, licenseSpec);
|
|
||||||
}
|
|
||||||
List<License> licenseSpecs = Licenses.fromSource(TestUtils.generateLicenseSpecString(new ArrayList<>(inputLicenseSpecs.values())).getBytes(StandardCharsets.UTF_8), false);
|
|
||||||
|
|
||||||
String output = runLicenseGenerationTool(pubKeyPath, priKeyPath, new HashSet<>(licenseSpecs), ExitStatus.OK);
|
|
||||||
List<License> outputLicenses = Licenses.fromSource(output.getBytes(StandardCharsets.UTF_8), true);
|
|
||||||
assertThat(outputLicenses.size(), equalTo(inputLicenseSpecs.size()));
|
|
||||||
|
|
||||||
for (License outputLicense : outputLicenses) {
|
|
||||||
TestUtils.LicenseSpec inputLicenseSpec = inputLicenseSpecs.get(outputLicense.feature());
|
|
||||||
assertThat(inputLicenseSpec, notNullValue());
|
|
||||||
TestUtils.assertLicenseSpec(inputLicenseSpec, outputLicense);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String runLicenseGenerationTool(Path pubKeyPath, Path priKeyPath, Set<License> licenseSpecs, ExitStatus expectedExitStatus) throws Exception {
|
|
||||||
CaptureOutputTerminal outputTerminal = new CaptureOutputTerminal();
|
|
||||||
Settings settings = Settings.builder().put("path.home", createTempDir("LicenseGenerationToolTests")).build();
|
|
||||||
LicenseGenerator licenseGenerator = new LicenseGenerator(outputTerminal, pubKeyPath, priKeyPath, licenseSpecs);
|
|
||||||
assertThat(execute(licenseGenerator, settings), equalTo(expectedExitStatus));
|
|
||||||
assertThat(outputTerminal.getTerminalOutput().size(), equalTo(1));
|
|
||||||
return outputTerminal.getTerminalOutput().get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private ExitStatus execute(Command cmd, Settings settings) throws Exception {
|
|
||||||
Environment env = new Environment(settings);
|
|
||||||
return cmd.execute(settings, env);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,203 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.licensor.tools;
|
|
||||||
|
|
||||||
import org.apache.commons.cli.MissingOptionException;
|
|
||||||
import org.elasticsearch.common.cli.CliToolTestCase;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.env.Environment;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.core.Licenses;
|
|
||||||
import org.elasticsearch.license.licensor.AbstractLicensingTestBase;
|
|
||||||
import org.elasticsearch.license.licensor.TestUtils;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static org.elasticsearch.common.cli.CliTool.Command;
|
|
||||||
import static org.elasticsearch.common.cli.CliTool.ExitStatus;
|
|
||||||
import static org.elasticsearch.license.licensor.tools.LicenseVerificationTool.LicenseVerifier;
|
|
||||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
|
||||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
import static org.hamcrest.core.IsEqual.equalTo;
|
|
||||||
|
|
||||||
public class LicenseVerificationToolTests extends CliToolTestCase {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParsingMissingLicense() throws Exception {
|
|
||||||
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
|
|
||||||
Path path = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
|
|
||||||
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME, args(" --publicKeyPath " + path));
|
|
||||||
|
|
||||||
assertThat(command, instanceOf(Command.Exit.class));
|
|
||||||
Command.Exit exitCommand = (Command.Exit) command;
|
|
||||||
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParsingMissingPublicKeyPath() throws Exception {
|
|
||||||
Path pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
|
|
||||||
Path priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
|
|
||||||
License inputLicense = AbstractLicensingTestBase.generateSignedLicense("feature__1",
|
|
||||||
TimeValue.timeValueHours(1), pubKeyPath.toString(), priKeyPath.toString());
|
|
||||||
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
|
|
||||||
try {
|
|
||||||
licenseVerificationTool.parse(LicenseVerificationTool.NAME,
|
|
||||||
args("--license " + TestUtils.dumpLicense(inputLicense)));
|
|
||||||
} catch (MissingOptionException e) {
|
|
||||||
assertThat(e.getMessage(), containsString("pub"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParsingNonExistentPublicKeyPath() throws Exception {
|
|
||||||
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
|
|
||||||
Path path = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
|
|
||||||
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME, args(" --publicKeyPath "
|
|
||||||
+ path.toString().concat(".invalid")));
|
|
||||||
|
|
||||||
assertThat(command, instanceOf(Command.Exit.class));
|
|
||||||
Command.Exit exitCommand = (Command.Exit) command;
|
|
||||||
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParsingSimple() throws Exception {
|
|
||||||
Path pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
|
|
||||||
Path priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
|
|
||||||
License inputLicense = AbstractLicensingTestBase.generateSignedLicense("feature__1",
|
|
||||||
TimeValue.timeValueHours(1), pubKeyPath.toString(), priKeyPath.toString());
|
|
||||||
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
|
|
||||||
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME,
|
|
||||||
args("--license " + TestUtils.dumpLicense(inputLicense)
|
|
||||||
+ " --publicKeyPath " + getDataPath(TestUtils.PUBLIC_KEY_RESOURCE)));
|
|
||||||
assertThat(command, instanceOf(LicenseVerifier.class));
|
|
||||||
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
|
|
||||||
assertThat(licenseVerifier.licenses.size(), equalTo(1));
|
|
||||||
License outputLicense = licenseVerifier.licenses.iterator().next();
|
|
||||||
TestUtils.isSame(inputLicense, outputLicense);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParsingLicenseFile() throws Exception {
|
|
||||||
Path pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
|
|
||||||
Path priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
|
|
||||||
License inputLicense = AbstractLicensingTestBase.generateSignedLicense("feature__1",
|
|
||||||
TimeValue.timeValueHours(1), pubKeyPath.toString(), priKeyPath.toString());
|
|
||||||
|
|
||||||
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
|
|
||||||
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME,
|
|
||||||
new String[] { "--licenseFile", dumpLicenseAsFile(inputLicense),
|
|
||||||
"--publicKeyPath", getDataPath(TestUtils.PUBLIC_KEY_RESOURCE).toString() });
|
|
||||||
assertThat(command, instanceOf(LicenseVerifier.class));
|
|
||||||
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
|
|
||||||
assertThat(licenseVerifier.licenses.size(), equalTo(1));
|
|
||||||
License outputLicense = licenseVerifier.licenses.iterator().next();
|
|
||||||
TestUtils.isSame(inputLicense, outputLicense);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParsingMultipleLicense() throws Exception {
|
|
||||||
Path pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
|
|
||||||
Path priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
|
|
||||||
|
|
||||||
int n = randomIntBetween(2, 5);
|
|
||||||
Map<String, License> inputLicenses = new HashMap<>();
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
License license = AbstractLicensingTestBase.generateSignedLicense("feature__" + i,
|
|
||||||
TimeValue.timeValueHours(1), pubKeyPath.toString(), priKeyPath.toString());
|
|
||||||
inputLicenses.put(license.feature(), license);
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder argsBuilder = new StringBuilder();
|
|
||||||
for (License inputLicense : inputLicenses.values()) {
|
|
||||||
argsBuilder.append(" --license ")
|
|
||||||
.append(TestUtils.dumpLicense(inputLicense));
|
|
||||||
}
|
|
||||||
argsBuilder.append(" --publicKeyPath ").append(getDataPath(TestUtils.PUBLIC_KEY_RESOURCE).toString());
|
|
||||||
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
|
|
||||||
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME, args(argsBuilder.toString()));
|
|
||||||
|
|
||||||
assertThat(command, instanceOf(LicenseVerifier.class));
|
|
||||||
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
|
|
||||||
assertThat(licenseVerifier.licenses.size(), equalTo(inputLicenses.size()));
|
|
||||||
|
|
||||||
for (License outputLicense : licenseVerifier.licenses) {
|
|
||||||
License inputLicense = inputLicenses.get(outputLicense.feature());
|
|
||||||
assertThat(inputLicense, notNullValue());
|
|
||||||
TestUtils.isSame(inputLicense, outputLicense);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testToolSimple() throws Exception {
|
|
||||||
Path pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
|
|
||||||
Path priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
|
|
||||||
|
|
||||||
int n = randomIntBetween(2, 5);
|
|
||||||
Map<String, License> inputLicenses = new HashMap<>();
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
License license = AbstractLicensingTestBase.generateSignedLicense("feature__" + i,
|
|
||||||
TimeValue.timeValueHours(1), pubKeyPath.toString(), priKeyPath.toString());
|
|
||||||
inputLicenses.put(license.feature(), license);
|
|
||||||
}
|
|
||||||
|
|
||||||
String output = runLicenseVerificationTool(new HashSet<>(inputLicenses.values()), getDataPath(TestUtils.PUBLIC_KEY_RESOURCE), ExitStatus.OK);
|
|
||||||
List<License> outputLicenses = Licenses.fromSource(output.getBytes(StandardCharsets.UTF_8), true);
|
|
||||||
assertThat(outputLicenses.size(), equalTo(inputLicenses.size()));
|
|
||||||
|
|
||||||
for (License outputLicense : outputLicenses) {
|
|
||||||
License inputLicense = inputLicenses.get(outputLicense.feature());
|
|
||||||
assertThat(inputLicense, notNullValue());
|
|
||||||
TestUtils.isSame(inputLicense, outputLicense);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testToolInvalidLicense() throws Exception {
|
|
||||||
Path pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
|
|
||||||
Path priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
|
|
||||||
License signedLicense = AbstractLicensingTestBase.generateSignedLicense("feature__1"
|
|
||||||
, TimeValue.timeValueHours(1), pubKeyPath.toString(), priKeyPath.toString());
|
|
||||||
|
|
||||||
License tamperedLicense = License.builder()
|
|
||||||
.fromLicenseSpec(signedLicense, signedLicense.signature())
|
|
||||||
.expiryDate(signedLicense.expiryDate() + randomIntBetween(1, 1000)).build();
|
|
||||||
|
|
||||||
runLicenseVerificationTool(Collections.singleton(tamperedLicense), getDataPath(TestUtils.PUBLIC_KEY_RESOURCE), ExitStatus.DATA_ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String dumpLicenseAsFile(License license) throws Exception {
|
|
||||||
Path tempFile = createTempFile();
|
|
||||||
Files.write(tempFile, TestUtils.dumpLicense(license).getBytes(StandardCharsets.UTF_8));
|
|
||||||
return tempFile.toAbsolutePath().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String runLicenseVerificationTool(Set<License> licenses, Path publicKeyPath, ExitStatus expectedExitStatus) throws Exception {
|
|
||||||
CaptureOutputTerminal outputTerminal = new CaptureOutputTerminal();
|
|
||||||
Settings settings = Settings.builder().put("path.home", createTempDir("LicenseVerificationToolTests")).build();
|
|
||||||
LicenseVerifier licenseVerifier = new LicenseVerifier(outputTerminal, licenses, publicKeyPath);
|
|
||||||
assertThat(execute(licenseVerifier, settings), equalTo(expectedExitStatus));
|
|
||||||
if (expectedExitStatus == ExitStatus.OK) {
|
|
||||||
assertThat(outputTerminal.getTerminalOutput().size(), equalTo(1));
|
|
||||||
|
|
||||||
return outputTerminal.getTerminalOutput().get(0);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExitStatus execute(Command cmd, Settings settings) throws Exception {
|
|
||||||
Environment env = new Environment(settings);
|
|
||||||
return cmd.execute(settings, env);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
es.logger.level=INFO
|
|
||||||
log4j.rootLogger=${es.logger.level}, out
|
|
||||||
|
|
||||||
log4j.logger.org.apache.http=INFO, out
|
|
||||||
log4j.additivity.org.apache.http=false
|
|
||||||
|
|
||||||
log4j.logger.org.elasticsearch.license=TRACE
|
|
||||||
|
|
||||||
log4j.appender.out=org.apache.log4j.ConsoleAppender
|
|
||||||
log4j.appender.out.layout=org.apache.log4j.PatternLayout
|
|
||||||
log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n
|
|
Binary file not shown.
|
@ -1,3 +0,0 @@
|
||||||
ýŽÇqÝnęÄĚgŠśwM}Ťą‡UiKŠ•0âbÖ2Řşqö]â쇴ŻÖĎĂcĚ+IŇđÔ &IJ†fÉ~ßlj <09>ş]d™}o§OčľId®Č
|
|
||||||
5A(ěµ´^ŘöW©DŤŞJµë}ů-Oîë?u N5ľŰvpŰ{’Ľ˛Áôśát–ť¤7ůřĂę#˛Vqöó»ktwm’Ś]ĎLőŁz"| Q‹lźňQđsâ>ů<}Ź[Á2ÖÓŕZÖ|5‹ŻŤĘĘ7%ŘęD
|
|
||||||
Yĺ‘xn:ĽlúLČćHň˘«Ë2<C38B>źHvEEWÇ\¦H:“6Žh9 [!š…Űć©Š¤+;Ö.w7Cě©_|ŢÓŞĎÁ*ń§D`<60>Ú?‚ůxU/3>xUÓ“+ č
|
|
|
@ -1 +0,0 @@
|
||||||
/eclipse-build/
|
|
|
@ -1,43 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>elasticsearch-license</artifactId>
|
|
||||||
<groupId>org.elasticsearch</groupId>
|
|
||||||
<version>2.0.0.beta1-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>elasticsearch-license-plugin-api</artifactId>
|
|
||||||
<properties>
|
|
||||||
<license.basedir combine.self="override">${project.parent.basedir}</license.basedir>
|
|
||||||
<!-- we aren't really a plugin... -->
|
|
||||||
<skip.integ.tests>true</skip.integ.tests>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.elasticsearch</groupId>
|
|
||||||
<artifactId>elasticsearch-license-licensor</artifactId>
|
|
||||||
<version>2.0.0.beta1-SNAPSHOT</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.elasticsearch</groupId>
|
|
||||||
<artifactId>elasticsearch-license-core</artifactId>
|
|
||||||
<version>2.0.0.beta1-SNAPSHOT</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<resources>
|
|
||||||
<resource>
|
|
||||||
<directory>src/main/resources</directory>
|
|
||||||
<filtering>true</filtering>
|
|
||||||
</resource>
|
|
||||||
</resources>
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,191 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import org.elasticsearch.Version;
|
|
||||||
import org.elasticsearch.common.Nullable;
|
|
||||||
import org.elasticsearch.common.Strings;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public class LicenseVersion implements Serializable {
|
|
||||||
|
|
||||||
// The logic for ID is: XXYYZZAA, where XX is major version, YY is minor version, ZZ is revision, and AA is Beta/RC indicator
|
|
||||||
// AA values below 50 are beta builds, and below 99 are RC builds, with 99 indicating a release
|
|
||||||
// the (internal) format of the id is there so we can easily do after/before checks on the id
|
|
||||||
|
|
||||||
public static final int V_1_0_0_ID = /*00*/1000099;
|
|
||||||
public static final int V_2_0_0_beta1_ID = /*00*/2000001;
|
|
||||||
public static final LicenseVersion V_1_0_0 = new LicenseVersion(V_1_0_0_ID, false, License.VERSION_START, Version.V_1_4_0_Beta1);
|
|
||||||
public static final LicenseVersion V_2_0_0_beta1 = new LicenseVersion(V_2_0_0_beta1_ID, true, License.VERSION_START, Version.V_2_0_0_beta1);
|
|
||||||
|
|
||||||
public static final LicenseVersion CURRENT = V_2_0_0_beta1;
|
|
||||||
|
|
||||||
public static LicenseVersion readVersion(StreamInput in) throws IOException {
|
|
||||||
return fromId(in.readVInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LicenseVersion fromId(int id) {
|
|
||||||
switch (id) {
|
|
||||||
case V_1_0_0_ID:
|
|
||||||
return V_1_0_0;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return new LicenseVersion(id, null, License.VERSION_CURRENT, Version.CURRENT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void writeVersion(LicenseVersion version, StreamOutput out) throws IOException {
|
|
||||||
out.writeVInt(version.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the smallest version between the 2.
|
|
||||||
*/
|
|
||||||
public static LicenseVersion smallest(LicenseVersion version1, LicenseVersion version2) {
|
|
||||||
return version1.id < version2.id ? version1 : version2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the version given its string representation, current version if the argument is null or empty
|
|
||||||
*/
|
|
||||||
public static LicenseVersion fromString(String version) {
|
|
||||||
if (!Strings.hasLength(version)) {
|
|
||||||
return LicenseVersion.CURRENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] parts = version.split("\\.|\\-");
|
|
||||||
if (parts.length < 3 || parts.length > 4) {
|
|
||||||
throw new IllegalArgumentException("the version needs to contain major, minor and revision, and optionally the build");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
//we reverse the version id calculation based on some assumption as we can't reliably reverse the modulo
|
|
||||||
int major = Integer.parseInt(parts[0]) * 1000000;
|
|
||||||
int minor = Integer.parseInt(parts[1]) * 10000;
|
|
||||||
int revision = Integer.parseInt(parts[2]) * 100;
|
|
||||||
|
|
||||||
int build = 99;
|
|
||||||
if (parts.length == 4) {
|
|
||||||
String buildStr = parts[3];
|
|
||||||
if (buildStr.startsWith("beta")) {
|
|
||||||
build = Integer.parseInt(buildStr.substring(4));
|
|
||||||
}
|
|
||||||
if (buildStr.startsWith("rc")) {
|
|
||||||
build = Integer.parseInt(buildStr.substring(2)) + 50;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fromId(major + minor + revision + build);
|
|
||||||
|
|
||||||
} catch(NumberFormatException e) {
|
|
||||||
throw new IllegalArgumentException("unable to parse version " + version, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final int id;
|
|
||||||
public final byte major;
|
|
||||||
public final byte minor;
|
|
||||||
public final byte revision;
|
|
||||||
public final byte build;
|
|
||||||
public final Boolean snapshot;
|
|
||||||
public final int minSignatureVersion;
|
|
||||||
public final Version minEsCompatibilityVersion;
|
|
||||||
|
|
||||||
LicenseVersion(int id, @Nullable Boolean snapshot, int minSignatureVersion, Version minEsCompatibilityVersion) {
|
|
||||||
this.id = id;
|
|
||||||
this.major = (byte) ((id / 1000000) % 100);
|
|
||||||
this.minor = (byte) ((id / 10000) % 100);
|
|
||||||
this.revision = (byte) ((id / 100) % 100);
|
|
||||||
this.build = (byte) (id % 100);
|
|
||||||
this.snapshot = snapshot;
|
|
||||||
this.minSignatureVersion = minSignatureVersion;
|
|
||||||
this.minEsCompatibilityVersion = minEsCompatibilityVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean snapshot() {
|
|
||||||
return snapshot != null && snapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean after(LicenseVersion version) {
|
|
||||||
return version.id < id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean before(LicenseVersion version) {
|
|
||||||
return version.id > id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the minimum compatible version based on the current
|
|
||||||
* version. Ie a node needs to have at least the return version in order
|
|
||||||
* to communicate with a node running the current version. The returned version
|
|
||||||
* is in most of the cases the smallest major version release unless the current version
|
|
||||||
* is a beta or RC release then the version itself is returned.
|
|
||||||
*/
|
|
||||||
public LicenseVersion minimumCompatibilityVersion() {
|
|
||||||
return LicenseVersion.smallest(this, fromId(major * 1000000 + 99));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The minimum elasticsearch version this license version is compatible with.
|
|
||||||
*/
|
|
||||||
public Version minimumEsCompatiblityVersion() {
|
|
||||||
return minEsCompatibilityVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The minimum license signature version this license plugin is compatible with.
|
|
||||||
*/
|
|
||||||
public int minimumSignatureVersion() {
|
|
||||||
return minSignatureVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Just the version number (without -SNAPSHOT if snapshot).
|
|
||||||
*/
|
|
||||||
public String number() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(major).append('.').append(minor).append('.').append(revision);
|
|
||||||
if (build < 50) {
|
|
||||||
sb.append("-beta").append(build);
|
|
||||||
} else if (build < 99) {
|
|
||||||
sb.append("-rc").append(build - 50);
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(number());
|
|
||||||
if (snapshot()) {
|
|
||||||
sb.append("-SNAPSHOT");
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
|
|
||||||
LicenseVersion that = (LicenseVersion) o;
|
|
||||||
|
|
||||||
if (id != that.id) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.core;
|
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchSecurityException;
|
|
||||||
import org.elasticsearch.rest.RestStatus;
|
|
||||||
|
|
||||||
public class LicenseUtils {
|
|
||||||
|
|
||||||
public final static String EXPIRED_FEATURE_HEADER = "es.license.expired.feature";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception to be thrown when a feature action requires a valid license, but license
|
|
||||||
* has expired
|
|
||||||
*
|
|
||||||
* <code>feature</code> accessible through {@link #EXPIRED_FEATURE_HEADER} in the
|
|
||||||
* exception's rest header
|
|
||||||
*/
|
|
||||||
public static ElasticsearchSecurityException newExpirationException(String feature) {
|
|
||||||
ElasticsearchSecurityException e = new ElasticsearchSecurityException("license expired for feature [{}]", RestStatus.UNAUTHORIZED, feature);
|
|
||||||
e.addHeader(EXPIRED_FEATURE_HEADER, feature);
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,163 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.core;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
|
|
||||||
public interface LicensesClientService {
|
|
||||||
|
|
||||||
public interface Listener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to enable a feature
|
|
||||||
*/
|
|
||||||
public void onEnabled(License license);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to disable a feature
|
|
||||||
*/
|
|
||||||
public void onDisabled(License license);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a feature for licensing
|
|
||||||
*
|
|
||||||
* @param feature - name of the feature to register (must be in sync with license Generator feature name)
|
|
||||||
* @param trialLicenseOptions - Trial license specification used to generate a one-time trial license for the feature;
|
|
||||||
* use <code>null</code> if no trial license should be generated for the feature
|
|
||||||
* @param expirationCallbacks - A collection of Pre and/or Post expiration callbacks
|
|
||||||
* @param listener - used to notify on feature enable/disable
|
|
||||||
*/
|
|
||||||
void register(String feature, TrialLicenseOptions trialLicenseOptions, Collection<ExpirationCallback> expirationCallbacks, Listener listener);
|
|
||||||
|
|
||||||
public static class TrialLicenseOptions {
|
|
||||||
final TimeValue duration;
|
|
||||||
final int maxNodes;
|
|
||||||
|
|
||||||
public TrialLicenseOptions(TimeValue duration, int maxNodes) {
|
|
||||||
this.duration = duration;
|
|
||||||
this.maxNodes = maxNodes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static interface LicenseCallback {
|
|
||||||
void on(License license, ExpirationStatus status);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static abstract class ExpirationCallback implements LicenseCallback {
|
|
||||||
|
|
||||||
public enum Orientation { PRE, POST }
|
|
||||||
|
|
||||||
public static abstract class Pre extends ExpirationCallback {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback schedule prior to license expiry
|
|
||||||
*
|
|
||||||
* @param min latest relative time to execute before license expiry
|
|
||||||
* @param max earliest relative time to execute before license expiry
|
|
||||||
* @param frequency interval between execution
|
|
||||||
*/
|
|
||||||
public Pre(TimeValue min, TimeValue max, TimeValue frequency) {
|
|
||||||
super(Orientation.PRE, min, max, frequency);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(long expirationDate, long now) {
|
|
||||||
long expiryDuration = expirationDate - now;
|
|
||||||
if (expiryDuration > 0l) {
|
|
||||||
if (expiryDuration <= max().getMillis()) {
|
|
||||||
return expiryDuration >= min().getMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static abstract class Post extends ExpirationCallback {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback schedule after license expiry
|
|
||||||
*
|
|
||||||
* @param min earliest relative time to execute after license expiry
|
|
||||||
* @param max latest relative time to execute after license expiry
|
|
||||||
* @param frequency interval between execution
|
|
||||||
*/
|
|
||||||
public Post(TimeValue min, TimeValue max, TimeValue frequency) {
|
|
||||||
super(Orientation.POST, min, max, frequency);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(long expirationDate, long now) {
|
|
||||||
long postExpiryDuration = now - expirationDate;
|
|
||||||
if (postExpiryDuration > 0l) {
|
|
||||||
if (postExpiryDuration <= max().getMillis()) {
|
|
||||||
return postExpiryDuration >= min().getMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Orientation orientation;
|
|
||||||
private final TimeValue min;
|
|
||||||
private final TimeValue max;
|
|
||||||
private final TimeValue frequency;
|
|
||||||
|
|
||||||
private ExpirationCallback(Orientation orientation, TimeValue min, TimeValue max, TimeValue frequency) {
|
|
||||||
this.orientation = orientation;
|
|
||||||
this.min = (min == null) ? TimeValue.timeValueMillis(0) : min;
|
|
||||||
this.max = (max == null) ? TimeValue.timeValueMillis(Long.MAX_VALUE) : max;
|
|
||||||
this.frequency = frequency;
|
|
||||||
if (frequency == null) {
|
|
||||||
throw new IllegalArgumentException("frequency can not be null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Orientation orientation() {
|
|
||||||
return orientation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TimeValue min() {
|
|
||||||
return min;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TimeValue max() {
|
|
||||||
return max;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TimeValue frequency() {
|
|
||||||
return frequency;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract boolean matches(long expirationDate, long now);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ExpirationStatus {
|
|
||||||
private final boolean expired;
|
|
||||||
private final TimeValue time;
|
|
||||||
|
|
||||||
ExpirationStatus(boolean expired, TimeValue time) {
|
|
||||||
this.expired = expired;
|
|
||||||
this.time = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean expired() {
|
|
||||||
return expired;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TimeValue time() {
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
import static org.hamcrest.Matchers.is;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class LicenseVersionTests extends ElasticsearchTestCase {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStrings() throws Exception {
|
|
||||||
boolean beta = randomBoolean();
|
|
||||||
int buildNumber = beta ? randomIntBetween(0, 49) : randomIntBetween(0, 48);
|
|
||||||
int major = randomIntBetween(0, 20);
|
|
||||||
int minor = randomIntBetween(0, 20);
|
|
||||||
int revision = randomIntBetween(0, 20);
|
|
||||||
|
|
||||||
String build = buildNumber == 0 ? "" :
|
|
||||||
beta ? "-beta" + buildNumber : "-rc" + buildNumber;
|
|
||||||
|
|
||||||
|
|
||||||
String versionName = new StringBuilder()
|
|
||||||
.append(major)
|
|
||||||
.append(".").append(minor)
|
|
||||||
.append(".").append(revision)
|
|
||||||
.append(build).toString();
|
|
||||||
LicenseVersion version = LicenseVersion.fromString(versionName);
|
|
||||||
|
|
||||||
logger.info("version: {}", versionName);
|
|
||||||
|
|
||||||
assertThat(version.major, is((byte) major));
|
|
||||||
assertThat(version.minor, is((byte) minor));
|
|
||||||
assertThat(version.revision, is((byte) revision));
|
|
||||||
if (buildNumber == 0) {
|
|
||||||
assertThat(version.build, is((byte) 99));
|
|
||||||
} else if (beta) {
|
|
||||||
assertThat(version.build, is((byte) buildNumber));
|
|
||||||
} else {
|
|
||||||
assertThat(version.build, is((byte) (buildNumber + 50)));
|
|
||||||
}
|
|
||||||
|
|
||||||
assertThat(version.number(), equalTo(versionName));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
/eclipse-build/
|
|
125
plugin/pom.xml
125
plugin/pom.xml
|
@ -1,125 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<parent>
|
|
||||||
<artifactId>elasticsearch-license</artifactId>
|
|
||||||
<groupId>org.elasticsearch</groupId>
|
|
||||||
<version>2.0.0.beta1-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>elasticsearch-license-plugin</artifactId>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<!-- TODO: do we want to always enforce non-default keys
|
|
||||||
-->
|
|
||||||
<keys.path>${basedir}/src/test/resources</keys.path>
|
|
||||||
<license.basedir combine.self="override">${project.parent.basedir}</license.basedir>
|
|
||||||
<tests.rest.suite>license</tests.rest.suite>
|
|
||||||
<tests.rest.load_packaged>false</tests.rest.load_packaged>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.elasticsearch</groupId>
|
|
||||||
<artifactId>elasticsearch-license-licensor</artifactId>
|
|
||||||
<version>2.0.0.beta1-SNAPSHOT</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.elasticsearch</groupId>
|
|
||||||
<artifactId>elasticsearch-license-core</artifactId>
|
|
||||||
<version>2.0.0.beta1-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.elasticsearch</groupId>
|
|
||||||
<artifactId>elasticsearch-license-plugin-api</artifactId>
|
|
||||||
<version>2.0.0.beta1-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<resources>
|
|
||||||
<resource>
|
|
||||||
<directory>${keys.path}</directory>
|
|
||||||
<filtering>false</filtering>
|
|
||||||
<includes>
|
|
||||||
<include>public.key</include>
|
|
||||||
</includes>
|
|
||||||
</resource>
|
|
||||||
<resource>
|
|
||||||
<directory>src/main/resources</directory>
|
|
||||||
<filtering>true</filtering>
|
|
||||||
</resource>
|
|
||||||
</resources>
|
|
||||||
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-enforcer-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<!-- TODO: do we want to always enforce non-default keys -->
|
|
||||||
<execution>
|
|
||||||
<id>enforce-property</id>
|
|
||||||
<goals>
|
|
||||||
<goal>enforce</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<rules>
|
|
||||||
<requireProperty>
|
|
||||||
<property>keys.path</property>
|
|
||||||
<message>"You must set a keys.path property!"</message>
|
|
||||||
</requireProperty>
|
|
||||||
<requireFilesExist>
|
|
||||||
<files>
|
|
||||||
<file>${keys.path}/public.key</file>
|
|
||||||
</files>
|
|
||||||
<message>"public.key file does not exist in ${keys.path} directory!"</message>
|
|
||||||
</requireFilesExist>
|
|
||||||
</rules>
|
|
||||||
<fail>true</fail>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>for-plugin</id>
|
|
||||||
<phase>prepare-package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>jar</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>com.carrotsearch.randomizedtesting</groupId>
|
|
||||||
<artifactId>junit4-maven-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>tests</id>
|
|
||||||
<phase>test</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>junit4</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<heartbeat combine.self="override">40</heartbeat>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
|
|
||||||
</build>
|
|
||||||
</project>
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"licenses": {
|
|
||||||
"documentation": "https://www.elastic.co/guide/en/shield/current/license-management.html",
|
|
||||||
"methods": ["GET"],
|
|
||||||
"url": {
|
|
||||||
"path": "/_licenses",
|
|
||||||
"paths": ["/_licenses"],
|
|
||||||
"parts" : {
|
|
||||||
},
|
|
||||||
"params": {
|
|
||||||
"local": {
|
|
||||||
"type" : "boolean",
|
|
||||||
"description" : "Return local information, do not retrieve the state from master node (default: false)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"body": null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
"licenses endpoint exists":
|
|
||||||
- do: {licenses: {}}
|
|
||||||
- match: {licenses: []}
|
|
|
@ -1,22 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
<assembly>
|
|
||||||
<id>plugin</id>
|
|
||||||
<formats>
|
|
||||||
<format>zip</format>
|
|
||||||
</formats>
|
|
||||||
<includeBaseDirectory>false</includeBaseDirectory>
|
|
||||||
<dependencySets>
|
|
||||||
<dependencySet>
|
|
||||||
<outputDirectory>/</outputDirectory>
|
|
||||||
<useProjectArtifact>true</useProjectArtifact>
|
|
||||||
<includes>
|
|
||||||
<include>org.elasticsearch:elasticsearch-license-plugin</include>
|
|
||||||
<include>org.elasticsearch:elasticsearch-license-plugin-api</include>
|
|
||||||
<include>org.elasticsearch:elasticsearch-license-core</include>
|
|
||||||
</includes>
|
|
||||||
<excludes>
|
|
||||||
<exclude>org.elasticsearch:elasticsearch</exclude>
|
|
||||||
</excludes>
|
|
||||||
</dependencySet>
|
|
||||||
</dependencySets>
|
|
||||||
</assembly>
|
|
|
@ -1,21 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.inject.AbstractModule;
|
|
||||||
import org.elasticsearch.common.inject.Scopes;
|
|
||||||
import org.elasticsearch.license.core.LicenseVerifier;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesClientService;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesService;
|
|
||||||
|
|
||||||
public class LicenseModule extends AbstractModule {
|
|
||||||
@Override
|
|
||||||
protected void configure() {
|
|
||||||
bind(LicenseVerifier.class).in(Scopes.SINGLETON);
|
|
||||||
bind(LicensesService.class).in(Scopes.SINGLETON);
|
|
||||||
bind(LicensesClientService.class).to(LicensesService.class).in(Scopes.SINGLETON);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import org.elasticsearch.action.ActionModule;
|
|
||||||
import org.elasticsearch.client.Client;
|
|
||||||
import org.elasticsearch.cluster.metadata.MetaData;
|
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
|
||||||
import org.elasticsearch.common.component.LifecycleComponent;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.inject.Module;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.action.delete.TransportDeleteLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.action.get.GetLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.action.get.TransportGetLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.TransportPutLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesMetaData;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesService;
|
|
||||||
import org.elasticsearch.license.plugin.rest.RestDeleteLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.rest.RestGetLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.rest.RestPutLicenseAction;
|
|
||||||
import org.elasticsearch.plugins.AbstractPlugin;
|
|
||||||
import org.elasticsearch.rest.RestModule;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
public class LicensePlugin extends AbstractPlugin {
|
|
||||||
|
|
||||||
public static final String NAME = "license";
|
|
||||||
private final boolean isEnabled;
|
|
||||||
|
|
||||||
static {
|
|
||||||
MetaData.registerPrototype(LicensesMetaData.TYPE, LicensesMetaData.PROTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public LicensePlugin(Settings settings) {
|
|
||||||
if (DiscoveryNode.clientNode(settings)) {
|
|
||||||
// Enable plugin only on node clients
|
|
||||||
this.isEnabled = "node".equals(settings.get(Client.CLIENT_TYPE_SETTING));
|
|
||||||
} else {
|
|
||||||
this.isEnabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String name() {
|
|
||||||
return NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String description() {
|
|
||||||
return "Internal Elasticsearch Licensing Plugin";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onModule(RestModule module) {
|
|
||||||
// Register REST endpoint
|
|
||||||
module.addRestAction(RestPutLicenseAction.class);
|
|
||||||
module.addRestAction(RestGetLicenseAction.class);
|
|
||||||
module.addRestAction(RestDeleteLicenseAction.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onModule(ActionModule module) {
|
|
||||||
module.registerAction(PutLicenseAction.INSTANCE, TransportPutLicenseAction.class);
|
|
||||||
module.registerAction(GetLicenseAction.INSTANCE, TransportGetLicenseAction.class);
|
|
||||||
module.registerAction(DeleteLicenseAction.INSTANCE, TransportDeleteLicenseAction.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Class<? extends LifecycleComponent>> services() {
|
|
||||||
Collection<Class<? extends LifecycleComponent>> services = Lists.newArrayList();
|
|
||||||
if (isEnabled) {
|
|
||||||
services.add(LicensesService.class);
|
|
||||||
}
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Class<? extends Module>> modules() {
|
|
||||||
if (isEnabled) {
|
|
||||||
return ImmutableSet.<Class<? extends Module>>of(LicenseModule.class);
|
|
||||||
}
|
|
||||||
return ImmutableSet.of();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.delete;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.Action;
|
|
||||||
import org.elasticsearch.client.ElasticsearchClient;
|
|
||||||
|
|
||||||
public class DeleteLicenseAction extends Action<DeleteLicenseRequest, DeleteLicenseResponse, DeleteLicenseRequestBuilder> {
|
|
||||||
|
|
||||||
public static final DeleteLicenseAction INSTANCE = new DeleteLicenseAction();
|
|
||||||
public static final String NAME = "cluster:admin/plugin/license/delete";
|
|
||||||
|
|
||||||
private DeleteLicenseAction() {
|
|
||||||
super(NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DeleteLicenseResponse newResponse() {
|
|
||||||
return new DeleteLicenseResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DeleteLicenseRequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
|
||||||
return new DeleteLicenseRequestBuilder(client, this);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.delete;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionRequestValidationException;
|
|
||||||
import org.elasticsearch.action.support.master.AcknowledgedRequest;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
|
|
||||||
public class DeleteLicenseRequest extends AcknowledgedRequest<DeleteLicenseRequest> {
|
|
||||||
|
|
||||||
private String[] features;
|
|
||||||
|
|
||||||
public DeleteLicenseRequest() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public DeleteLicenseRequest(String... features) {
|
|
||||||
this.features = features;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void features(Set<String> features) {
|
|
||||||
this.features = features.toArray(new String[features.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> features() {
|
|
||||||
return new HashSet<>(Arrays.asList(features));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ActionRequestValidationException validate() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
features = in.readStringArray();
|
|
||||||
readTimeout(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
super.writeTo(out);
|
|
||||||
out.writeStringArray(features);
|
|
||||||
writeTimeout(out);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.delete;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.support.master.AcknowledgedRequestBuilder;
|
|
||||||
import org.elasticsearch.client.ClusterAdminClient;
|
|
||||||
import org.elasticsearch.client.ElasticsearchClient;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class DeleteLicenseRequestBuilder extends AcknowledgedRequestBuilder<DeleteLicenseRequest, DeleteLicenseResponse, DeleteLicenseRequestBuilder> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates new get licenses request builder
|
|
||||||
*
|
|
||||||
* @param client elasticsearch client
|
|
||||||
*/
|
|
||||||
public DeleteLicenseRequestBuilder(ElasticsearchClient client, DeleteLicenseAction action) {
|
|
||||||
super(client, action, new DeleteLicenseRequest());
|
|
||||||
}
|
|
||||||
|
|
||||||
public DeleteLicenseRequestBuilder setFeatures(Set<String> features) {
|
|
||||||
request.features(features);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.delete;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class DeleteLicenseResponse extends AcknowledgedResponse {
|
|
||||||
|
|
||||||
DeleteLicenseResponse() {
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteLicenseResponse(boolean acknowledged) {
|
|
||||||
super(acknowledged);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
readAcknowledged(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
super.writeTo(out);
|
|
||||||
writeAcknowledged(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.delete;
|
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.action.support.ActionFilters;
|
|
||||||
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
|
|
||||||
import org.elasticsearch.cluster.ClusterService;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockException;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockLevel;
|
|
||||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesManagerService;
|
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
|
||||||
import org.elasticsearch.transport.TransportService;
|
|
||||||
|
|
||||||
import static org.elasticsearch.license.plugin.core.LicensesService.DeleteLicenseRequestHolder;
|
|
||||||
|
|
||||||
public class TransportDeleteLicenseAction extends TransportMasterNodeAction<DeleteLicenseRequest, DeleteLicenseResponse> {
|
|
||||||
|
|
||||||
private final LicensesManagerService licensesManagerService;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public TransportDeleteLicenseAction(Settings settings, TransportService transportService, ClusterService clusterService, LicensesManagerService licensesManagerService,
|
|
||||||
ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) {
|
|
||||||
super(settings, DeleteLicenseAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver, DeleteLicenseRequest.class);
|
|
||||||
this.licensesManagerService = licensesManagerService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String executor() {
|
|
||||||
return ThreadPool.Names.MANAGEMENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected DeleteLicenseResponse newResponse() {
|
|
||||||
return new DeleteLicenseResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ClusterBlockException checkBlock(DeleteLicenseRequest request, ClusterState state) {
|
|
||||||
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void masterOperation(final DeleteLicenseRequest request, ClusterState state, final ActionListener<DeleteLicenseResponse> listener) throws ElasticsearchException {
|
|
||||||
final DeleteLicenseRequestHolder requestHolder = new DeleteLicenseRequestHolder(request, "delete licenses []");
|
|
||||||
licensesManagerService.removeLicenses(requestHolder, new ActionListener<ClusterStateUpdateResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(ClusterStateUpdateResponse clusterStateUpdateResponse) {
|
|
||||||
listener.onResponse(new DeleteLicenseResponse(clusterStateUpdateResponse.isAcknowledged()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.get;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.Action;
|
|
||||||
import org.elasticsearch.client.ElasticsearchClient;
|
|
||||||
|
|
||||||
public class GetLicenseAction extends Action<GetLicenseRequest, GetLicenseResponse, GetLicenseRequestBuilder> {
|
|
||||||
|
|
||||||
public static final GetLicenseAction INSTANCE = new GetLicenseAction();
|
|
||||||
public static final String NAME = "cluster:admin/plugin/license/get";
|
|
||||||
|
|
||||||
private GetLicenseAction() {
|
|
||||||
super(NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public GetLicenseResponse newResponse() {
|
|
||||||
return new GetLicenseResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public GetLicenseRequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
|
||||||
return new GetLicenseRequestBuilder(client, this);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.get;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionRequestValidationException;
|
|
||||||
import org.elasticsearch.action.support.master.MasterNodeReadRequest;
|
|
||||||
|
|
||||||
|
|
||||||
public class GetLicenseRequest extends MasterNodeReadRequest<GetLicenseRequest> {
|
|
||||||
|
|
||||||
public GetLicenseRequest() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ActionRequestValidationException validate() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.get;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.support.master.MasterNodeReadOperationRequestBuilder;
|
|
||||||
import org.elasticsearch.client.ElasticsearchClient;
|
|
||||||
|
|
||||||
public class GetLicenseRequestBuilder extends MasterNodeReadOperationRequestBuilder<GetLicenseRequest, GetLicenseResponse, GetLicenseRequestBuilder> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates new get licenses request builder
|
|
||||||
*
|
|
||||||
* @param client elasticsearch client
|
|
||||||
*/
|
|
||||||
public GetLicenseRequestBuilder(ElasticsearchClient client, GetLicenseAction action) {
|
|
||||||
super(client, action, new GetLicenseRequest());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.get;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionResponse;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.core.Licenses;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class GetLicenseResponse extends ActionResponse {
|
|
||||||
|
|
||||||
private List<License> licenses = new ArrayList<>();
|
|
||||||
|
|
||||||
GetLicenseResponse() {
|
|
||||||
}
|
|
||||||
|
|
||||||
GetLicenseResponse(List<License> licenses) {
|
|
||||||
this.licenses = licenses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<License> licenses() {
|
|
||||||
return licenses;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
licenses = Licenses.readFrom(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
super.writeTo(out);
|
|
||||||
Licenses.writeTo(licenses, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.get;
|
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.action.support.ActionFilters;
|
|
||||||
import org.elasticsearch.action.support.master.TransportMasterNodeReadAction;
|
|
||||||
import org.elasticsearch.cluster.ClusterService;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockException;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockLevel;
|
|
||||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesManagerService;
|
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
|
||||||
import org.elasticsearch.transport.TransportService;
|
|
||||||
|
|
||||||
public class TransportGetLicenseAction extends TransportMasterNodeReadAction<GetLicenseRequest, GetLicenseResponse> {
|
|
||||||
|
|
||||||
private final LicensesManagerService licensesManagerService;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public TransportGetLicenseAction(Settings settings, TransportService transportService, ClusterService clusterService, LicensesManagerService licensesManagerService,
|
|
||||||
ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) {
|
|
||||||
super(settings, GetLicenseAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver, GetLicenseRequest.class);
|
|
||||||
this.licensesManagerService = licensesManagerService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String executor() {
|
|
||||||
return ThreadPool.Names.MANAGEMENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected GetLicenseResponse newResponse() {
|
|
||||||
return new GetLicenseResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ClusterBlockException checkBlock(GetLicenseRequest request, ClusterState state) {
|
|
||||||
return state.blocks().indexBlockedException(ClusterBlockLevel.METADATA_READ, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void masterOperation(final GetLicenseRequest request, ClusterState state, final ActionListener<GetLicenseResponse> listener) throws ElasticsearchException {
|
|
||||||
listener.onResponse(new GetLicenseResponse(licensesManagerService.getLicenses()));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.put;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.Action;
|
|
||||||
import org.elasticsearch.client.ElasticsearchClient;
|
|
||||||
|
|
||||||
public class PutLicenseAction extends Action<PutLicenseRequest, PutLicenseResponse, PutLicenseRequestBuilder> {
|
|
||||||
|
|
||||||
public static final PutLicenseAction INSTANCE = new PutLicenseAction();
|
|
||||||
public static final String NAME = "cluster:admin/plugin/license/put";
|
|
||||||
|
|
||||||
private PutLicenseAction() {
|
|
||||||
super(NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PutLicenseResponse newResponse() {
|
|
||||||
return new PutLicenseResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PutLicenseRequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
|
||||||
return new PutLicenseRequestBuilder(client, this);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.put;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionRequestValidationException;
|
|
||||||
import org.elasticsearch.action.support.master.AcknowledgedRequest;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.core.Licenses;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
public class PutLicenseRequest extends AcknowledgedRequest<PutLicenseRequest> {
|
|
||||||
|
|
||||||
private List<License> licenses;
|
|
||||||
|
|
||||||
public PutLicenseRequest() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ActionRequestValidationException validate() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses licenses from json format to an instance of {@link org.elasticsearch.license.core.Licenses}
|
|
||||||
*
|
|
||||||
* @param licenseDefinition licenses definition
|
|
||||||
*/
|
|
||||||
public PutLicenseRequest licenses(String licenseDefinition) {
|
|
||||||
try {
|
|
||||||
return licenses(Licenses.fromSource(licenseDefinition));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalArgumentException("failed to parse licenses source", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PutLicenseRequest licenses(List<License> licenses) {
|
|
||||||
this.licenses = licenses;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<License> licenses() {
|
|
||||||
return licenses;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
licenses = Licenses.readFrom(in);
|
|
||||||
readTimeout(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
super.writeTo(out);
|
|
||||||
Licenses.writeTo(licenses, out);
|
|
||||||
writeTimeout(out);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.put;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.support.master.AcknowledgedRequestBuilder;
|
|
||||||
import org.elasticsearch.client.ElasticsearchClient;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register license request builder
|
|
||||||
*/
|
|
||||||
public class PutLicenseRequestBuilder extends AcknowledgedRequestBuilder<PutLicenseRequest, PutLicenseResponse, PutLicenseRequestBuilder> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs register license request
|
|
||||||
*
|
|
||||||
* @param client elasticsearch client
|
|
||||||
*/
|
|
||||||
public PutLicenseRequestBuilder(ElasticsearchClient client, PutLicenseAction action) {
|
|
||||||
super(client, action, new PutLicenseRequest());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the license
|
|
||||||
*
|
|
||||||
* @param licenses license
|
|
||||||
* @return this builder
|
|
||||||
*/
|
|
||||||
public PutLicenseRequestBuilder setLicense(List<License> licenses) {
|
|
||||||
request.licenses(licenses);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PutLicenseRequestBuilder setLicense(String licenseSource) {
|
|
||||||
request.licenses(licenseSource);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.put;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesStatus;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class PutLicenseResponse extends AcknowledgedResponse {
|
|
||||||
|
|
||||||
private LicensesStatus status;
|
|
||||||
|
|
||||||
PutLicenseResponse() {
|
|
||||||
}
|
|
||||||
|
|
||||||
PutLicenseResponse(boolean acknowledged, LicensesStatus status) {
|
|
||||||
super(acknowledged);
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LicensesStatus status() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
readAcknowledged(in);
|
|
||||||
status = LicensesStatus.fromId(in.readVInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
super.writeTo(out);
|
|
||||||
writeAcknowledged(out);
|
|
||||||
out.writeVInt(status.id());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.action.put;
|
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.action.support.ActionFilters;
|
|
||||||
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
|
|
||||||
import org.elasticsearch.cluster.ClusterService;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockException;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockLevel;
|
|
||||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesManagerService;
|
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
|
||||||
import org.elasticsearch.transport.TransportService;
|
|
||||||
|
|
||||||
import static org.elasticsearch.license.plugin.core.LicensesService.LicensesUpdateResponse;
|
|
||||||
import static org.elasticsearch.license.plugin.core.LicensesService.PutLicenseRequestHolder;
|
|
||||||
|
|
||||||
public class TransportPutLicenseAction extends TransportMasterNodeAction<PutLicenseRequest, PutLicenseResponse> {
|
|
||||||
|
|
||||||
private final LicensesManagerService licensesManagerService;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public TransportPutLicenseAction(Settings settings, TransportService transportService, ClusterService clusterService,
|
|
||||||
LicensesManagerService licensesManagerService, ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) {
|
|
||||||
super(settings, PutLicenseAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver, PutLicenseRequest.class);
|
|
||||||
this.licensesManagerService = licensesManagerService;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String executor() {
|
|
||||||
return ThreadPool.Names.MANAGEMENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PutLicenseResponse newResponse() {
|
|
||||||
return new PutLicenseResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ClusterBlockException checkBlock(PutLicenseRequest request, ClusterState state) {
|
|
||||||
return state.blocks().indexBlockedException(ClusterBlockLevel.METADATA_WRITE, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void masterOperation(final PutLicenseRequest request, ClusterState state, final ActionListener<PutLicenseResponse> listener) throws ElasticsearchException {
|
|
||||||
licensesManagerService.registerLicenses(new PutLicenseRequestHolder(request, "put licenses []"), new ActionListener<LicensesUpdateResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(LicensesUpdateResponse licensesUpdateResponse) {
|
|
||||||
listener.onResponse(new PutLicenseResponse(licensesUpdateResponse.isAcknowledged(), licensesUpdateResponse.status()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.core;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
|
|
||||||
import org.elasticsearch.common.inject.ImplementedBy;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.elasticsearch.license.plugin.core.LicensesService.*;
|
|
||||||
|
|
||||||
@ImplementedBy(LicensesService.class)
|
|
||||||
public interface LicensesManagerService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers new licenses in the cluster
|
|
||||||
* <p/>
|
|
||||||
* This method can be only called on the master node. It tries to create a new licenses on the master
|
|
||||||
* and if provided license(s) is VALID it is added to cluster state metadata {@link org.elasticsearch.license.plugin.core.LicensesMetaData}
|
|
||||||
*/
|
|
||||||
public void registerLicenses(final PutLicenseRequestHolder requestHolder, final ActionListener<LicensesService.LicensesUpdateResponse> listener);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove only signed license(s) for provided features from the cluster state metadata
|
|
||||||
*/
|
|
||||||
public void removeLicenses(final DeleteLicenseRequestHolder requestHolder, final ActionListener<ClusterStateUpdateResponse> listener);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the set of features that are currently enabled
|
|
||||||
*/
|
|
||||||
public Set<String> enabledFeatures();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a list of licenses, contains one license (with the latest expiryDate) per registered features sorted by latest issueDate
|
|
||||||
*/
|
|
||||||
public List<License> getLicenses();
|
|
||||||
}
|
|
|
@ -1,155 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.core;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import org.elasticsearch.cluster.AbstractDiffable;
|
|
||||||
import org.elasticsearch.cluster.metadata.MetaData;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.core.Licenses;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains metadata about registered licenses
|
|
||||||
*/
|
|
||||||
public class LicensesMetaData extends AbstractDiffable<MetaData.Custom> implements MetaData.Custom {
|
|
||||||
|
|
||||||
public static final String TYPE = "licenses";
|
|
||||||
|
|
||||||
public static final LicensesMetaData PROTO = new LicensesMetaData(Collections.<License>emptyList(), Collections.<License>emptyList());
|
|
||||||
|
|
||||||
private final ImmutableList<License> signedLicenses;
|
|
||||||
|
|
||||||
private final ImmutableList<License> trialLicenses;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs new licenses metadata
|
|
||||||
*
|
|
||||||
* @param signedLicenses list of signed Licenses
|
|
||||||
* @param trialLicenses set of encoded trial licenses
|
|
||||||
*/
|
|
||||||
public LicensesMetaData(List<License> signedLicenses, List<License> trialLicenses) {
|
|
||||||
this.signedLicenses = ImmutableList.copyOf(signedLicenses);
|
|
||||||
this.trialLicenses = ImmutableList.copyOf(trialLicenses);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<License> getSignedLicenses() {
|
|
||||||
return signedLicenses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<License> getTrialLicenses() {
|
|
||||||
return trialLicenses;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) return true;
|
|
||||||
if (obj == null || getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LicensesMetaData that = (LicensesMetaData) obj;
|
|
||||||
return signedLicenses.equals(that.signedLicenses)
|
|
||||||
&& trialLicenses.equals(that.trialLicenses);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return signedLicenses.hashCode() + 31 * trialLicenses.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String type() {
|
|
||||||
return TYPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LicensesMetaData fromXContent(XContentParser parser) throws IOException {
|
|
||||||
List<License> trialLicenses = new ArrayList<>();
|
|
||||||
List<License> signedLicenses = new ArrayList<>();
|
|
||||||
XContentParser.Token token;
|
|
||||||
while (parser.currentToken() != XContentParser.Token.END_OBJECT) {
|
|
||||||
token = parser.nextToken();
|
|
||||||
if (token == XContentParser.Token.FIELD_NAME) {
|
|
||||||
String fieldName = parser.text();
|
|
||||||
if (fieldName != null) {
|
|
||||||
if (fieldName.equals(Fields.TRIAL_LICENSES)) {
|
|
||||||
if (parser.nextToken() == XContentParser.Token.START_ARRAY) {
|
|
||||||
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
|
|
||||||
if (parser.currentToken().isValue()) {
|
|
||||||
trialLicenses.add(TrialLicenseUtils.fromEncodedTrialLicense(parser.text()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fieldName.equals(Fields.SIGNED_LICENCES)) {
|
|
||||||
if (parser.nextToken() == XContentParser.Token.START_ARRAY) {
|
|
||||||
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
|
|
||||||
License.Builder builder = License.builder().fromXContent(parser);
|
|
||||||
signedLicenses.add(builder.build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new LicensesMetaData(signedLicenses, trialLicenses);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EnumSet<MetaData.XContentContext> context() {
|
|
||||||
return EnumSet.of(MetaData.XContentContext.GATEWAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput streamOutput) throws IOException {
|
|
||||||
Licenses.writeTo(signedLicenses, streamOutput);
|
|
||||||
streamOutput.writeVInt(trialLicenses.size());
|
|
||||||
for (License trialLicense : trialLicenses) {
|
|
||||||
streamOutput.writeString(TrialLicenseUtils.toEncodedTrialLicense(trialLicense));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MetaData.Custom readFrom(StreamInput streamInput) throws IOException {
|
|
||||||
List<License> signedLicenses = Licenses.readFrom(streamInput);
|
|
||||||
int numTrialLicenses = streamInput.readVInt();
|
|
||||||
List<License> trialLicenses = new ArrayList<>(numTrialLicenses);
|
|
||||||
for (int i = 0; i < numTrialLicenses; i++) {
|
|
||||||
trialLicenses.add(TrialLicenseUtils.fromEncodedTrialLicense(streamInput.readString()));
|
|
||||||
}
|
|
||||||
return new LicensesMetaData(signedLicenses, trialLicenses);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
|
||||||
builder.startArray(Fields.TRIAL_LICENSES);
|
|
||||||
for (License trailLicense : trialLicenses) {
|
|
||||||
builder.value(TrialLicenseUtils.toEncodedTrialLicense(trailLicense));
|
|
||||||
}
|
|
||||||
builder.endArray();
|
|
||||||
|
|
||||||
builder.startArray(Fields.SIGNED_LICENCES);
|
|
||||||
for (License license : signedLicenses) {
|
|
||||||
license.toXContent(builder, params);
|
|
||||||
}
|
|
||||||
builder.endArray();
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final static class Fields {
|
|
||||||
private static final String SIGNED_LICENCES = "signed_licenses";
|
|
||||||
private static final String TRIAL_LICENSES = "trial_licenses";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,986 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.core;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.collect.Iterables;
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import org.apache.lucene.util.CollectionUtil;
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.cluster.*;
|
|
||||||
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
|
|
||||||
import org.elasticsearch.cluster.metadata.MetaData;
|
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
|
||||||
import org.elasticsearch.common.Nullable;
|
|
||||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
|
||||||
import org.elasticsearch.common.component.Lifecycle;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.inject.Singleton;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
|
|
||||||
import org.elasticsearch.common.util.concurrent.FutureUtils;
|
|
||||||
import org.elasticsearch.gateway.GatewayService;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.core.LicenseVerifier;
|
|
||||||
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
|
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
|
||||||
import org.elasticsearch.transport.*;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import static org.elasticsearch.license.core.Licenses.reduceAndMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service responsible for managing {@link LicensesMetaData}
|
|
||||||
* Interfaces through which this is exposed are:
|
|
||||||
* - LicensesManagerService - responsible for managing signed and one-time-trial licenses
|
|
||||||
* - LicensesClientService - responsible for feature registration and notification to consumer plugin(s)
|
|
||||||
* <p/>
|
|
||||||
* <p/>
|
|
||||||
* Registration Scheme:
|
|
||||||
* <p/>
|
|
||||||
* A consumer plugin (feature) is registered with {@link LicensesClientService#register(String, TrialLicenseOptions, java.util.Collection<ExpirationCallback>, LicensesClientService.Listener)}
|
|
||||||
* This method can be called at any time during the life-cycle of the consumer plugin.
|
|
||||||
* If the feature can not be registered immediately, it is queued up and registered on the first clusterChanged event with
|
|
||||||
* no {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK} block
|
|
||||||
* Upon successful registration, the feature(s) are notified appropriately using the notification scheme
|
|
||||||
* <p/>
|
|
||||||
* <p/>
|
|
||||||
* Notification Scheme:
|
|
||||||
* <p/>
|
|
||||||
* All registered feature(s) are notified using {@link #notifyFeatures(LicensesMetaData)} (depends on the current
|
|
||||||
* {@link #registeredListeners}). It is idempotent with respect to all the feature listeners.
|
|
||||||
* <p/>
|
|
||||||
* The notification scheduling is done by {@link #notifyFeaturesAndScheduleNotification(LicensesMetaData)} which does the following:
|
|
||||||
* - calls {@link #notifyFeatures(LicensesMetaData)} to notify all registered feature(s)
|
|
||||||
* - if there is any license(s) with a future expiry date in the current cluster state:
|
|
||||||
* - schedules a delayed {@link LicensingClientNotificationJob} on the MIN of all the expiry dates of all the registered feature(s)
|
|
||||||
* <p/>
|
|
||||||
* The {@link LicensingClientNotificationJob} calls {@link #notifyFeaturesAndScheduleNotification(LicensesMetaData)} to schedule
|
|
||||||
* another delayed {@link LicensingClientNotificationJob} as stated above. It is a no-op in case of a global block on
|
|
||||||
* {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK}
|
|
||||||
* <p/>
|
|
||||||
* Upon successful registration of a new feature:
|
|
||||||
* - {@link #notifyFeaturesAndScheduleNotification(LicensesMetaData)} is called
|
|
||||||
* <p/>
|
|
||||||
* Upon clusterChanged():
|
|
||||||
* - {@link #notifyFeaturesAndScheduleNotification(LicensesMetaData)} is called if:
|
|
||||||
* - new trial/signed license(s) are found in the cluster state meta data
|
|
||||||
* - if new feature(s) are added to the registeredListener
|
|
||||||
* - if the previous cluster state had a global block on {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK}
|
|
||||||
* - no-op in case of global block on {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK}
|
|
||||||
*/
|
|
||||||
@Singleton
|
|
||||||
public class LicensesService extends AbstractLifecycleComponent<LicensesService> implements ClusterStateListener, LicensesManagerService, LicensesClientService {
|
|
||||||
|
|
||||||
public static final String REGISTER_TRIAL_LICENSE_ACTION_NAME = "internal:plugin/license/cluster/register_trial_license";
|
|
||||||
|
|
||||||
private final ClusterService clusterService;
|
|
||||||
|
|
||||||
private final ThreadPool threadPool;
|
|
||||||
|
|
||||||
private final TransportService transportService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Currently active consumers to notify to
|
|
||||||
*/
|
|
||||||
private final List<ListenerHolder> registeredListeners = new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Currently pending consumers that has to be registered
|
|
||||||
*/
|
|
||||||
private final Queue<ListenerHolder> pendingListeners = new ConcurrentLinkedQueue<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Currently active scheduledNotifications
|
|
||||||
* All finished notifications will be cleared by {@link #scheduleNextNotification(long)}
|
|
||||||
*/
|
|
||||||
private final Queue<ScheduledFuture> scheduledNotifications = new ConcurrentLinkedQueue<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Currently active event notifications for every registered feature
|
|
||||||
*/
|
|
||||||
private final Map<String, Queue<ScheduledFuture>> eventNotificationsMap = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The last licensesMetaData that has been notified by {@link #notifyFeatures(LicensesMetaData)}
|
|
||||||
*/
|
|
||||||
private final AtomicReference<LicensesMetaData> lastObservedLicensesState;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public LicensesService(Settings settings, ClusterService clusterService, ThreadPool threadPool, TransportService transportService) {
|
|
||||||
super(settings);
|
|
||||||
this.clusterService = clusterService;
|
|
||||||
this.threadPool = threadPool;
|
|
||||||
this.transportService = transportService;
|
|
||||||
this.lastObservedLicensesState = new AtomicReference<>(null);
|
|
||||||
if (DiscoveryNode.masterNode(settings)) {
|
|
||||||
transportService.registerRequestHandler(REGISTER_TRIAL_LICENSE_ACTION_NAME, RegisterTrialLicenseRequest.class,
|
|
||||||
ThreadPool.Names.SAME, new RegisterTrialLicenseRequestHandler());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void registerLicenses(final PutLicenseRequestHolder requestHolder, final ActionListener<LicensesUpdateResponse> listener) {
|
|
||||||
final PutLicenseRequest request = requestHolder.request;
|
|
||||||
final Set<License> newLicenses = Sets.newHashSet(request.licenses());
|
|
||||||
LicensesStatus status = checkLicenses(newLicenses);
|
|
||||||
if (status == LicensesStatus.VALID) {
|
|
||||||
clusterService.submitStateUpdateTask(requestHolder.source, new AckedClusterStateUpdateTask<LicensesUpdateResponse>(request, listener) {
|
|
||||||
@Override
|
|
||||||
protected LicensesUpdateResponse newResponse(boolean acknowledged) {
|
|
||||||
return new LicensesUpdateResponse(acknowledged, LicensesStatus.VALID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ClusterState execute(ClusterState currentState) throws Exception {
|
|
||||||
MetaData metaData = currentState.metaData();
|
|
||||||
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
|
|
||||||
LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE);
|
|
||||||
final LicensesWrapper licensesWrapper = LicensesWrapper.wrap(currentLicenses);
|
|
||||||
List<License> updatedSignedLicenses = licensesWrapper.addAndGetSignedLicenses(newLicenses);
|
|
||||||
if (updatedSignedLicenses.size() != licensesWrapper.signedLicenses.size()) {
|
|
||||||
LicensesMetaData newLicensesMetaData = new LicensesMetaData(updatedSignedLicenses, licensesWrapper.trialLicenses);
|
|
||||||
mdBuilder.putCustom(LicensesMetaData.TYPE, newLicensesMetaData);
|
|
||||||
return ClusterState.builder(currentState).metaData(mdBuilder).build();
|
|
||||||
}
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
listener.onResponse(new LicensesUpdateResponse(true, status));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class LicensesUpdateResponse extends ClusterStateUpdateResponse {
|
|
||||||
private final LicensesStatus status;
|
|
||||||
|
|
||||||
public LicensesUpdateResponse(boolean acknowledged, LicensesStatus status) {
|
|
||||||
super(acknowledged);
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LicensesStatus status() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void removeLicenses(final DeleteLicenseRequestHolder requestHolder, final ActionListener<ClusterStateUpdateResponse> listener) {
|
|
||||||
final DeleteLicenseRequest request = requestHolder.request;
|
|
||||||
clusterService.submitStateUpdateTask(requestHolder.source, new AckedClusterStateUpdateTask<ClusterStateUpdateResponse>(request, listener) {
|
|
||||||
@Override
|
|
||||||
protected ClusterStateUpdateResponse newResponse(boolean acknowledged) {
|
|
||||||
return new ClusterStateUpdateResponse(acknowledged);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ClusterState execute(ClusterState currentState) throws Exception {
|
|
||||||
MetaData metaData = currentState.metaData();
|
|
||||||
LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE);
|
|
||||||
final LicensesWrapper licensesWrapper = LicensesWrapper.wrap(currentLicenses);
|
|
||||||
List<License> updatedSignedLicenses = licensesWrapper.removeAndGetSignedLicenses(request.features());
|
|
||||||
if (updatedSignedLicenses.size() != licensesWrapper.signedLicenses.size()) {
|
|
||||||
LicensesMetaData newLicensesMetaData = new LicensesMetaData(updatedSignedLicenses, licensesWrapper.trialLicenses);
|
|
||||||
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
|
|
||||||
mdBuilder.putCustom(LicensesMetaData.TYPE, newLicensesMetaData);
|
|
||||||
return ClusterState.builder(currentState).metaData(mdBuilder).build();
|
|
||||||
} else {
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Set<String> enabledFeatures() {
|
|
||||||
Set<String> enabledFeatures = Sets.newHashSet();
|
|
||||||
for (ListenerHolder holder : registeredListeners) {
|
|
||||||
if (holder.enabled) {
|
|
||||||
enabledFeatures.add(holder.feature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
|
||||||
for (String feature: enabledFeatures) {
|
|
||||||
if (stringBuilder.length() != 0) {
|
|
||||||
stringBuilder.append(", ");
|
|
||||||
}
|
|
||||||
stringBuilder.append(feature);
|
|
||||||
}
|
|
||||||
logger.debug("LicensesManagerService: Enabled Features: [" + stringBuilder.toString() + "]");
|
|
||||||
}
|
|
||||||
return enabledFeatures;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public List<License> getLicenses() {
|
|
||||||
final LicensesMetaData currentMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
|
|
||||||
if (currentMetaData != null) {
|
|
||||||
// don't use ESLicenses.reduceAndMap, as it will merge out expired licenses
|
|
||||||
List<License> currentLicenses = new ArrayList<>();
|
|
||||||
currentLicenses.addAll(currentMetaData.getSignedLicenses());
|
|
||||||
currentLicenses.addAll(currentMetaData.getTrialLicenses());
|
|
||||||
|
|
||||||
// bucket license for feature with the latest expiry date
|
|
||||||
Map<String, License> licenseMap = new HashMap<>();
|
|
||||||
for (License license : currentLicenses) {
|
|
||||||
if (!licenseMap.containsKey(license.feature())) {
|
|
||||||
licenseMap.put(license.feature(), license);
|
|
||||||
} else {
|
|
||||||
License prevLicense = licenseMap.get(license.feature());
|
|
||||||
if (license.expiryDate() > prevLicense.expiryDate()) {
|
|
||||||
licenseMap.put(license.feature(), license);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort the licenses by issue date
|
|
||||||
List<License> reducedLicenses = new ArrayList<>(licenseMap.values());
|
|
||||||
CollectionUtil.introSort(reducedLicenses, new Comparator<License>() {
|
|
||||||
@Override
|
|
||||||
public int compare(License license1, License license2) {
|
|
||||||
return (int) (license2.issueDate() - license1.issueDate());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return reducedLicenses;
|
|
||||||
}
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private LicensesStatus checkLicenses(Set<License> licenses) {
|
|
||||||
final ImmutableMap<String, License> map = reduceAndMap(licenses);
|
|
||||||
if (LicenseVerifier.verifyLicenses(map.values())) {
|
|
||||||
return LicensesStatus.VALID;
|
|
||||||
} else {
|
|
||||||
return LicensesStatus.INVALID;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Master-only operation to generate a one-time trial license for a feature.
|
|
||||||
* The trial license is only generated and stored if the current cluster state metaData
|
|
||||||
* has no signed/one-time-trial license for the feature in question
|
|
||||||
*/
|
|
||||||
private void registerTrialLicense(final RegisterTrialLicenseRequest request) {
|
|
||||||
clusterService.submitStateUpdateTask("register trial license []", new ProcessedClusterStateUpdateTask() {
|
|
||||||
@Override
|
|
||||||
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
|
|
||||||
LicensesMetaData licensesMetaData = newState.metaData().custom(LicensesMetaData.TYPE);
|
|
||||||
logLicenseMetaDataStats("after trial license registration", licensesMetaData);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ClusterState execute(ClusterState currentState) throws Exception {
|
|
||||||
MetaData metaData = currentState.metaData();
|
|
||||||
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
|
|
||||||
LicensesMetaData currentLicensesMetaData = metaData.custom(LicensesMetaData.TYPE);
|
|
||||||
final LicensesWrapper licensesWrapper = LicensesWrapper.wrap(currentLicensesMetaData);
|
|
||||||
// do not generate a trial license for a feature that already has a signed/trial license
|
|
||||||
if (checkTrialLicenseGenerationCondition(request.feature, licensesWrapper)) {
|
|
||||||
List<License> currentTrailLicenses = new ArrayList<>(licensesWrapper.trialLicenses);
|
|
||||||
currentTrailLicenses.add(generateEncodedTrialLicense(request.feature, request.duration, request.maxNodes));
|
|
||||||
final LicensesMetaData newLicensesMetaData = new LicensesMetaData(
|
|
||||||
licensesWrapper.signedLicenses, ImmutableList.copyOf(currentTrailLicenses));
|
|
||||||
mdBuilder.putCustom(LicensesMetaData.TYPE, newLicensesMetaData);
|
|
||||||
return ClusterState.builder(currentState).metaData(mdBuilder).build();
|
|
||||||
}
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(String source, @Nullable Throwable t) {
|
|
||||||
logger.debug("LicensesService: " + source, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkTrialLicenseGenerationCondition(String feature, LicensesWrapper licensesWrapper) {
|
|
||||||
final List<License> currentLicenses = new ArrayList<>();
|
|
||||||
currentLicenses.addAll(licensesWrapper.signedLicenses);
|
|
||||||
currentLicenses.addAll(licensesWrapper.trialLicenses);
|
|
||||||
for (License license : currentLicenses) {
|
|
||||||
if (license.feature().equals(feature)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private License generateEncodedTrialLicense(String feature, TimeValue duration, int maxNodes) {
|
|
||||||
return TrialLicenseUtils.builder()
|
|
||||||
.issuedTo(clusterService.state().getClusterName().value())
|
|
||||||
.issueDate(System.currentTimeMillis())
|
|
||||||
.duration(duration)
|
|
||||||
.feature(feature)
|
|
||||||
.maxNodes(maxNodes)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStart() throws ElasticsearchException {
|
|
||||||
clusterService.add(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStop() throws ElasticsearchException {
|
|
||||||
clusterService.remove(this);
|
|
||||||
|
|
||||||
// cancel all notifications
|
|
||||||
for (ScheduledFuture scheduledNotification : scheduledNotifications) {
|
|
||||||
FutureUtils.cancel(scheduledNotification);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Queue<ScheduledFuture> queue : eventNotificationsMap.values()) {
|
|
||||||
for (ScheduledFuture scheduledFuture : queue) {
|
|
||||||
FutureUtils.cancel(scheduledFuture);
|
|
||||||
}
|
|
||||||
queue.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
LicensesMetaData currentLicensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
|
|
||||||
final Map<String, License> effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData);
|
|
||||||
// notify features to be disabled
|
|
||||||
for (ListenerHolder holder : registeredListeners) {
|
|
||||||
holder.disableFeatureIfNeeded(effectiveLicenses.get(holder.feature), false);
|
|
||||||
}
|
|
||||||
// clear all handlers
|
|
||||||
registeredListeners.clear();
|
|
||||||
|
|
||||||
// empty out notification queue
|
|
||||||
scheduledNotifications.clear();
|
|
||||||
|
|
||||||
lastObservedLicensesState.set(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doClose() throws ElasticsearchException {
|
|
||||||
transportService.removeHandler(REGISTER_TRIAL_LICENSE_ACTION_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When there is no global block on {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK}:
|
|
||||||
* - tries to register any {@link #pendingListeners} by calling {@link #registeredListeners}
|
|
||||||
* - if any {@link #pendingListeners} are registered successfully or if previous cluster state had a block on
|
|
||||||
* {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK}, calls
|
|
||||||
* {@link #notifyFeaturesAndScheduleNotification(LicensesMetaData)}
|
|
||||||
* - else calls {@link #notifyFeaturesAndScheduleNotificationIfNeeded(LicensesMetaData)}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void clusterChanged(ClusterChangedEvent event) {
|
|
||||||
final ClusterState previousClusterState = event.previousState();
|
|
||||||
final ClusterState currentClusterState = event.state();
|
|
||||||
if (!currentClusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
|
|
||||||
final LicensesMetaData oldLicensesMetaData = previousClusterState.getMetaData().custom(LicensesMetaData.TYPE);
|
|
||||||
final LicensesMetaData currentLicensesMetaData = currentClusterState.getMetaData().custom(LicensesMetaData.TYPE);
|
|
||||||
logLicenseMetaDataStats("old", oldLicensesMetaData);
|
|
||||||
logLicenseMetaDataStats("new", currentLicensesMetaData);
|
|
||||||
|
|
||||||
// register any pending listeners
|
|
||||||
if (!pendingListeners.isEmpty()) {
|
|
||||||
ListenerHolder pendingRegistrationListener;
|
|
||||||
while ((pendingRegistrationListener = pendingListeners.poll()) != null) {
|
|
||||||
boolean masterAvailable = registerListener(pendingRegistrationListener);
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("trying to register pending listener for " + pendingRegistrationListener.feature + " masterAvailable: " + masterAvailable);
|
|
||||||
}
|
|
||||||
if (!masterAvailable) {
|
|
||||||
// if the master is not available do not, break out of trying pendingListeners
|
|
||||||
pendingListeners.add(pendingRegistrationListener);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// notify all interested plugins
|
|
||||||
if (previousClusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
|
|
||||||
notifyFeaturesAndScheduleNotification(currentLicensesMetaData);
|
|
||||||
} else {
|
|
||||||
notifyFeaturesAndScheduleNotificationIfNeeded(currentLicensesMetaData);
|
|
||||||
}
|
|
||||||
final Map<String, License> effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData);
|
|
||||||
for (ListenerHolder listenerHolder : registeredListeners) {
|
|
||||||
listenerHolder.scheduleNotificationIfNeeded(effectiveLicenses.get(listenerHolder.feature));
|
|
||||||
}
|
|
||||||
} else if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("clusterChanged: no action [has STATE_NOT_RECOVERED_BLOCK]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls {@link #notifyFeaturesAndScheduleNotification(LicensesMetaData)} with <code>currentLicensesMetaData</code>
|
|
||||||
* if it was not already notified on.
|
|
||||||
* <p/>
|
|
||||||
* Upon completion sets <code>currentLicensesMetaData</code> to {@link #lastObservedLicensesState}
|
|
||||||
* to ensure the same license(s) are not notified on from
|
|
||||||
* {@link #clusterChanged(org.elasticsearch.cluster.ClusterChangedEvent)}
|
|
||||||
*/
|
|
||||||
private void notifyFeaturesAndScheduleNotificationIfNeeded(final LicensesMetaData currentLicensesMetaData) {
|
|
||||||
final LicensesMetaData lastNotifiedLicensesMetaData = lastObservedLicensesState.get();
|
|
||||||
if (lastNotifiedLicensesMetaData != null && lastNotifiedLicensesMetaData.equals(currentLicensesMetaData)) {
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("currentLicensesMetaData has been already notified on");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
notifyFeaturesAndScheduleNotification(currentLicensesMetaData);
|
|
||||||
lastObservedLicensesState.set(currentLicensesMetaData);
|
|
||||||
logLicenseMetaDataStats("Setting last observed metaData", currentLicensesMetaData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls {@link #notifyFeatures(LicensesMetaData)} with <code>currentLicensesMetaData</code>
|
|
||||||
* and schedules the earliest expiry (if any) notification for registered feature(s)
|
|
||||||
*/
|
|
||||||
private void notifyFeaturesAndScheduleNotification(final LicensesMetaData currentLicensesMetaData) {
|
|
||||||
long nextScheduleFrequency = notifyFeatures(currentLicensesMetaData);
|
|
||||||
if (nextScheduleFrequency != -1l) {
|
|
||||||
scheduleNextNotification(nextScheduleFrequency);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks license expiry for all the registered feature(s)
|
|
||||||
*
|
|
||||||
* @return -1 if there are no expiring license(s) for any registered feature(s), else
|
|
||||||
* returns the minimum of the expiry times of all the registered feature(s) to
|
|
||||||
* schedule an expiry notification
|
|
||||||
*/
|
|
||||||
private long notifyFeatures(final LicensesMetaData currentLicensesMetaData) {
|
|
||||||
long nextScheduleFrequency = -1l;
|
|
||||||
for (ListenerHolder listenerHolder : registeredListeners) {
|
|
||||||
final Map<String, License> effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData);
|
|
||||||
License license = effectiveLicenses.get(listenerHolder.feature);
|
|
||||||
if (license == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
long expiryDate = license.expiryDate();
|
|
||||||
long issueDate = license.issueDate();
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
long expiryDuration = expiryDate - now;
|
|
||||||
|
|
||||||
if (expiryDuration > 0l && (now - issueDate) >= 0l) {
|
|
||||||
listenerHolder.enableFeatureIfNeeded(license);
|
|
||||||
if (nextScheduleFrequency == -1l) {
|
|
||||||
nextScheduleFrequency = expiryDuration;
|
|
||||||
} else {
|
|
||||||
nextScheduleFrequency = Math.min(expiryDuration, nextScheduleFrequency);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
listenerHolder.disableFeatureIfNeeded(license, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
String status;
|
|
||||||
if (expiryDate != -1l) {
|
|
||||||
status = " status: license expires in : " + TimeValue.timeValueMillis(expiryDate - System.currentTimeMillis());
|
|
||||||
} else {
|
|
||||||
status = " status: no trial/signed license found";
|
|
||||||
}
|
|
||||||
if (expiryDuration > 0l) {
|
|
||||||
status += " action: enableFeatureIfNeeded";
|
|
||||||
} else {
|
|
||||||
status += " action: disableFeatureIfNeeded";
|
|
||||||
}
|
|
||||||
logger.debug(listenerHolder.toString() + "" + status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
if (nextScheduleFrequency == -1l) {
|
|
||||||
logger.debug("no need to schedule next notification");
|
|
||||||
} else {
|
|
||||||
logger.debug("next notification time: " + TimeValue.timeValueMillis(nextScheduleFrequency).toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nextScheduleFrequency;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void logLicenseMetaDataStats(String prefix, LicensesMetaData licensesMetaData) {
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
if (licensesMetaData != null) {
|
|
||||||
StringBuilder signedFeatures = new StringBuilder();
|
|
||||||
for (License license : licensesMetaData.getSignedLicenses()) {
|
|
||||||
if (signedFeatures.length() != 0) {
|
|
||||||
signedFeatures.append(", ");
|
|
||||||
}
|
|
||||||
signedFeatures.append(license.feature());
|
|
||||||
}
|
|
||||||
StringBuilder trialFeatures = new StringBuilder();
|
|
||||||
for (License license : licensesMetaData.getTrialLicenses()) {
|
|
||||||
if (trialFeatures.length() != 0) {
|
|
||||||
trialFeatures.append(", ");
|
|
||||||
}
|
|
||||||
trialFeatures.append(license.feature());
|
|
||||||
}
|
|
||||||
logger.debug(prefix + " LicensesMetaData: signedLicenses: [" + signedFeatures.toString() + "] trialLicenses: [" + trialFeatures.toString() + "]");
|
|
||||||
} else {
|
|
||||||
logger.debug(prefix + " LicensesMetaData: signedLicenses: [] trialLicenses: []");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void register(String feature, TrialLicenseOptions trialLicenseOptions, Collection<ExpirationCallback> expirationCallbacks, Listener listener) {
|
|
||||||
for (final ListenerHolder listenerHolder : Iterables.concat(registeredListeners, pendingListeners)) {
|
|
||||||
if (listenerHolder.feature.equals(feature)) {
|
|
||||||
throw new IllegalStateException("feature: [" + feature + "] has been already registered");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Queue<ScheduledFuture> notificationQueue = new ConcurrentLinkedQueue<>();
|
|
||||||
eventNotificationsMap.put(feature, notificationQueue);
|
|
||||||
final ListenerHolder listenerHolder = new ListenerHolder(feature, trialLicenseOptions, expirationCallbacks, listener, notificationQueue);
|
|
||||||
// don't trust the clusterState for blocks just yet!
|
|
||||||
final Lifecycle.State clusterServiceState = clusterService.lifecycleState();
|
|
||||||
if (clusterServiceState != Lifecycle.State.STARTED) {
|
|
||||||
pendingListeners.add(listenerHolder);
|
|
||||||
} else {
|
|
||||||
if (!registerListener(listenerHolder)) {
|
|
||||||
pendingListeners.add(listenerHolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies new feature listener if it already has a signed license
|
|
||||||
* if new feature has a non-null trial license option, a master node request is made to generate the trial license
|
|
||||||
* then notifies features if needed
|
|
||||||
*
|
|
||||||
* @param listenerHolder of the feature to register
|
|
||||||
* @return true if registration has been completed, false otherwise (if masterNode is not available & trail license spec is provided)
|
|
||||||
* or if there is a global block on {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK}
|
|
||||||
*/
|
|
||||||
private boolean registerListener(final ListenerHolder listenerHolder) {
|
|
||||||
logger.debug("Registering listener for " + listenerHolder.feature);
|
|
||||||
final ClusterState currentState = clusterService.state();
|
|
||||||
if (currentState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final LicensesMetaData currentMetaData = currentState.metaData().custom(LicensesMetaData.TYPE);
|
|
||||||
if (expiryDateForFeature(listenerHolder.feature, currentMetaData) == -1l) {
|
|
||||||
// does not have any license so generate a trial license
|
|
||||||
TrialLicenseOptions options = listenerHolder.trialLicenseOptions;
|
|
||||||
if (options != null) {
|
|
||||||
// Trial license option is provided
|
|
||||||
RegisterTrialLicenseRequest request = new RegisterTrialLicenseRequest(listenerHolder.feature,
|
|
||||||
options.duration, options.maxNodes);
|
|
||||||
if (currentState.nodes().localNodeMaster()) {
|
|
||||||
registerTrialLicense(request);
|
|
||||||
} else {
|
|
||||||
DiscoveryNode masterNode = currentState.nodes().masterNode();
|
|
||||||
if (masterNode != null) {
|
|
||||||
transportService.sendRequest(masterNode,
|
|
||||||
REGISTER_TRIAL_LICENSE_ACTION_NAME, request, EmptyTransportResponseHandler.INSTANCE_SAME);
|
|
||||||
} else {
|
|
||||||
// could not sent register trial license request to master
|
|
||||||
logger.debug("Store as pendingRegistration [master not available yet]");
|
|
||||||
}
|
|
||||||
// make sure trial license is available before registration
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (logger.isDebugEnabled()) {
|
|
||||||
// notify feature as clusterChangedEvent may not happen
|
|
||||||
// as no trial or signed license has been found for feature
|
|
||||||
logger.debug("Calling notifyFeaturesAndScheduleNotification [no trial license spec provided]");
|
|
||||||
}
|
|
||||||
} else if (logger.isDebugEnabled()) {
|
|
||||||
// signed license already found for the new registered
|
|
||||||
// feature, notify feature on registration
|
|
||||||
logger.debug("Calling notifyFeaturesAndScheduleNotification [signed/trial license available]");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentMetaData == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
registeredListeners.add(listenerHolder);
|
|
||||||
notifyFeaturesAndScheduleNotification(currentMetaData);
|
|
||||||
final Map<String, License> effectiveLicenses = getEffectiveLicenses(currentMetaData);
|
|
||||||
listenerHolder.scheduleNotificationIfNeeded(effectiveLicenses.get(listenerHolder.feature));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long expiryDateForFeature(String feature, final LicensesMetaData currentLicensesMetaData) {
|
|
||||||
final Map<String, License> effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData);
|
|
||||||
License featureLicense;
|
|
||||||
if ((featureLicense = effectiveLicenses.get(feature)) != null) {
|
|
||||||
return featureLicense.expiryDate();
|
|
||||||
}
|
|
||||||
return -1l;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<String, License> getEffectiveLicenses(final LicensesMetaData metaData) {
|
|
||||||
Map<String, License> map = new HashMap<>();
|
|
||||||
if (metaData != null) {
|
|
||||||
Set<License> licenses = new HashSet<>();
|
|
||||||
for (License license : metaData.getSignedLicenses()) {
|
|
||||||
if (LicenseVerifier.verifyLicense(license)) {
|
|
||||||
licenses.add(license);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
licenses.addAll(metaData.getTrialLicenses());
|
|
||||||
return reduceAndMap(licenses);
|
|
||||||
}
|
|
||||||
return ImmutableMap.copyOf(map);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears out any completed notification futures
|
|
||||||
*/
|
|
||||||
private static void clearFinishedNotifications(Queue<ScheduledFuture> scheduledNotifications) {
|
|
||||||
while (!scheduledNotifications.isEmpty()) {
|
|
||||||
ScheduledFuture notification = scheduledNotifications.peek();
|
|
||||||
if (notification != null && notification.isDone()) {
|
|
||||||
// remove the notifications that are done
|
|
||||||
scheduledNotifications.poll();
|
|
||||||
} else {
|
|
||||||
// stop emptying out the queue as soon as the first undone future hits
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String executorName() {
|
|
||||||
return ThreadPool.Names.GENERIC;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedules an expiry notification with a delay of <code>nextScheduleDelay</code>
|
|
||||||
*/
|
|
||||||
private void scheduleNextNotification(long nextScheduleDelay) {
|
|
||||||
clearFinishedNotifications(scheduledNotifications);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final TimeValue delay = TimeValue.timeValueMillis(nextScheduleDelay);
|
|
||||||
scheduledNotifications.add(threadPool.schedule(delay, executorName(), new LicensingClientNotificationJob()));
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Scheduling next notification after: " + delay);
|
|
||||||
}
|
|
||||||
} catch (EsRejectedExecutionException ex) {
|
|
||||||
logger.debug("Couldn't re-schedule licensing client notification job", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Job for notifying on expired license(s) to registered feature(s)
|
|
||||||
* In case of a global block on {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK},
|
|
||||||
* the notification is not run, instead the feature(s) would be notified on the next
|
|
||||||
* {@link #clusterChanged(org.elasticsearch.cluster.ClusterChangedEvent)} with no global block
|
|
||||||
*/
|
|
||||||
private class LicensingClientNotificationJob implements Runnable {
|
|
||||||
|
|
||||||
public LicensingClientNotificationJob() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
logger.debug("Performing LicensingClientNotificationJob");
|
|
||||||
|
|
||||||
// next clusterChanged event will deal with the missed notifications
|
|
||||||
final ClusterState currentClusterState = clusterService.state();
|
|
||||||
if (!currentClusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
|
|
||||||
final LicensesMetaData currentLicensesMetaData = currentClusterState.metaData().custom(LicensesMetaData.TYPE);
|
|
||||||
notifyFeaturesAndScheduleNotification(currentLicensesMetaData);
|
|
||||||
} else if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("skip notification [STATE_NOT_RECOVERED_BLOCK]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class PutLicenseRequestHolder {
|
|
||||||
private final PutLicenseRequest request;
|
|
||||||
private final String source;
|
|
||||||
|
|
||||||
public PutLicenseRequestHolder(PutLicenseRequest request, String source) {
|
|
||||||
this.request = request;
|
|
||||||
this.source = source;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class DeleteLicenseRequestHolder {
|
|
||||||
private final DeleteLicenseRequest request;
|
|
||||||
private final String source;
|
|
||||||
|
|
||||||
public DeleteLicenseRequestHolder(DeleteLicenseRequest request, String source) {
|
|
||||||
this.request = request;
|
|
||||||
this.source = source;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores configuration and listener for a feature
|
|
||||||
*/
|
|
||||||
private class ListenerHolder {
|
|
||||||
final String feature;
|
|
||||||
final TrialLicenseOptions trialLicenseOptions;
|
|
||||||
final Collection<ExpirationCallback> expirationCallbacks;
|
|
||||||
final Listener listener;
|
|
||||||
final AtomicLong currentExpiryDate = new AtomicLong(-1l);
|
|
||||||
final Queue<ScheduledFuture> notificationQueue;
|
|
||||||
|
|
||||||
volatile boolean initialized = false;
|
|
||||||
volatile boolean enabled = false; // by default, a consumer plugin should be disabled
|
|
||||||
|
|
||||||
private ListenerHolder(String feature, TrialLicenseOptions trialLicenseOptions, Collection<ExpirationCallback> expirationCallbacks, Listener listener, Queue<ScheduledFuture> notificationQueue) {
|
|
||||||
this.feature = feature;
|
|
||||||
this.trialLicenseOptions = trialLicenseOptions;
|
|
||||||
this.expirationCallbacks = expirationCallbacks;
|
|
||||||
this.listener = listener;
|
|
||||||
this.notificationQueue = notificationQueue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void enableFeatureIfNeeded(License license) {
|
|
||||||
if (!initialized || !enabled) {
|
|
||||||
listener.onEnabled(license);
|
|
||||||
initialized = true;
|
|
||||||
enabled = true;
|
|
||||||
logger.info("license for [" + feature + "] - valid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void disableFeatureIfNeeded(License license, boolean log) {
|
|
||||||
if (!initialized || enabled) {
|
|
||||||
listener.onDisabled(license);
|
|
||||||
initialized = true;
|
|
||||||
enabled = false;
|
|
||||||
if (log) {
|
|
||||||
logger.info("license for [" + feature + "] - expired");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Runnable triggerJob(final ExpirationCallback callback) {
|
|
||||||
return new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
LicensesMetaData currentLicensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
|
|
||||||
final Map<String, License> effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData);
|
|
||||||
triggerEvent(effectiveLicenses.get(feature), System.currentTimeMillis(), callback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* schedules all {@link ExpirationCallback} associated with <code>license</code>
|
|
||||||
* If callbacks are already scheduled clear up finished notifications from the queue
|
|
||||||
*/
|
|
||||||
private void scheduleNotificationIfNeeded(final License license) {
|
|
||||||
long expiryDate = ((license != null) ? license.expiryDate() : -1l);
|
|
||||||
if (currentExpiryDate.get() == expiryDate) {
|
|
||||||
clearFinishedNotifications(notificationQueue);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentExpiryDate.set(expiryDate);
|
|
||||||
|
|
||||||
// clear out notification queue
|
|
||||||
while (!notificationQueue.isEmpty()) {
|
|
||||||
ScheduledFuture notification = notificationQueue.peek();
|
|
||||||
if (notification != null) {
|
|
||||||
// cancel
|
|
||||||
FutureUtils.cancel(notification);
|
|
||||||
notificationQueue.poll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
// Schedule the first for all the callbacks
|
|
||||||
long expiryDuration = expiryDate - now;
|
|
||||||
|
|
||||||
//schedule first event of callbacks that will be activated in the future
|
|
||||||
for (ExpirationCallback expirationCallback : expirationCallbacks) {
|
|
||||||
if (!expirationCallback.matches(expiryDate, now)) {
|
|
||||||
long delay = -1l;
|
|
||||||
switch (expirationCallback.orientation()) {
|
|
||||||
case PRE:
|
|
||||||
delay = expiryDuration - expirationCallback.max().getMillis();
|
|
||||||
break;
|
|
||||||
case POST:
|
|
||||||
if (expiryDuration >= 0l) {
|
|
||||||
delay = expiryDuration + expirationCallback.min().getMillis();
|
|
||||||
} else {
|
|
||||||
delay = (-1l * expiryDuration) - expirationCallback.min().getMillis();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (delay > 0l) {
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Adding first notification for: orientation: " + expirationCallback.orientation().name()
|
|
||||||
+ " min: " + expirationCallback.min()
|
|
||||||
+ " max: " + expirationCallback.max()
|
|
||||||
+ " with delay: " + TimeValue.timeValueMillis(delay)
|
|
||||||
+ " license expiry duration: " + TimeValue.timeValueMillis(expiryDuration));
|
|
||||||
}
|
|
||||||
notificationQueue.add(threadPool.schedule(TimeValue.timeValueMillis(delay), executorName(), triggerJob(expirationCallback)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (license != null) {
|
|
||||||
// schedule first event of callbacks that match
|
|
||||||
logger.debug("Calling TRIGGER_EVENTS with license for " + license.feature() + " expiry: " + license.expiryDate());
|
|
||||||
for (ExpirationCallback expirationCallback : expirationCallbacks) {
|
|
||||||
triggerEvent(license, now, expirationCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void triggerEvent(final License license, long now, final ExpirationCallback expirationCallback) {
|
|
||||||
if (expirationCallback.matches(license.expiryDate(), now)) {
|
|
||||||
long expiryDuration = license.expiryDate() - now;
|
|
||||||
boolean expired = expiryDuration <= 0l;
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Calling notification on: orientation: " + expirationCallback.orientation().name()
|
|
||||||
+ " min: " + expirationCallback.min()
|
|
||||||
+ " max: " + expirationCallback.max()
|
|
||||||
+ " scheduled after: " + expirationCallback.frequency().getMillis()
|
|
||||||
+ " next interval match: " + expirationCallback.matches(license.expiryDate(), System.currentTimeMillis() + expirationCallback.frequency().getMillis()));
|
|
||||||
}
|
|
||||||
expirationCallback.on(license, new ExpirationStatus(expired, TimeValue.timeValueMillis((!expired) ? expiryDuration : (-1l * expiryDuration))));
|
|
||||||
notificationQueue.add(threadPool.schedule(expirationCallback.frequency(), executorName(), triggerJob(expirationCallback)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "(feature: " + feature + ", enabled: " + enabled + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thin wrapper to work with {@link LicensesMetaData}
|
|
||||||
* Never mutates the wrapped metaData
|
|
||||||
*/
|
|
||||||
private static class LicensesWrapper {
|
|
||||||
|
|
||||||
public static LicensesWrapper wrap(LicensesMetaData licensesMetaData) {
|
|
||||||
return new LicensesWrapper(licensesMetaData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ImmutableList<License> signedLicenses = ImmutableList.of();
|
|
||||||
private ImmutableList<License> trialLicenses = ImmutableList.of();
|
|
||||||
|
|
||||||
private LicensesWrapper(LicensesMetaData licensesMetaData) {
|
|
||||||
if (licensesMetaData != null) {
|
|
||||||
this.signedLicenses = ImmutableList.copyOf(licensesMetaData.getSignedLicenses());
|
|
||||||
this.trialLicenses = ImmutableList.copyOf(licensesMetaData.getTrialLicenses());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns existingLicenses + newLicenses.
|
|
||||||
* A new license is added if:
|
|
||||||
* - there is no current license for the feature
|
|
||||||
* - current license for feature has a earlier expiry date
|
|
||||||
*/
|
|
||||||
private List<License> addAndGetSignedLicenses(Set<License> newLicenses) {
|
|
||||||
final ImmutableMap<String, License> newLicensesMap = reduceAndMap(newLicenses);
|
|
||||||
List<License> newSignedLicenses = new ArrayList<>(signedLicenses);
|
|
||||||
final ImmutableMap<String, License> oldLicenseMap = reduceAndMap(Sets.newHashSet(signedLicenses));
|
|
||||||
for (String newFeature : newLicensesMap.keySet()) {
|
|
||||||
final License newFeatureLicense = newLicensesMap.get(newFeature);
|
|
||||||
if (oldLicenseMap.containsKey(newFeature)) {
|
|
||||||
final License oldFeatureLicense = oldLicenseMap.get(newFeature);
|
|
||||||
if (oldFeatureLicense.expiryDate() < newFeatureLicense.expiryDate()) {
|
|
||||||
newSignedLicenses.add(newFeatureLicense);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newSignedLicenses.add(newFeatureLicense);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ImmutableList.copyOf(newSignedLicenses);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<License> removeAndGetSignedLicenses(Set<String> features) {
|
|
||||||
List<License> updatedSignedLicenses = new ArrayList<>();
|
|
||||||
for (License license : signedLicenses) {
|
|
||||||
if (!features.contains(license.feature())) {
|
|
||||||
updatedSignedLicenses.add(license);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ImmutableList.copyOf(updatedSignedLicenses);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request for trial license generation to master
|
|
||||||
*/
|
|
||||||
private static class RegisterTrialLicenseRequest extends TransportRequest {
|
|
||||||
private int maxNodes;
|
|
||||||
private String feature;
|
|
||||||
private TimeValue duration;
|
|
||||||
|
|
||||||
private RegisterTrialLicenseRequest() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private RegisterTrialLicenseRequest(String feature, TimeValue duration, int maxNodes) {
|
|
||||||
this.maxNodes = maxNodes;
|
|
||||||
this.feature = feature;
|
|
||||||
this.duration = duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
maxNodes = in.readVInt();
|
|
||||||
feature = in.readString();
|
|
||||||
duration = new TimeValue(in.readVLong(), TimeUnit.MILLISECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
super.writeTo(out);
|
|
||||||
out.writeVInt(maxNodes);
|
|
||||||
out.writeString(feature);
|
|
||||||
out.writeVLong(duration.getMillis());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request handler for trial license generation to master
|
|
||||||
*/
|
|
||||||
private class RegisterTrialLicenseRequestHandler implements TransportRequestHandler<RegisterTrialLicenseRequest> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void messageReceived(RegisterTrialLicenseRequest request, TransportChannel channel) throws Exception {
|
|
||||||
registerTrialLicense(request);
|
|
||||||
channel.sendResponse(TransportResponse.Empty.INSTANCE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.core;
|
|
||||||
|
|
||||||
public enum LicensesStatus {
|
|
||||||
VALID((byte) 0),
|
|
||||||
INVALID((byte) 1),
|
|
||||||
EXPIRED((byte) 2);
|
|
||||||
|
|
||||||
private final byte id;
|
|
||||||
|
|
||||||
LicensesStatus(byte id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int id() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LicensesStatus fromId(int id) {
|
|
||||||
if (id == 0) {
|
|
||||||
return VALID;
|
|
||||||
} else if (id == 1) {
|
|
||||||
return INVALID;
|
|
||||||
} else if (id == 2) {
|
|
||||||
return EXPIRED;
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("no valid LicensesStatus for id=" + id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.core;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.Base64;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.common.xcontent.*;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.core.Licenses;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.elasticsearch.license.core.CryptUtils.decrypt;
|
|
||||||
import static org.elasticsearch.license.core.CryptUtils.encrypt;
|
|
||||||
|
|
||||||
public class TrialLicenseUtils {
|
|
||||||
|
|
||||||
public static TrialLicenseBuilder builder() {
|
|
||||||
return new TrialLicenseBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TrialLicenseBuilder {
|
|
||||||
private static final String DEFAULT_ISSUER = "elasticsearch";
|
|
||||||
private static final String DEFAULT_TYPE = "trial";
|
|
||||||
private static final String DEFAULT_SUBSCRIPTION_TYPE = "none";
|
|
||||||
|
|
||||||
private String feature;
|
|
||||||
private long expiryDate = -1;
|
|
||||||
private long issueDate = -1;
|
|
||||||
private TimeValue duration;
|
|
||||||
private int maxNodes = -1;
|
|
||||||
private String uid = null;
|
|
||||||
private String issuedTo;
|
|
||||||
|
|
||||||
public TrialLicenseBuilder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrialLicenseBuilder uid(String uid) {
|
|
||||||
this.uid = uid;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrialLicenseBuilder issuedTo(String issuedTo) {
|
|
||||||
this.issuedTo = issuedTo;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrialLicenseBuilder maxNodes(int maxNodes) {
|
|
||||||
this.maxNodes = maxNodes;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrialLicenseBuilder feature(String featureType) {
|
|
||||||
this.feature = featureType;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrialLicenseBuilder issueDate(long issueDate) {
|
|
||||||
this.issueDate = issueDate;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrialLicenseBuilder duration(TimeValue duration) {
|
|
||||||
this.duration = duration;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrialLicenseBuilder expiryDate(long expiryDate) {
|
|
||||||
this.expiryDate = expiryDate;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public License build() {
|
|
||||||
if (expiryDate == -1) {
|
|
||||||
expiryDate = issueDate + duration.millis();
|
|
||||||
}
|
|
||||||
if (uid == null) {
|
|
||||||
uid = UUID.randomUUID().toString();
|
|
||||||
}
|
|
||||||
return License.builder()
|
|
||||||
.type(DEFAULT_TYPE)
|
|
||||||
.subscriptionType(DEFAULT_SUBSCRIPTION_TYPE)
|
|
||||||
.issuer(DEFAULT_ISSUER)
|
|
||||||
.uid(uid)
|
|
||||||
.issuedTo(issuedTo)
|
|
||||||
.issueDate(issueDate)
|
|
||||||
.feature(feature)
|
|
||||||
.maxNodes(maxNodes)
|
|
||||||
.expiryDate(expiryDate)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static License fromEncodedTrialLicense(String encodedTrialLicense) throws IOException {
|
|
||||||
byte[] data = decrypt(Base64.decode(encodedTrialLicense));
|
|
||||||
XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(data);
|
|
||||||
parser.nextToken();
|
|
||||||
return License.builder().fromXContent(parser).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String toEncodedTrialLicense(License trialLicense) throws IOException {
|
|
||||||
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
|
|
||||||
// trial license is equivalent to a license spec (no signature)
|
|
||||||
trialLicense.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(Licenses.LICENSE_SPEC_VIEW_MODE, "true")));
|
|
||||||
return Base64.encodeBytes(encrypt(contentBuilder.bytes().toBytes()));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.rest;
|
|
||||||
|
|
||||||
import org.elasticsearch.client.Client;
|
|
||||||
import org.elasticsearch.common.Strings;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
|
|
||||||
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseResponse;
|
|
||||||
import org.elasticsearch.rest.BaseRestHandler;
|
|
||||||
import org.elasticsearch.rest.RestChannel;
|
|
||||||
import org.elasticsearch.rest.RestController;
|
|
||||||
import org.elasticsearch.rest.RestRequest;
|
|
||||||
import org.elasticsearch.rest.action.support.AcknowledgedRestListener;
|
|
||||||
|
|
||||||
import static org.elasticsearch.rest.RestRequest.Method.DELETE;
|
|
||||||
|
|
||||||
public class RestDeleteLicenseAction extends BaseRestHandler {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public RestDeleteLicenseAction(Settings settings, RestController controller, Client client) {
|
|
||||||
super(settings, controller, client);
|
|
||||||
controller.registerHandler(DELETE, "/_licenses/{features}", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
|
|
||||||
final String[] features = Strings.splitStringByCommaToArray(request.param("features"));
|
|
||||||
if (features.length == 0) {
|
|
||||||
throw new IllegalArgumentException("no feature specified for license deletion");
|
|
||||||
}
|
|
||||||
DeleteLicenseRequest deleteLicenseRequest = new DeleteLicenseRequest(features);
|
|
||||||
client.admin().cluster().execute(DeleteLicenseAction.INSTANCE, deleteLicenseRequest, new AcknowledgedRestListener<DeleteLicenseResponse>(channel));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.rest;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import org.elasticsearch.client.Client;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.license.core.Licenses;
|
|
||||||
import org.elasticsearch.license.plugin.action.get.GetLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.action.get.GetLicenseRequest;
|
|
||||||
import org.elasticsearch.license.plugin.action.get.GetLicenseResponse;
|
|
||||||
import org.elasticsearch.rest.*;
|
|
||||||
import org.elasticsearch.rest.action.support.RestBuilderListener;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.elasticsearch.rest.RestRequest.Method.GET;
|
|
||||||
import static org.elasticsearch.rest.RestStatus.OK;
|
|
||||||
|
|
||||||
public class RestGetLicenseAction extends BaseRestHandler {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public RestGetLicenseAction(Settings settings, RestController controller, Client client) {
|
|
||||||
super(settings, controller, client);
|
|
||||||
controller.registerHandler(GET, "/_licenses", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* There will be only one license displayed per feature, the selected license will have the latest expiry_date
|
|
||||||
* out of all other licenses for the feature.
|
|
||||||
* <p/>
|
|
||||||
* The licenses are sorted by latest issue_date
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
|
|
||||||
final Map<String, String> overrideParams = ImmutableMap.of(Licenses.REST_VIEW_MODE, "true");
|
|
||||||
final ToXContent.Params params = new ToXContent.DelegatingMapParams(overrideParams, request);
|
|
||||||
GetLicenseRequest getLicenseRequest = new GetLicenseRequest();
|
|
||||||
getLicenseRequest.local(request.paramAsBoolean("local", getLicenseRequest.local()));
|
|
||||||
client.admin().cluster().execute(GetLicenseAction.INSTANCE, getLicenseRequest, new RestBuilderListener<GetLicenseResponse>(channel) {
|
|
||||||
@Override
|
|
||||||
public RestResponse buildResponse(GetLicenseResponse response, XContentBuilder builder) throws Exception {
|
|
||||||
// Default to pretty printing, but allow ?pretty=false to disable
|
|
||||||
if (!request.hasParam("pretty")) {
|
|
||||||
builder.prettyPrint().lfAtEnd();
|
|
||||||
}
|
|
||||||
Licenses.toXContent(response.licenses(), builder, params);
|
|
||||||
return new BytesRestResponse(OK, builder);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.rest;
|
|
||||||
|
|
||||||
import org.elasticsearch.client.Client;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesStatus;
|
|
||||||
import org.elasticsearch.rest.BaseRestHandler;
|
|
||||||
import org.elasticsearch.rest.RestChannel;
|
|
||||||
import org.elasticsearch.rest.RestController;
|
|
||||||
import org.elasticsearch.rest.RestRequest;
|
|
||||||
import org.elasticsearch.rest.action.support.AcknowledgedRestListener;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import static org.elasticsearch.rest.RestRequest.Method.POST;
|
|
||||||
import static org.elasticsearch.rest.RestRequest.Method.PUT;
|
|
||||||
|
|
||||||
public class RestPutLicenseAction extends BaseRestHandler {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public RestPutLicenseAction(Settings settings, RestController controller, Client client) {
|
|
||||||
super(settings, controller, client);
|
|
||||||
controller.registerHandler(PUT, "/_licenses", this);
|
|
||||||
controller.registerHandler(POST, "/_licenses", this);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
|
|
||||||
PutLicenseRequest putLicenseRequest = new PutLicenseRequest();
|
|
||||||
putLicenseRequest.licenses(request.content().toUtf8());
|
|
||||||
client.admin().cluster().execute(PutLicenseAction.INSTANCE, putLicenseRequest, new LicensesAcknowledgedListener(channel));
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LicensesAcknowledgedListener extends AcknowledgedRestListener<PutLicenseResponse> {
|
|
||||||
|
|
||||||
|
|
||||||
public LicensesAcknowledgedListener(RestChannel channel) {
|
|
||||||
super(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void addCustomFields(XContentBuilder builder, PutLicenseResponse response) throws IOException {
|
|
||||||
LicensesStatus status = response.status();
|
|
||||||
String statusString = null;
|
|
||||||
switch (status) {
|
|
||||||
case VALID:
|
|
||||||
statusString = "valid";
|
|
||||||
break;
|
|
||||||
case INVALID:
|
|
||||||
statusString = "invalid";
|
|
||||||
break;
|
|
||||||
case EXPIRED:
|
|
||||||
statusString = "expired";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
builder.field("licenses_status", statusString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
plugin=org.elasticsearch.license.plugin.LicensePlugin
|
|
||||||
version=${project.version}
|
|
|
@ -1,154 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.gateway.GatewayService;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.TestConsumerPluginBase;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.TestPluginServiceBase;
|
|
||||||
import org.elasticsearch.test.InternalTestCluster;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
|
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Framework to test licensing plugin integration for existing/new consumer plugins
|
|
||||||
* see {@link org.elasticsearch.license.plugin.LicensesEagerConsumerPluginIntegrationTests} and {@link org.elasticsearch.license.plugin.LicensesLazyConsumerPluginIntegrationTests}
|
|
||||||
* for example usage
|
|
||||||
*/
|
|
||||||
@ClusterScope(scope = TEST, numDataNodes = 10, numClientNodes = 0, transportClientRatio = 0.0)
|
|
||||||
public abstract class AbstractLicensesConsumerPluginIntegrationTests extends AbstractLicensesIntegrationTests {
|
|
||||||
|
|
||||||
protected final TestConsumerPluginBase consumerPlugin;
|
|
||||||
|
|
||||||
public AbstractLicensesConsumerPluginIntegrationTests(TestConsumerPluginBase consumerPlugin) {
|
|
||||||
this.consumerPlugin = consumerPlugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final int trialLicenseDurationInSeconds = 10;
|
|
||||||
|
|
||||||
protected Settings nodeSettings(int nodeOrdinal) {
|
|
||||||
return Settings.settingsBuilder()
|
|
||||||
.put(super.nodeSettings(nodeOrdinal))
|
|
||||||
.put(consumerPlugin.name()
|
|
||||||
+ ".trial_license_duration_in_seconds", trialLicenseDurationInSeconds)
|
|
||||||
.putArray("plugin.types", LicensePlugin.class.getName(), consumerPlugin.getClass().getName())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void afterTest() throws Exception {
|
|
||||||
wipeAllLicenses();
|
|
||||||
assertThat(awaitBusy(new Predicate<Object>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(Object o) {
|
|
||||||
return !clusterService().state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK);
|
|
||||||
}
|
|
||||||
}), equalTo(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTrialLicenseAndSignedLicenseNotification() throws Exception {
|
|
||||||
logger.info("using " + consumerPlugin.getClass().getName() + " consumer plugin");
|
|
||||||
logger.info(" --> trial license generated");
|
|
||||||
// managerService should report feature to be enabled on all data nodes
|
|
||||||
assertLicenseManagerEnabledFeatureFor(consumerPlugin.featureName());
|
|
||||||
// consumer plugin service should return enabled on all data nodes
|
|
||||||
assertConsumerPluginEnabledNotification(2);
|
|
||||||
|
|
||||||
logger.info(" --> check trial license expiry notification");
|
|
||||||
// consumer plugin should notify onDisabled on all data nodes (expired trial license)
|
|
||||||
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(consumerPlugin.featureName());
|
|
||||||
|
|
||||||
logger.info(" --> put signed license");
|
|
||||||
putLicense(consumerPlugin.featureName(), TimeValue.timeValueSeconds(trialLicenseDurationInSeconds));
|
|
||||||
|
|
||||||
logger.info(" --> check signed license enabled notification");
|
|
||||||
// consumer plugin should notify onEnabled on all data nodes (signed license)
|
|
||||||
assertConsumerPluginEnabledNotification(1);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(consumerPlugin.featureName());
|
|
||||||
|
|
||||||
logger.info(" --> check signed license expiry notification");
|
|
||||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
|
||||||
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(consumerPlugin.featureName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTrialLicenseNotification() throws Exception {
|
|
||||||
logger.info(" --> check onEnabled for trial license");
|
|
||||||
// managerService should report feature to be enabled on all data nodes
|
|
||||||
assertLicenseManagerEnabledFeatureFor(consumerPlugin.featureName());
|
|
||||||
// consumer plugin service should return enabled on all data nodes
|
|
||||||
assertConsumerPluginEnabledNotification(1);
|
|
||||||
|
|
||||||
logger.info(" --> sleep for rest of trailLicense duration");
|
|
||||||
Thread.sleep(trialLicenseDurationInSeconds * 1000l);
|
|
||||||
|
|
||||||
logger.info(" --> check trial license expiry notification");
|
|
||||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
|
||||||
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(consumerPlugin.featureName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOverlappingTrialAndSignedLicenseNotification() throws Exception {
|
|
||||||
logger.info(" --> check onEnabled for trial license");
|
|
||||||
// managerService should report feature to be enabled on all data nodes
|
|
||||||
assertLicenseManagerEnabledFeatureFor(consumerPlugin.featureName());
|
|
||||||
// consumer plugin service should return enabled on all data nodes
|
|
||||||
assertConsumerPluginEnabledNotification(1);
|
|
||||||
|
|
||||||
logger.info(" --> put signed license while trial license is in effect");
|
|
||||||
putLicense(consumerPlugin.featureName(), TimeValue.timeValueSeconds(trialLicenseDurationInSeconds * 2));
|
|
||||||
|
|
||||||
logger.info(" --> check signed license enabled notification");
|
|
||||||
// consumer plugin should notify onEnabled on all data nodes (signed license)
|
|
||||||
assertConsumerPluginEnabledNotification(1);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(consumerPlugin.featureName());
|
|
||||||
|
|
||||||
logger.info(" --> sleep for rest of trailLicense duration");
|
|
||||||
Thread.sleep(trialLicenseDurationInSeconds * 1000l);
|
|
||||||
|
|
||||||
logger.info(" --> check consumer is still enabled [signed license]");
|
|
||||||
// consumer plugin should notify onEnabled on all data nodes (signed license)
|
|
||||||
assertConsumerPluginEnabledNotification(1);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(consumerPlugin.featureName());
|
|
||||||
|
|
||||||
logger.info(" --> check signed license expiry notification");
|
|
||||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
|
||||||
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2 * 2);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(consumerPlugin.featureName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertConsumerPluginEnabledNotification(int timeoutInSec) throws InterruptedException {
|
|
||||||
assertConsumerPluginNotification(consumerPluginServices(), true, timeoutInSec);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertConsumerPluginDisabledNotification(int timeoutInSec) throws InterruptedException {
|
|
||||||
assertConsumerPluginNotification(consumerPluginServices(), false, timeoutInSec);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private List<TestPluginServiceBase> consumerPluginServices() {
|
|
||||||
final InternalTestCluster clients = internalCluster();
|
|
||||||
List<TestPluginServiceBase> consumerPluginServices = new ArrayList<>();
|
|
||||||
for (TestPluginServiceBase service : clients.getDataNodeInstances(consumerPlugin.service())) {
|
|
||||||
consumerPluginServices.add(service);
|
|
||||||
}
|
|
||||||
return consumerPluginServices;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,205 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import org.elasticsearch.cluster.ClusterService;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.ProcessedClusterStateUpdateTask;
|
|
||||||
import org.elasticsearch.cluster.metadata.MetaData;
|
|
||||||
import org.elasticsearch.common.Nullable;
|
|
||||||
import org.elasticsearch.common.joda.DateMathParser;
|
|
||||||
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
|
|
||||||
import org.elasticsearch.common.joda.Joda;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationPluginService;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.TestPluginServiceBase;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesManagerService;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesMetaData;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesStatus;
|
|
||||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
|
||||||
import org.elasticsearch.test.InternalTestCluster;
|
|
||||||
import org.joda.time.format.DateTimeFormatter;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
|
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
|
||||||
import static org.hamcrest.Matchers.greaterThan;
|
|
||||||
|
|
||||||
public abstract class AbstractLicensesIntegrationTests extends ElasticsearchIntegrationTest {
|
|
||||||
|
|
||||||
private final static FormatDateTimeFormatter formatDateTimeFormatter = Joda.forPattern("yyyy-MM-dd");
|
|
||||||
private final static DateTimeFormatter dateTimeFormatter = formatDateTimeFormatter.printer();
|
|
||||||
private final static DateMathParser dateMathParser = new DateMathParser(formatDateTimeFormatter);
|
|
||||||
|
|
||||||
public static String dateMathString(String time, final long now) {
|
|
||||||
return dateTimeFormatter.print(dateMathParser.parse(time, new Callable<Long>() {
|
|
||||||
@Override
|
|
||||||
public Long call() throws Exception {
|
|
||||||
return now;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long dateMath(String time, final long now) {
|
|
||||||
return dateMathParser.parse(time, new Callable<Long>() {
|
|
||||||
@Override
|
|
||||||
public Long call() throws Exception {
|
|
||||||
return now;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
protected Settings nodeSettings(int nodeOrdinal) {
|
|
||||||
return Settings.settingsBuilder()
|
|
||||||
.put("plugins.load_classpath_plugins", false)
|
|
||||||
.put("plugin.types", LicensePlugin.class.getName())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Settings transportClientSettings() {
|
|
||||||
// Plugin should be loaded on the transport client as well
|
|
||||||
return nodeSettings(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void wipeAllLicenses() throws InterruptedException {
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
ClusterService clusterService = internalCluster().getInstance(ClusterService.class, internalCluster().getMasterName());
|
|
||||||
clusterService.submitStateUpdateTask("delete licensing metadata", new ProcessedClusterStateUpdateTask() {
|
|
||||||
@Override
|
|
||||||
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ClusterState execute(ClusterState currentState) throws Exception {
|
|
||||||
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
|
|
||||||
mdBuilder.removeCustom(LicensesMetaData.TYPE);
|
|
||||||
return ClusterState.builder(currentState).metaData(mdBuilder).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(String source, @Nullable Throwable t) {
|
|
||||||
logger.error("error on metaData cleanup after test", t);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
latch.await();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void putLicense(String feature, TimeValue expiryDuration) throws Exception {
|
|
||||||
License license1 = generateSignedLicense(feature, expiryDuration);
|
|
||||||
final PutLicenseResponse putLicenseResponse = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE).setLicense(Lists.newArrayList(license1)).get();
|
|
||||||
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
|
|
||||||
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected void assertLicenseManagerEnabledFeatureFor(final String feature) throws InterruptedException {
|
|
||||||
assertLicenseManagerStatusFor(feature, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertLicenseManagerDisabledFeatureFor(final String feature) throws InterruptedException {
|
|
||||||
assertLicenseManagerStatusFor(feature, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertLicenseManagerStatusFor(final String feature, final boolean expectedEnabled) throws InterruptedException {
|
|
||||||
assertThat("LicensesManagerService for feature " + feature + " should have enabled status of " + expectedEnabled, awaitBusy(new Predicate<Object>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(Object o) {
|
|
||||||
for (LicensesManagerService managerService : licensesManagerServices()) {
|
|
||||||
if (expectedEnabled != managerService.enabledFeatures().contains(feature)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}, 5, TimeUnit.SECONDS), equalTo(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertEagerConsumerPluginDisableNotification(int timeoutInSec) throws InterruptedException {
|
|
||||||
assertEagerConsumerPluginNotification(false, timeoutInSec);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertEagerConsumerPluginEnableNotification(int timeoutInSec) throws InterruptedException {
|
|
||||||
assertEagerConsumerPluginNotification(true, timeoutInSec);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertLazyConsumerPluginDisableNotification(int timeoutInSec) throws InterruptedException {
|
|
||||||
assertLazyConsumerPluginNotification(false, timeoutInSec);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertLazyConsumerPluginEnableNotification(int timeoutInSec) throws InterruptedException {
|
|
||||||
assertLazyConsumerPluginNotification(true, timeoutInSec);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertLazyConsumerPluginNotification(final boolean expectedEnabled, int timeoutInSec) throws InterruptedException {
|
|
||||||
final List<TestPluginServiceBase> consumerPluginServices = consumerLazyPluginServices();
|
|
||||||
assertConsumerPluginNotification(consumerPluginServices, expectedEnabled, timeoutInSec);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertEagerConsumerPluginNotification(final boolean expectedEnabled, int timeoutInSec) throws InterruptedException {
|
|
||||||
final List<TestPluginServiceBase> consumerPluginServices = consumerEagerPluginServices();
|
|
||||||
assertConsumerPluginNotification(consumerPluginServices, expectedEnabled, timeoutInSec);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void assertConsumerPluginNotification(final List<TestPluginServiceBase> consumerPluginServices, final boolean expectedEnabled, int timeoutInSec) throws InterruptedException {
|
|
||||||
assertThat("At least one instance has to be present", consumerPluginServices.size(), greaterThan(0));
|
|
||||||
assertConsumerPluginNotification(consumerPluginServices.get(0).getClass().getName() + " should have license status of: " + expectedEnabled, consumerPluginServices, expectedEnabled, timeoutInSec);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertConsumerPluginNotification(String msg, final Iterable<TestPluginServiceBase> consumerPluginServices, final boolean expectedEnabled, int timeoutInSec) throws InterruptedException {
|
|
||||||
boolean success = awaitBusy(new Predicate<Object>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(Object o) {
|
|
||||||
for (TestPluginServiceBase pluginService : consumerPluginServices) {
|
|
||||||
if (expectedEnabled != pluginService.enabled()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}, timeoutInSec + 1, TimeUnit.SECONDS);
|
|
||||||
logger.debug("Notification assertion complete");
|
|
||||||
assertThat(msg, success, equalTo(true));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TestPluginServiceBase> consumerLazyPluginServices() {
|
|
||||||
final InternalTestCluster clients = internalCluster();
|
|
||||||
List<TestPluginServiceBase> consumerPluginServices = new ArrayList<>();
|
|
||||||
for (TestPluginServiceBase service : clients.getDataNodeInstances(LazyLicenseRegistrationPluginService.class)) {
|
|
||||||
consumerPluginServices.add(service);
|
|
||||||
}
|
|
||||||
return consumerPluginServices;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<TestPluginServiceBase> consumerEagerPluginServices() {
|
|
||||||
final InternalTestCluster clients = internalCluster();
|
|
||||||
List<TestPluginServiceBase> consumerPluginServices = new ArrayList<>();
|
|
||||||
for (TestPluginServiceBase service : clients.getDataNodeInstances(EagerLicenseRegistrationPluginService.class)) {
|
|
||||||
consumerPluginServices.add(service);
|
|
||||||
}
|
|
||||||
return consumerPluginServices;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Iterable<LicensesManagerService> licensesManagerServices() {
|
|
||||||
final InternalTestCluster clients = internalCluster();
|
|
||||||
return clients.getDataNodeInstances(LicensesManagerService.class);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,195 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
|
||||||
import org.elasticsearch.cluster.ClusterService;
|
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNodes;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesClientService;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesManagerService;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesService;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesStatus;
|
|
||||||
import org.elasticsearch.test.InternalTestCluster;
|
|
||||||
import org.junit.Before;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import static org.elasticsearch.license.plugin.core.LicensesService.LicensesUpdateResponse;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
|
|
||||||
public abstract class AbstractLicensesServiceTests extends AbstractLicensesIntegrationTests {
|
|
||||||
|
|
||||||
private static String node = null;
|
|
||||||
private static String[] nodes;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void beforeTest() throws Exception {
|
|
||||||
wipeAllLicenses();
|
|
||||||
|
|
||||||
DiscoveryNodes discoveryNodes = masterClusterService().state().getNodes();
|
|
||||||
Set<String> dataNodeSet = new HashSet<>();
|
|
||||||
for (DiscoveryNode discoveryNode : discoveryNodes) {
|
|
||||||
if (discoveryNode.dataNode()) {
|
|
||||||
dataNodeSet.add(discoveryNode.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nodes = dataNodeSet.toArray(new String[dataNodeSet.size()]);
|
|
||||||
node = nodes[randomIntBetween(0, nodes.length - 1)];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void registerAndAckSignedLicenses(final LicensesManagerService masterLicensesManagerService, final List<License> license, final LicensesStatus expectedStatus) {
|
|
||||||
PutLicenseRequest putLicenseRequest = new PutLicenseRequest().licenses(license);
|
|
||||||
LicensesService.PutLicenseRequestHolder requestHolder = new LicensesService.PutLicenseRequestHolder(putLicenseRequest, "test");
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
final AtomicBoolean success = new AtomicBoolean(false);
|
|
||||||
masterLicensesManagerService.registerLicenses(requestHolder, new ActionListener<LicensesUpdateResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(LicensesUpdateResponse licensesUpdateResponse) {
|
|
||||||
if (licensesUpdateResponse.isAcknowledged() && licensesUpdateResponse.status() == expectedStatus) {
|
|
||||||
success.set(true);
|
|
||||||
}
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable e) {
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
latch.await();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
fail(e.getMessage());
|
|
||||||
}
|
|
||||||
assertThat("register license(s) failed", success.get(), equalTo(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Action registerWithTrialLicense(final LicensesClientService clientService, final LicensesClientService.Listener clientListener, final String feature, final TimeValue expiryDuration) {
|
|
||||||
return new Action(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
clientService.register(feature, new LicensesService.TrialLicenseOptions(expiryDuration, 10), Collections.<LicensesService.ExpirationCallback>emptyList(),
|
|
||||||
clientListener);
|
|
||||||
|
|
||||||
// invoke clusterChanged event to flush out pendingRegistration
|
|
||||||
LicensesService licensesService = (LicensesService) clientService;
|
|
||||||
ClusterChangedEvent event = new ClusterChangedEvent("", clusterService().state(), clusterService().state());
|
|
||||||
licensesService.clusterChanged(event);
|
|
||||||
}
|
|
||||||
}, 0, 1, "should trigger onEnable for " + feature + " once [trial license]");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Action registerWithEventNotification(final LicensesClientService clientService, final LicensesClientService.Listener clientListener, final String feature, final TimeValue expiryDuration, final Collection<LicensesService.ExpirationCallback> expirationCallbacks) {
|
|
||||||
return new Action(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
clientService.register(feature, new LicensesService.TrialLicenseOptions(expiryDuration, 10), expirationCallbacks,
|
|
||||||
clientListener);
|
|
||||||
|
|
||||||
// invoke clusterChanged event to flush out pendingRegistration
|
|
||||||
LicensesService licensesService = (LicensesService) clientService;
|
|
||||||
ClusterChangedEvent event = new ClusterChangedEvent("", clusterService().state(), clusterService().state());
|
|
||||||
licensesService.clusterChanged(event);
|
|
||||||
}
|
|
||||||
}, 0, 1, "should trigger onEnable for " + feature + " once [trial license]");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class TestTrackingClientListener implements LicensesClientService.Listener {
|
|
||||||
CountDownLatch enableLatch;
|
|
||||||
CountDownLatch disableLatch;
|
|
||||||
AtomicBoolean enabled = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
final boolean track;
|
|
||||||
final String featureName;
|
|
||||||
|
|
||||||
public TestTrackingClientListener(String featureName) {
|
|
||||||
this(featureName, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TestTrackingClientListener(String featureName, boolean track) {
|
|
||||||
this.track = track;
|
|
||||||
this.featureName = featureName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void latch(CountDownLatch enableLatch, CountDownLatch disableLatch) {
|
|
||||||
this.enableLatch = enableLatch;
|
|
||||||
this.disableLatch = disableLatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnabled(License license) {
|
|
||||||
assertNotNull(license);
|
|
||||||
assertThat(license.feature(), equalTo(featureName));
|
|
||||||
enabled.set(true);
|
|
||||||
if (track) {
|
|
||||||
this.enableLatch.countDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisabled(License license) {
|
|
||||||
assertNotNull(license);
|
|
||||||
assertThat(license.feature(), equalTo(featureName));
|
|
||||||
enabled.set(false);
|
|
||||||
if (track) {
|
|
||||||
this.disableLatch.countDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected class Action {
|
|
||||||
final int expectedDisabledCount;
|
|
||||||
final int expectedEnabledCount;
|
|
||||||
final TimeValue timeout;
|
|
||||||
final Runnable action;
|
|
||||||
final String msg;
|
|
||||||
|
|
||||||
protected Action(Runnable action, int expectedEnabledCount, int expectedDisabledCount, String msg) {
|
|
||||||
this(action, expectedEnabledCount, expectedDisabledCount, TimeValue.timeValueSeconds(1), msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Action(Runnable action, int expectedDisabledCount, int expectedEnabledCount, TimeValue timeout, String msg) {
|
|
||||||
this.expectedDisabledCount = expectedDisabledCount;
|
|
||||||
this.expectedEnabledCount = expectedEnabledCount;
|
|
||||||
this.action = action;
|
|
||||||
this.timeout = timeout;
|
|
||||||
this.msg = msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run() {
|
|
||||||
action.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected LicensesManagerService masterLicensesManagerService() {
|
|
||||||
final InternalTestCluster clients = internalCluster();
|
|
||||||
return clients.getInstance(LicensesManagerService.class, clients.getMasterName());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected LicensesClientService licensesClientService() {
|
|
||||||
final InternalTestCluster clients = internalCluster();
|
|
||||||
return internalCluster().getInstance(LicensesClientService.class, clients.getMasterName());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected LicensesService licensesService() {
|
|
||||||
final InternalTestCluster clients = internalCluster();
|
|
||||||
return internalCluster().getInstance(LicensesService.class, clients.getMasterName());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static ClusterService masterClusterService() {
|
|
||||||
final InternalTestCluster clients = internalCluster();
|
|
||||||
return clients.getInstance(ClusterService.class, clients.getMasterName());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,467 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.plugin.core.*;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
|
|
||||||
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
|
|
||||||
import static org.hamcrest.Matchers.*;
|
|
||||||
|
|
||||||
@ClusterScope(scope = TEST, numDataNodes = 1)
|
|
||||||
public class LicensesClientServiceTests extends AbstractLicensesServiceTests {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTrialLicenseEnforcement() throws Exception {
|
|
||||||
// register with trial license and assert onEnable and onDisable notification
|
|
||||||
|
|
||||||
final LicensesClientService clientService = licensesClientService();
|
|
||||||
String feature1 = "feature1";
|
|
||||||
final TestTrackingClientListener clientListener = new TestTrackingClientListener(feature1);
|
|
||||||
List<Action> actions = new ArrayList<>();
|
|
||||||
|
|
||||||
final TimeValue expiryDuration = TimeValue.timeValueSeconds(2);
|
|
||||||
actions.add(registerWithTrialLicense(clientService, clientListener, feature1, expiryDuration));
|
|
||||||
actions.add(assertExpiryAction(feature1, "trial", expiryDuration));
|
|
||||||
assertClientListenerNotificationCount(clientListener, actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLicenseWithFutureIssueDate() throws Exception {
|
|
||||||
final LicensesClientService clientService = licensesClientService();
|
|
||||||
String feature = "feature";
|
|
||||||
final TestTrackingClientListener clientListener = new TestTrackingClientListener(feature);
|
|
||||||
List<Action> actions = new ArrayList<>();
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
long issueDate = dateMath("now+10d/d", now);
|
|
||||||
|
|
||||||
actions.add(registerWithoutTrialLicense(clientService, clientListener, feature));
|
|
||||||
actions.add(generateAndPutSignedLicenseAction(masterLicensesManagerService(), feature, issueDate, TimeValue.timeValueHours(24 * 20)));
|
|
||||||
actions.add(assertExpiryAction(feature, "signed", TimeValue.timeValueSeconds(4)));
|
|
||||||
assertClientListenerNotificationCount(clientListener, actions);
|
|
||||||
assertThat(clientListener.enabled.get(), equalTo(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPostExpiration() throws Exception {
|
|
||||||
int postExpirySeconds = randomIntBetween(5, 10);
|
|
||||||
TimeValue postExpiryDuration = TimeValue.timeValueSeconds(postExpirySeconds);
|
|
||||||
TimeValue min = TimeValue.timeValueSeconds(postExpirySeconds - randomIntBetween(1, 3));
|
|
||||||
TimeValue max = TimeValue.timeValueSeconds(postExpirySeconds + randomIntBetween(1, 10));
|
|
||||||
|
|
||||||
final LicensesService.ExpirationCallback.Post post = new LicensesService.ExpirationCallback.Post(min, max, TimeValue.timeValueMillis(10)) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void on(License license, LicensesService.ExpirationStatus status) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
assertThat(post.matches(now - postExpiryDuration.millis(), now), equalTo(true));
|
|
||||||
assertThat(post.matches(now + postExpiryDuration.getMillis(), now), equalTo(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPostExpirationWithNullMax() throws Exception {
|
|
||||||
int postExpirySeconds = randomIntBetween(5, 10);
|
|
||||||
TimeValue postExpiryDuration = TimeValue.timeValueSeconds(postExpirySeconds);
|
|
||||||
TimeValue min = TimeValue.timeValueSeconds(postExpirySeconds - randomIntBetween(1, 3));
|
|
||||||
|
|
||||||
final LicensesService.ExpirationCallback.Post post = new LicensesService.ExpirationCallback.Post(min, null, TimeValue.timeValueMillis(10)) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void on(License license, LicensesService.ExpirationStatus status) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
assertThat(post.matches(now - postExpiryDuration.millis(), now), equalTo(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPreExpirationWithNullMin() throws Exception {
|
|
||||||
int expirySeconds = randomIntBetween(5, 10);
|
|
||||||
TimeValue expiryDuration = TimeValue.timeValueSeconds(expirySeconds);
|
|
||||||
TimeValue max = TimeValue.timeValueSeconds(expirySeconds + randomIntBetween(1, 10));
|
|
||||||
|
|
||||||
final LicensesService.ExpirationCallback.Pre pre = new LicensesService.ExpirationCallback.Pre(null, max, TimeValue.timeValueMillis(10)) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void on(License license, LicensesService.ExpirationStatus status) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
assertThat(pre.matches(expiryDuration.millis() + now, now), equalTo(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPreExpiration() throws Exception {
|
|
||||||
int expirySeconds = randomIntBetween(5, 10);
|
|
||||||
TimeValue expiryDuration = TimeValue.timeValueSeconds(expirySeconds);
|
|
||||||
TimeValue min = TimeValue.timeValueSeconds(expirySeconds - randomIntBetween(0, 3));
|
|
||||||
TimeValue max = TimeValue.timeValueSeconds(expirySeconds + randomIntBetween(1, 10));
|
|
||||||
|
|
||||||
final LicensesService.ExpirationCallback.Pre pre = new LicensesService.ExpirationCallback.Pre(min, max, TimeValue.timeValueMillis(10)) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void on(License license, LicensesService.ExpirationStatus status) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
assertThat(pre.matches(expiryDuration.millis() + now, now), equalTo(true));
|
|
||||||
assertThat(pre.matches(now - expiryDuration.getMillis(), now), equalTo(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMultipleEventNotification() throws Exception {
|
|
||||||
final LicensesManagerService licensesManagerService = masterLicensesManagerService();
|
|
||||||
final LicensesClientService clientService = licensesClientService();
|
|
||||||
final String feature = "feature";
|
|
||||||
TestTrackingClientListener clientListener = new TestTrackingClientListener(feature, true);
|
|
||||||
|
|
||||||
List<LicensesService.ExpirationCallback> callbacks = new ArrayList<>();
|
|
||||||
final AtomicInteger preTriggerCountStartingFromOneSecond = new AtomicInteger(0);
|
|
||||||
callbacks.add(preCallbackLatch(TimeValue.timeValueMillis(499), TimeValue.timeValueMillis(999), TimeValue.timeValueMillis(100), preTriggerCountStartingFromOneSecond));
|
|
||||||
final AtomicInteger postTriggerCount = new AtomicInteger(0);
|
|
||||||
callbacks.add(postCallbackLatch(TimeValue.timeValueMillis(10), null, TimeValue.timeValueMillis(200), postTriggerCount));
|
|
||||||
final AtomicInteger preTriggerCountStartingFromTwoSeconds = new AtomicInteger(0);
|
|
||||||
callbacks.add(preCallbackLatch(TimeValue.timeValueSeconds(1), TimeValue.timeValueSeconds(2), TimeValue.timeValueMillis(500), preTriggerCountStartingFromTwoSeconds));
|
|
||||||
|
|
||||||
List<Action> actions = new ArrayList<>();
|
|
||||||
actions.add(registerWithEventNotification(clientService, clientListener, feature, TimeValue.timeValueSeconds(3), callbacks));
|
|
||||||
actions.add(assertExpiryAction(feature, "trial", TimeValue.timeValueMinutes(1)));
|
|
||||||
assertClientListenerNotificationCount(clientListener, actions);
|
|
||||||
assertThat(preTriggerCountStartingFromTwoSeconds.get(), greaterThanOrEqualTo(2));
|
|
||||||
assertThat(preTriggerCountStartingFromTwoSeconds.get(), lessThan(4));
|
|
||||||
assertThat(preTriggerCountStartingFromOneSecond.get(), greaterThan(4));
|
|
||||||
int initialPreTriggerStartingFromTwoSeconds = preTriggerCountStartingFromTwoSeconds.get();
|
|
||||||
int initialPreTriggerCountStartingFromOneSecond = preTriggerCountStartingFromOneSecond.get();
|
|
||||||
assertThat(awaitBusy(new Predicate<Object>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(Object o) {
|
|
||||||
if (postTriggerCount.get() > 2) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}, 2, TimeUnit.SECONDS), equalTo(true));
|
|
||||||
int previousTriggerCount = postTriggerCount.get();
|
|
||||||
|
|
||||||
// Update license
|
|
||||||
generateAndPutSignedLicenseAction(licensesManagerService, feature, TimeValue.timeValueSeconds(10)).run();
|
|
||||||
assertThat(previousTriggerCount, lessThanOrEqualTo(postTriggerCount.get() + 1));
|
|
||||||
assertThat(initialPreTriggerCountStartingFromOneSecond, equalTo(preTriggerCountStartingFromOneSecond.get()));
|
|
||||||
assertThat(initialPreTriggerStartingFromTwoSeconds, equalTo(preTriggerCountStartingFromTwoSeconds.get()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPreEventNotification() throws Exception {
|
|
||||||
final LicensesClientService clientService = licensesClientService();
|
|
||||||
final String feature = "feature";
|
|
||||||
TestTrackingClientListener clientListener = new TestTrackingClientListener(feature, true);
|
|
||||||
AtomicInteger counter = new AtomicInteger(0);
|
|
||||||
List<Action> actions = new ArrayList<>();
|
|
||||||
actions.add(
|
|
||||||
registerWithEventNotification(clientService, clientListener, feature, TimeValue.timeValueSeconds(3),
|
|
||||||
Arrays.asList(
|
|
||||||
preCallbackLatch(TimeValue.timeValueMillis(500), TimeValue.timeValueSeconds(2), TimeValue.timeValueMillis(500), counter)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
actions.add(assertExpiryAction(feature, "trial", TimeValue.timeValueSeconds(3)));
|
|
||||||
assertClientListenerNotificationCount(clientListener, actions);
|
|
||||||
assertThat(counter.get(), greaterThanOrEqualTo(3));
|
|
||||||
assertThat(counter.get(), lessThan(5));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPostEventNotification() throws Exception {
|
|
||||||
final LicensesClientService clientService = licensesClientService();
|
|
||||||
final String feature = "feature";
|
|
||||||
TestTrackingClientListener clientListener = new TestTrackingClientListener(feature, true);
|
|
||||||
AtomicInteger counter = new AtomicInteger(0);
|
|
||||||
List<Action> actions = new ArrayList<>();
|
|
||||||
actions.add(
|
|
||||||
registerWithEventNotification(clientService, clientListener, feature, TimeValue.timeValueSeconds(1),
|
|
||||||
Arrays.asList(
|
|
||||||
postCallbackLatch(TimeValue.timeValueMillis(500), TimeValue.timeValueSeconds(2), TimeValue.timeValueMillis(500), counter)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
actions.add(assertExpiryAction(feature, "trial", TimeValue.timeValueSeconds(1)));
|
|
||||||
assertClientListenerNotificationCount(clientListener, actions);
|
|
||||||
Thread.sleep(50 + 2000);
|
|
||||||
assertThat(counter.get(), greaterThanOrEqualTo(3));
|
|
||||||
assertThat(counter.get(), lessThan(5));
|
|
||||||
}
|
|
||||||
|
|
||||||
private LicensesService.ExpirationCallback preCallbackLatch(TimeValue min, TimeValue max, TimeValue frequency, final AtomicInteger triggerCount) {
|
|
||||||
return new LicensesService.ExpirationCallback.Pre(min, max, frequency) {
|
|
||||||
@Override
|
|
||||||
public void on(License license, LicensesService.ExpirationStatus status) {
|
|
||||||
triggerCount.incrementAndGet();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private LicensesService.ExpirationCallback postCallbackLatch(TimeValue min, TimeValue max, TimeValue frequency, final AtomicInteger triggerCount) {
|
|
||||||
return new LicensesService.ExpirationCallback.Post(min, max, frequency) {
|
|
||||||
@Override
|
|
||||||
public void on(License license, LicensesService.ExpirationStatus status) {
|
|
||||||
triggerCount.incrementAndGet();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMultipleClientSignedLicenseEnforcement() throws Exception {
|
|
||||||
// multiple client registration with null trial license and then different expiry signed license
|
|
||||||
|
|
||||||
final LicensesManagerService masterLicensesManagerService = masterLicensesManagerService();
|
|
||||||
final LicensesService licensesService = licensesService();
|
|
||||||
String feature1 = "feature1";
|
|
||||||
String feature2 = "feature2";
|
|
||||||
final TestTrackingClientListener clientListener1 = new TestTrackingClientListener(feature1);
|
|
||||||
final TestTrackingClientListener clientListener2 = new TestTrackingClientListener(feature2);
|
|
||||||
List<Action> firstClientActions = new ArrayList<>();
|
|
||||||
List<Action> secondClientActions = new ArrayList<>();
|
|
||||||
|
|
||||||
final TimeValue firstExpiryDuration = TimeValue.timeValueSeconds(5);
|
|
||||||
firstClientActions.add(registerWithoutTrialLicense(licensesService, clientListener1, feature1));
|
|
||||||
firstClientActions.add(generateAndPutSignedLicenseAction(masterLicensesManagerService, feature1, firstExpiryDuration));
|
|
||||||
firstClientActions.add(assertExpiryAction(feature1, "signed", firstExpiryDuration));
|
|
||||||
|
|
||||||
final TimeValue secondExpiryDuration = TimeValue.timeValueSeconds(4);
|
|
||||||
secondClientActions.add(registerWithoutTrialLicense(licensesService, clientListener2, feature2));
|
|
||||||
secondClientActions.add(generateAndPutSignedLicenseAction(masterLicensesManagerService, feature2, secondExpiryDuration));
|
|
||||||
secondClientActions.add(assertExpiryAction(feature2, "signed", secondExpiryDuration));
|
|
||||||
|
|
||||||
if (randomBoolean()) {
|
|
||||||
assertClientListenerNotificationCount(clientListener1, firstClientActions);
|
|
||||||
assertClientListenerNotificationCount(clientListener2, secondClientActions);
|
|
||||||
} else {
|
|
||||||
assertClientListenerNotificationCount(clientListener2, secondClientActions);
|
|
||||||
assertClientListenerNotificationCount(clientListener1, firstClientActions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMultipleClientTrialAndSignedLicenseEnforcement() throws Exception {
|
|
||||||
// multiple client registration: one with trial license and another with signed license (different expiry duration)
|
|
||||||
|
|
||||||
final LicensesManagerService masterLicensesManagerService = masterLicensesManagerService();
|
|
||||||
final LicensesService licensesService = licensesService();
|
|
||||||
String feature1 = "feature1";
|
|
||||||
String feature2 = "feature2";
|
|
||||||
final TestTrackingClientListener clientListener1 = new TestTrackingClientListener(feature1);
|
|
||||||
final TestTrackingClientListener clientListener2 = new TestTrackingClientListener(feature2);
|
|
||||||
List<Action> firstClientActions = new ArrayList<>();
|
|
||||||
List<Action> secondClientActions = new ArrayList<>();
|
|
||||||
|
|
||||||
final TimeValue firstExpiryDuration = TimeValue.timeValueSeconds(5);
|
|
||||||
firstClientActions.add(registerWithoutTrialLicense(licensesService, clientListener1, feature1));
|
|
||||||
firstClientActions.add(generateAndPutSignedLicenseAction(masterLicensesManagerService, feature1, firstExpiryDuration));
|
|
||||||
firstClientActions.add(assertExpiryAction(feature1, "signed", firstExpiryDuration));
|
|
||||||
|
|
||||||
final TimeValue secondExpiryDuration = TimeValue.timeValueSeconds(4);
|
|
||||||
secondClientActions.add(registerWithTrialLicense(licensesService, clientListener2, feature2, secondExpiryDuration));
|
|
||||||
secondClientActions.add(assertExpiryAction(feature2, "trial", secondExpiryDuration));
|
|
||||||
|
|
||||||
if (randomBoolean()) {
|
|
||||||
assertClientListenerNotificationCount(clientListener1, firstClientActions);
|
|
||||||
assertClientListenerNotificationCount(clientListener2, secondClientActions);
|
|
||||||
} else {
|
|
||||||
assertClientListenerNotificationCount(clientListener2, secondClientActions);
|
|
||||||
assertClientListenerNotificationCount(clientListener1, firstClientActions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMultipleClientTrialLicenseRegistration() throws Exception {
|
|
||||||
// multiple client registration: both with trail license of different expiryDuration
|
|
||||||
|
|
||||||
final LicensesService licensesService = licensesService();
|
|
||||||
String feature1 = "feature1";
|
|
||||||
String feature2 = "feature2";
|
|
||||||
final TestTrackingClientListener clientListener1 = new TestTrackingClientListener(feature1);
|
|
||||||
final TestTrackingClientListener clientListener2 = new TestTrackingClientListener(feature2);
|
|
||||||
List<Action> firstClientActions = new ArrayList<>();
|
|
||||||
List<Action> secondClientActions = new ArrayList<>();
|
|
||||||
|
|
||||||
TimeValue firstExpiryDuration = TimeValue.timeValueSeconds(5);
|
|
||||||
firstClientActions.add(registerWithTrialLicense(licensesService, clientListener1, feature1, firstExpiryDuration));
|
|
||||||
firstClientActions.add(assertExpiryAction(feature1, "trial", firstExpiryDuration));
|
|
||||||
|
|
||||||
TimeValue secondExpiryDuration = TimeValue.timeValueSeconds(4);
|
|
||||||
secondClientActions.add(registerWithTrialLicense(licensesService, clientListener2, feature2, secondExpiryDuration));
|
|
||||||
secondClientActions.add(assertExpiryAction(feature2, "trial", secondExpiryDuration));
|
|
||||||
|
|
||||||
if (randomBoolean()) {
|
|
||||||
assertClientListenerNotificationCount(clientListener1, firstClientActions);
|
|
||||||
assertClientListenerNotificationCount(clientListener2, secondClientActions);
|
|
||||||
} else {
|
|
||||||
assertClientListenerNotificationCount(clientListener2, secondClientActions);
|
|
||||||
assertClientListenerNotificationCount(clientListener1, firstClientActions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFeatureWithoutLicense() throws Exception {
|
|
||||||
// client registration with no trial license + no signed license
|
|
||||||
final LicensesClientService clientService = licensesClientService();
|
|
||||||
String feature = "feature1";
|
|
||||||
final TestTrackingClientListener clientListener = new TestTrackingClientListener(feature);
|
|
||||||
List<Action> actions = new ArrayList<>();
|
|
||||||
|
|
||||||
actions.add(registerWithoutTrialLicense(clientService, clientListener, feature));
|
|
||||||
assertClientListenerNotificationCount(clientListener, actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLicenseExpiry() throws Exception {
|
|
||||||
final LicensesClientService clientService = licensesClientService();
|
|
||||||
String feature = "feature1";
|
|
||||||
final TestTrackingClientListener clientListener = new TestTrackingClientListener(feature);
|
|
||||||
List<Action> actions = new ArrayList<>();
|
|
||||||
|
|
||||||
TimeValue expiryDuration = TimeValue.timeValueSeconds(2);
|
|
||||||
actions.add(registerWithTrialLicense(clientService, clientListener, feature, expiryDuration));
|
|
||||||
actions.add(assertExpiryAction(feature, "trial", expiryDuration));
|
|
||||||
|
|
||||||
assertClientListenerNotificationCount(clientListener, actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRandomActionSequenceMultipleFeature() throws Exception {
|
|
||||||
LicensesService licensesService = licensesService();
|
|
||||||
LicensesManagerService masterLicensesManagerService = masterLicensesManagerService();
|
|
||||||
Map<TestTrackingClientListener, List<Action>> clientListenersWithActions = new HashMap<>();
|
|
||||||
|
|
||||||
TimeValue expiryDuration = TimeValue.timeValueSeconds(0);
|
|
||||||
for (int i = 0; i < randomIntBetween(5, 10); i++) {
|
|
||||||
String feature = "feature_" + String.valueOf(i);
|
|
||||||
final TestTrackingClientListener clientListener = new TestTrackingClientListener(feature);
|
|
||||||
expiryDuration = TimeValue.timeValueMillis(randomIntBetween(2, 4) * 1000l + expiryDuration.millis());
|
|
||||||
List<Action> actions = new ArrayList<>();
|
|
||||||
|
|
||||||
if (randomBoolean()) {
|
|
||||||
actions.add(registerWithTrialLicense(licensesService, clientListener, feature, expiryDuration));
|
|
||||||
actions.add(assertExpiryAction(feature, "trial", expiryDuration));
|
|
||||||
} else {
|
|
||||||
actions.add(registerWithoutTrialLicense(licensesService, clientListener, feature));
|
|
||||||
actions.add(generateAndPutSignedLicenseAction(masterLicensesManagerService, feature, expiryDuration));
|
|
||||||
actions.add(assertExpiryAction(feature, "signed", expiryDuration));
|
|
||||||
}
|
|
||||||
clientListenersWithActions.put(clientListener, actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Map.Entry<TestTrackingClientListener, List<Action>> entry : clientListenersWithActions.entrySet()) {
|
|
||||||
assertClientListenerNotificationCount(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Action generateAndPutSignedLicenseAction(final LicensesManagerService masterLicensesManagerService, final String feature, final TimeValue expiryDuration) throws Exception {
|
|
||||||
return generateAndPutSignedLicenseAction(masterLicensesManagerService, feature, -1, expiryDuration);
|
|
||||||
}
|
|
||||||
private Action generateAndPutSignedLicenseAction(final LicensesManagerService masterLicensesManagerService, final String feature, final long issueDate, final TimeValue expiryDuration) throws Exception {
|
|
||||||
return new Action(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
License license;
|
|
||||||
try {
|
|
||||||
license = generateSignedLicense(feature, issueDate, expiryDuration);
|
|
||||||
} catch (Exception e) {
|
|
||||||
fail(e.getMessage());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
registerAndAckSignedLicenses(masterLicensesManagerService, Arrays.asList(license), LicensesStatus.VALID);
|
|
||||||
}
|
|
||||||
}, 0, (issueDate <= System.currentTimeMillis()) ? 1 : 0, "should trigger onEnable for " + feature + " once [signed license]");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Action registerWithoutTrialLicense(final LicensesClientService clientService, final LicensesClientService.Listener clientListener, final String feature) {
|
|
||||||
return new Action(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
clientService.register(feature, null, Collections.<LicensesService.ExpirationCallback>emptyList(), clientListener);
|
|
||||||
}
|
|
||||||
}, 0, 0, "should not trigger any notification [disabled by default]");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Action assertExpiryAction(String feature, String licenseType, TimeValue expiryDuration) {
|
|
||||||
return new Action(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
}
|
|
||||||
}, 1, 0, TimeValue.timeValueMillis(expiryDuration.getMillis() * 2),
|
|
||||||
"should trigger onDisable for " + feature + " once [" + licenseType + " license expiry]");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertClientListenerNotificationCount(final TestTrackingClientListener clientListener, List<Action> actions) throws Exception {
|
|
||||||
final AtomicInteger expectedEnabledCount = new AtomicInteger(0);
|
|
||||||
final AtomicInteger expectedDisableCount = new AtomicInteger(0);
|
|
||||||
|
|
||||||
for (final Action action : actions) {
|
|
||||||
expectedEnabledCount.addAndGet(action.expectedEnabledCount);
|
|
||||||
expectedDisableCount.addAndGet(action.expectedDisabledCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
final CountDownLatch enableLatch = new CountDownLatch(expectedEnabledCount.get());
|
|
||||||
final CountDownLatch disableLatch = new CountDownLatch(expectedDisableCount.get());
|
|
||||||
final AtomicLong cumulativeTimeoutMillis = new AtomicLong(0);
|
|
||||||
clientListener.latch(enableLatch, disableLatch);
|
|
||||||
for (final Action action : actions) {
|
|
||||||
action.run();
|
|
||||||
cumulativeTimeoutMillis.addAndGet(action.timeout.getMillis());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expectedDisableCount.get() > 0) {
|
|
||||||
assertThat(getActionMsg(true, enableLatch.getCount(), actions), enableLatch.await((cumulativeTimeoutMillis.get() * 2), TimeUnit.MILLISECONDS), equalTo(true));
|
|
||||||
}
|
|
||||||
if (expectedDisableCount.get() > 0) {
|
|
||||||
assertThat(getActionMsg(false, disableLatch.getCount(), actions), disableLatch.await((cumulativeTimeoutMillis.get() * 2), TimeUnit.MILLISECONDS), equalTo(true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getActionMsg(final boolean enabledCount, final long latchCount, final List<Action> actions) {
|
|
||||||
LicensesMetaData licensesMetaData = masterClusterService().state().metaData().custom(LicensesMetaData.TYPE);
|
|
||||||
Set<String> featureLicenses = new HashSet<>();
|
|
||||||
if (licensesMetaData != null) {
|
|
||||||
for (License license : licensesMetaData.getSignedLicenses()) {
|
|
||||||
featureLicenses.add(license.feature());
|
|
||||||
}
|
|
||||||
for (License license : licensesMetaData.getTrialLicenses()) {
|
|
||||||
featureLicenses.add(license.feature());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder featureLicensesString = new StringBuilder();
|
|
||||||
for (String featureLicense : featureLicenses) {
|
|
||||||
if (featureLicensesString.length() != 0) {
|
|
||||||
featureLicensesString.append(", ");
|
|
||||||
}
|
|
||||||
featureLicensesString.append(featureLicense);
|
|
||||||
}
|
|
||||||
|
|
||||||
AtomicLong cumulativeCount = new AtomicLong(0);
|
|
||||||
for (Action action : actions) {
|
|
||||||
cumulativeCount.addAndGet((enabledCount) ? action.expectedEnabledCount : action.expectedDisabledCount);
|
|
||||||
if (latchCount <= cumulativeCount.get()) {
|
|
||||||
return action.msg + "\n Current licenses in cluster state: [" + featureLicensesString.toString() + "]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "there should be no errors";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
|
|
||||||
|
|
||||||
public class LicensesEagerConsumerPluginIntegrationTests extends AbstractLicensesConsumerPluginIntegrationTests {
|
|
||||||
|
|
||||||
public LicensesEagerConsumerPluginIntegrationTests() {
|
|
||||||
super(new EagerLicenseRegistrationConsumerPlugin(Settings.EMPTY));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationConsumerPlugin;
|
|
||||||
|
|
||||||
public class LicensesLazyConsumerPluginIntegrationTests extends AbstractLicensesConsumerPluginIntegrationTests {
|
|
||||||
|
|
||||||
public LicensesLazyConsumerPluginIntegrationTests() {
|
|
||||||
super(new LazyLicenseRegistrationConsumerPlugin(Settings.EMPTY));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,135 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
|
|
||||||
import org.elasticsearch.license.plugin.core.*;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
|
|
||||||
@ClusterScope(scope = TEST, numDataNodes = 10)
|
|
||||||
public class LicensesManagerServiceTests extends AbstractLicensesServiceTests {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testStoreAndGetLicenses() throws Exception {
|
|
||||||
LicensesManagerService licensesManagerService = masterLicensesManagerService();
|
|
||||||
License shieldShortLicense = generateSignedLicense("shield", TimeValue.timeValueHours(1));
|
|
||||||
License shieldLongLicense = generateSignedLicense("shield", TimeValue.timeValueHours(2));
|
|
||||||
License marvelShortLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(1));
|
|
||||||
License marvelLongLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(2));
|
|
||||||
|
|
||||||
List<License> licenses = Arrays.asList(shieldLongLicense, shieldShortLicense, marvelLongLicense, marvelShortLicense);
|
|
||||||
Collections.shuffle(licenses);
|
|
||||||
registerAndAckSignedLicenses(licensesManagerService, licenses, LicensesStatus.VALID);
|
|
||||||
|
|
||||||
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
|
|
||||||
|
|
||||||
// all licenses should be stored in the metaData
|
|
||||||
TestUtils.isSame(licenses, licensesMetaData.getSignedLicenses());
|
|
||||||
|
|
||||||
// only the latest expiry date license for each feature should be returned by getLicenses()
|
|
||||||
final List<License> getLicenses = licensesManagerService.getLicenses();
|
|
||||||
TestUtils.isSame(getLicenses, Arrays.asList(shieldLongLicense, marvelLongLicense));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInvalidLicenseStorage() throws Exception {
|
|
||||||
LicensesManagerService licensesManagerService = masterLicensesManagerService();
|
|
||||||
License signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
|
|
||||||
|
|
||||||
// modify content of signed license
|
|
||||||
License tamperedLicense = License.builder()
|
|
||||||
.fromLicenseSpec(signedLicense, signedLicense.signature())
|
|
||||||
.expiryDate(signedLicense.expiryDate() + 10 * 24 * 60 * 60 * 1000l)
|
|
||||||
.validate()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
registerAndAckSignedLicenses(licensesManagerService, Arrays.asList(tamperedLicense), LicensesStatus.INVALID);
|
|
||||||
|
|
||||||
// ensure that the invalid license never made it to cluster state
|
|
||||||
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
|
|
||||||
if (licensesMetaData != null) {
|
|
||||||
assertThat(licensesMetaData.getSignedLicenses().size(), equalTo(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRemoveLicenses() throws Exception {
|
|
||||||
LicensesManagerService licensesManagerService = masterLicensesManagerService();
|
|
||||||
|
|
||||||
// generate a trial license for one feature
|
|
||||||
final LicensesClientService clientService = licensesClientService();
|
|
||||||
final TestTrackingClientListener clientListener = new TestTrackingClientListener("shield", false);
|
|
||||||
registerWithTrialLicense(clientService, clientListener, "shield", TimeValue.timeValueHours(1)).run();
|
|
||||||
|
|
||||||
// generate signed licenses for multiple features
|
|
||||||
License shieldShortLicense = generateSignedLicense("shield", TimeValue.timeValueHours(1));
|
|
||||||
License shieldLongLicense = generateSignedLicense("shield", TimeValue.timeValueHours(2));
|
|
||||||
License marvelShortLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(1));
|
|
||||||
License marvelLongLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(2));
|
|
||||||
|
|
||||||
List<License> licenses = Arrays.asList(shieldLongLicense, shieldShortLicense, marvelLongLicense, marvelShortLicense);
|
|
||||||
Collections.shuffle(licenses);
|
|
||||||
registerAndAckSignedLicenses(licensesManagerService, licenses, LicensesStatus.VALID);
|
|
||||||
|
|
||||||
// remove license(s) for one feature out of two
|
|
||||||
removeAndAckSignedLicenses(licensesManagerService, Sets.newHashSet("shield"));
|
|
||||||
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
|
|
||||||
TestUtils.isSame(Arrays.asList(marvelLongLicense, marvelShortLicense), licensesMetaData.getSignedLicenses());
|
|
||||||
// check that trial license is not removed
|
|
||||||
assertThat(licensesMetaData.getTrialLicenses().size(), equalTo(1));
|
|
||||||
|
|
||||||
// remove license(s) for all features
|
|
||||||
removeAndAckSignedLicenses(licensesManagerService, Sets.newHashSet("shield", "marvel"));
|
|
||||||
licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
|
|
||||||
assertThat(licensesMetaData.getSignedLicenses().size(), equalTo(0));
|
|
||||||
// check that trial license is not removed
|
|
||||||
assertThat(licensesMetaData.getTrialLicenses().size(), equalTo(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeAndAckSignedLicenses(final LicensesManagerService masterLicensesManagerService, final Set<String> featuresToDelete) {
|
|
||||||
DeleteLicenseRequest deleteLicenseRequest = new DeleteLicenseRequest(featuresToDelete.toArray(new String[featuresToDelete.size()]));
|
|
||||||
LicensesService.DeleteLicenseRequestHolder requestHolder = new LicensesService.DeleteLicenseRequestHolder(deleteLicenseRequest, "test");
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
final AtomicBoolean success = new AtomicBoolean(false);
|
|
||||||
masterLicensesManagerService.removeLicenses(requestHolder, new ActionListener<ClusterStateUpdateResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(ClusterStateUpdateResponse clusterStateUpdateResponse) {
|
|
||||||
if (clusterStateUpdateResponse.isAcknowledged()) {
|
|
||||||
success.set(true);
|
|
||||||
}
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable throwable) {
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
latch.await();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
fail(e.getMessage());
|
|
||||||
}
|
|
||||||
assertThat("remove license(s) failed", success.get(), equalTo(true));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,152 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.common.xcontent.*;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesMetaData;
|
|
||||||
import org.elasticsearch.license.plugin.core.TrialLicenseUtils;
|
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
|
||||||
|
|
||||||
public class LicensesMetaDataSerializationTests extends ElasticsearchTestCase {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testXContentSerializationOneSignedLicense() throws Exception {
|
|
||||||
License license = TestUtils.generateSignedLicense("feature", TimeValue.timeValueHours(2));
|
|
||||||
LicensesMetaData licensesMetaData = new LicensesMetaData(Arrays.asList(license), new ArrayList<License>());
|
|
||||||
XContentBuilder builder = XContentFactory.jsonBuilder();
|
|
||||||
builder.startObject("licensesMetaData");
|
|
||||||
licensesMetaData.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
|
||||||
builder.endObject();
|
|
||||||
byte[] serializedBytes = builder.bytes().toBytes();
|
|
||||||
|
|
||||||
LicensesMetaData licensesMetaDataFromXContent = getLicensesMetaDataFromXContent(serializedBytes);
|
|
||||||
|
|
||||||
assertThat(licensesMetaDataFromXContent.getSignedLicenses().size(), equalTo(1));
|
|
||||||
TestUtils.isSame(licensesMetaDataFromXContent.getSignedLicenses().get(0), license);
|
|
||||||
assertThat(licensesMetaDataFromXContent.getTrialLicenses().size(), equalTo(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testXContentSerializationManySignedLicense() throws Exception {
|
|
||||||
List<License> licenses = new ArrayList<>();
|
|
||||||
int n = randomIntBetween(2, 5);
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
licenses.add(TestUtils.generateSignedLicense("feature__" + String.valueOf(i), TimeValue.timeValueHours(2)));
|
|
||||||
}
|
|
||||||
LicensesMetaData licensesMetaData = new LicensesMetaData(licenses, new ArrayList<License>());
|
|
||||||
XContentBuilder builder = XContentFactory.jsonBuilder();
|
|
||||||
builder.startObject("licensesMetaData");
|
|
||||||
licensesMetaData.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
|
||||||
builder.endObject();
|
|
||||||
byte[] serializedBytes = builder.bytes().toBytes();
|
|
||||||
|
|
||||||
LicensesMetaData licensesMetaDataFromXContent = getLicensesMetaDataFromXContent(serializedBytes);
|
|
||||||
|
|
||||||
assertThat(licensesMetaDataFromXContent.getSignedLicenses().size(), equalTo(n));
|
|
||||||
TestUtils.isSame(licensesMetaDataFromXContent.getSignedLicenses(), licenses);
|
|
||||||
assertThat(licensesMetaDataFromXContent.getTrialLicenses().size(), equalTo(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testXContentSerializationOneTrial() throws Exception {
|
|
||||||
final License trialLicense = TrialLicenseUtils.builder()
|
|
||||||
.feature("feature")
|
|
||||||
.duration(TimeValue.timeValueHours(2))
|
|
||||||
.maxNodes(5)
|
|
||||||
.issuedTo("customer")
|
|
||||||
.issueDate(System.currentTimeMillis())
|
|
||||||
.build();
|
|
||||||
LicensesMetaData licensesMetaData = new LicensesMetaData(new ArrayList<License>(), Arrays.asList(trialLicense));
|
|
||||||
XContentBuilder builder = XContentFactory.jsonBuilder();
|
|
||||||
builder.startObject("licensesMetaData");
|
|
||||||
licensesMetaData.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
|
||||||
builder.endObject();
|
|
||||||
byte[] serializedBytes = builder.bytes().toBytes();
|
|
||||||
|
|
||||||
LicensesMetaData licensesMetaDataFromXContent = getLicensesMetaDataFromXContent(serializedBytes);
|
|
||||||
|
|
||||||
assertThat(licensesMetaDataFromXContent.getTrialLicenses().size(), equalTo(1));
|
|
||||||
TestUtils.isSame(licensesMetaDataFromXContent.getTrialLicenses().iterator().next(), trialLicense);
|
|
||||||
assertThat(licensesMetaDataFromXContent.getSignedLicenses().size(), equalTo(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testXContentSerializationManyTrial() throws Exception {
|
|
||||||
final TrialLicenseUtils.TrialLicenseBuilder trialLicenseBuilder = TrialLicenseUtils.builder()
|
|
||||||
.duration(TimeValue.timeValueHours(2))
|
|
||||||
.maxNodes(5)
|
|
||||||
.issuedTo("customer")
|
|
||||||
.issueDate(System.currentTimeMillis());
|
|
||||||
int n = randomIntBetween(2, 5);
|
|
||||||
List<License> trialLicenses = new ArrayList<>(n);
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
trialLicenses.add(trialLicenseBuilder.feature("feature__" + String.valueOf(i)).build());
|
|
||||||
}
|
|
||||||
LicensesMetaData licensesMetaData = new LicensesMetaData(new ArrayList<License>(), trialLicenses);
|
|
||||||
XContentBuilder builder = XContentFactory.jsonBuilder();
|
|
||||||
builder.startObject("licensesMetaData");
|
|
||||||
licensesMetaData.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
|
||||||
builder.endObject();
|
|
||||||
byte[] serializedBytes = builder.bytes().toBytes();
|
|
||||||
|
|
||||||
LicensesMetaData licensesMetaDataFromXContent = getLicensesMetaDataFromXContent(serializedBytes);
|
|
||||||
|
|
||||||
assertThat(licensesMetaDataFromXContent.getTrialLicenses().size(), equalTo(n));
|
|
||||||
TestUtils.isSame(licensesMetaDataFromXContent.getTrialLicenses(), trialLicenses);
|
|
||||||
assertThat(licensesMetaDataFromXContent.getSignedLicenses().size(), equalTo(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testXContentSerializationManyTrialAndSignedLicenses() throws Exception {
|
|
||||||
final TrialLicenseUtils.TrialLicenseBuilder trialLicenseBuilder = TrialLicenseUtils.builder()
|
|
||||||
.duration(TimeValue.timeValueHours(2))
|
|
||||||
.maxNodes(5)
|
|
||||||
.issuedTo("customer")
|
|
||||||
.issueDate(System.currentTimeMillis());
|
|
||||||
int n = randomIntBetween(2, 5);
|
|
||||||
List<License> trialLicenses = new ArrayList<>(n);
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
trialLicenses.add(trialLicenseBuilder.feature("feature__" + String.valueOf(i)).build());
|
|
||||||
}
|
|
||||||
List<License> licenses = new ArrayList<>();
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
licenses.add(TestUtils.generateSignedLicense("feature__" + String.valueOf(i), TimeValue.timeValueHours(2)));
|
|
||||||
}
|
|
||||||
LicensesMetaData licensesMetaData = new LicensesMetaData(licenses, trialLicenses);
|
|
||||||
XContentBuilder builder = XContentFactory.jsonBuilder();
|
|
||||||
builder.startObject("licensesMetaData");
|
|
||||||
licensesMetaData.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
|
||||||
builder.endObject();
|
|
||||||
byte[] serializedBytes = builder.bytes().toBytes();
|
|
||||||
|
|
||||||
LicensesMetaData licensesMetaDataFromXContent = getLicensesMetaDataFromXContent(serializedBytes);
|
|
||||||
|
|
||||||
assertThat(licensesMetaDataFromXContent.getTrialLicenses().size(), equalTo(n));
|
|
||||||
assertThat(licensesMetaDataFromXContent.getSignedLicenses().size(), equalTo(n));
|
|
||||||
TestUtils.isSame(licensesMetaDataFromXContent.getTrialLicenses(), trialLicenses);
|
|
||||||
TestUtils.isSame(licensesMetaDataFromXContent.getSignedLicenses(), licenses);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static LicensesMetaData getLicensesMetaDataFromXContent(byte[] bytes) throws Exception {
|
|
||||||
final XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(bytes);
|
|
||||||
parser.nextToken(); // consume null
|
|
||||||
parser.nextToken(); // consume "licensesMetaData"
|
|
||||||
LicensesMetaData licensesMetaDataFromXContent = LicensesMetaData.PROTO.fromXContent(parser);
|
|
||||||
parser.nextToken(); // consume endObject
|
|
||||||
assertThat(parser.nextToken(), nullValue());
|
|
||||||
return licensesMetaDataFromXContent;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,150 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.gateway.GatewayService;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationConsumerPlugin;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationPluginService;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
|
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
|
||||||
|
|
||||||
@ClusterScope(scope = TEST, numDataNodes = 10, numClientNodes = 0)
|
|
||||||
public class LicensesPluginIntegrationTests extends AbstractLicensesIntegrationTests {
|
|
||||||
|
|
||||||
private final boolean useEagerLicenseRegistrationPlugin = randomBoolean();
|
|
||||||
|
|
||||||
private final int trialLicenseDurationInSeconds = 10;
|
|
||||||
|
|
||||||
protected Settings nodeSettings(int nodeOrdinal) {
|
|
||||||
return Settings.settingsBuilder()
|
|
||||||
.put(super.nodeSettings(nodeOrdinal))
|
|
||||||
.put(((useEagerLicenseRegistrationPlugin) ? EagerLicenseRegistrationConsumerPlugin.NAME : LazyLicenseRegistrationConsumerPlugin.NAME)
|
|
||||||
+ ".trial_license_duration_in_seconds", trialLicenseDurationInSeconds)
|
|
||||||
.putArray("plugin.types", LicensePlugin.class.getName(),
|
|
||||||
(useEagerLicenseRegistrationPlugin) ? EagerLicenseRegistrationConsumerPlugin.class.getName() : LazyLicenseRegistrationConsumerPlugin.class.getName())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void afterTest() throws Exception {
|
|
||||||
wipeAllLicenses();
|
|
||||||
assertThat(awaitBusy(new Predicate<Object>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(Object o) {
|
|
||||||
return !clusterService().state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK);
|
|
||||||
}
|
|
||||||
}), equalTo(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTrialLicenseAndSignedLicenseNotification() throws Exception {
|
|
||||||
logger.info("using " + ((useEagerLicenseRegistrationPlugin) ? "eager" : "lazy") + " consumer plugin");
|
|
||||||
logger.info(" --> trial license generated");
|
|
||||||
// managerService should report feature to be enabled on all data nodes
|
|
||||||
assertLicenseManagerEnabledFeatureFor(getCurrentFeatureName());
|
|
||||||
// consumer plugin service should return enabled on all data nodes
|
|
||||||
assertConsumerPluginEnabledNotification(2);
|
|
||||||
|
|
||||||
logger.info(" --> check trial license expiry notification");
|
|
||||||
// consumer plugin should notify onDisabled on all data nodes (expired trial license)
|
|
||||||
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(getCurrentFeatureName());
|
|
||||||
|
|
||||||
logger.info(" --> put signed license");
|
|
||||||
putLicense(getCurrentFeatureName(), TimeValue.timeValueSeconds(trialLicenseDurationInSeconds));
|
|
||||||
|
|
||||||
logger.info(" --> check signed license enabled notification");
|
|
||||||
// consumer plugin should notify onEnabled on all data nodes (signed license)
|
|
||||||
assertConsumerPluginEnabledNotification(1);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(getCurrentFeatureName());
|
|
||||||
|
|
||||||
logger.info(" --> check signed license expiry notification");
|
|
||||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
|
||||||
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(getCurrentFeatureName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testTrialLicenseNotification() throws Exception {
|
|
||||||
logger.info(" --> check onEnabled for trial license");
|
|
||||||
// managerService should report feature to be enabled on all data nodes
|
|
||||||
assertLicenseManagerEnabledFeatureFor(getCurrentFeatureName());
|
|
||||||
// consumer plugin service should return enabled on all data nodes
|
|
||||||
assertConsumerPluginEnabledNotification(1);
|
|
||||||
|
|
||||||
logger.info(" --> sleep for rest of trailLicense duration");
|
|
||||||
Thread.sleep(trialLicenseDurationInSeconds * 1000l);
|
|
||||||
|
|
||||||
logger.info(" --> check trial license expiry notification");
|
|
||||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
|
||||||
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(getCurrentFeatureName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOverlappingTrialAndSignedLicenseNotification() throws Exception {
|
|
||||||
logger.info(" --> check onEnabled for trial license");
|
|
||||||
// managerService should report feature to be enabled on all data nodes
|
|
||||||
assertLicenseManagerEnabledFeatureFor(getCurrentFeatureName());
|
|
||||||
// consumer plugin service should return enabled on all data nodes
|
|
||||||
assertConsumerPluginEnabledNotification(1);
|
|
||||||
|
|
||||||
logger.info(" --> put signed license while trial license is in effect");
|
|
||||||
putLicense(getCurrentFeatureName(), TimeValue.timeValueSeconds(trialLicenseDurationInSeconds * 2));
|
|
||||||
|
|
||||||
logger.info(" --> check signed license enabled notification");
|
|
||||||
// consumer plugin should notify onEnabled on all data nodes (signed license)
|
|
||||||
assertConsumerPluginEnabledNotification(1);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(getCurrentFeatureName());
|
|
||||||
|
|
||||||
logger.info(" --> sleep for rest of trailLicense duration");
|
|
||||||
Thread.sleep(trialLicenseDurationInSeconds * 1000l);
|
|
||||||
|
|
||||||
logger.info(" --> check consumer is still enabled [signed license]");
|
|
||||||
// consumer plugin should notify onEnabled on all data nodes (signed license)
|
|
||||||
assertConsumerPluginEnabledNotification(1);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(getCurrentFeatureName());
|
|
||||||
|
|
||||||
logger.info(" --> check signed license expiry notification");
|
|
||||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
|
||||||
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2 * 2);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(getCurrentFeatureName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getCurrentFeatureName() {
|
|
||||||
if (useEagerLicenseRegistrationPlugin) {
|
|
||||||
return EagerLicenseRegistrationPluginService.FEATURE_NAME;
|
|
||||||
} else {
|
|
||||||
return LazyLicenseRegistrationPluginService.FEATURE_NAME;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertConsumerPluginEnabledNotification(int timeoutInSec) throws InterruptedException {
|
|
||||||
if (useEagerLicenseRegistrationPlugin) {
|
|
||||||
assertEagerConsumerPluginEnableNotification(timeoutInSec);
|
|
||||||
} else {
|
|
||||||
assertLazyConsumerPluginEnableNotification(timeoutInSec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertConsumerPluginDisabledNotification(int timeoutInSec) throws InterruptedException {
|
|
||||||
if (useEagerLicenseRegistrationPlugin) {
|
|
||||||
assertEagerConsumerPluginDisableNotification(timeoutInSec);
|
|
||||||
} else {
|
|
||||||
assertLazyConsumerPluginDisableNotification(timeoutInSec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,176 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationConsumerPlugin;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationPluginService;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
|
|
||||||
|
|
||||||
@ClusterScope(scope = TEST, numDataNodes = 0, numClientNodes = 0)
|
|
||||||
public class LicensesPluginsIntegrationTests extends AbstractLicensesIntegrationTests {
|
|
||||||
|
|
||||||
private final String FEATURE_NAME_1 = EagerLicenseRegistrationPluginService.FEATURE_NAME;
|
|
||||||
private final String FEATURE_NAME_2 = LazyLicenseRegistrationPluginService.FEATURE_NAME;
|
|
||||||
|
|
||||||
protected Settings nodeSettings(int nodeOrdinal) {
|
|
||||||
return Settings.settingsBuilder()
|
|
||||||
.put(super.nodeSettings(nodeOrdinal))
|
|
||||||
.putArray("plugin.types", LicensePlugin.class.getName(), EagerLicenseRegistrationConsumerPlugin.class.getName(), LazyLicenseRegistrationConsumerPlugin.class.getName())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Settings nodeSettingsWithConsumerPlugin(int consumer1TrialLicenseDuration, int consumer2TrialLicenseDuration) {
|
|
||||||
return Settings.settingsBuilder()
|
|
||||||
.put(super.nodeSettings(0))
|
|
||||||
.put(EagerLicenseRegistrationConsumerPlugin.NAME + ".trial_license_duration_in_seconds", consumer1TrialLicenseDuration)
|
|
||||||
.put(LazyLicenseRegistrationConsumerPlugin.NAME + ".trial_license_duration_in_seconds", consumer2TrialLicenseDuration)
|
|
||||||
.putArray("plugin.types", LicensePlugin.class.getName(), EagerLicenseRegistrationConsumerPlugin.class.getName(), LazyLicenseRegistrationConsumerPlugin.class.getName())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void afterTest() throws Exception {
|
|
||||||
wipeAllLicenses();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testWithNoTrialLicense() throws Exception {
|
|
||||||
int nNodes = randomIntBetween(2, 10);
|
|
||||||
startNodesWithConsumerPlugins(nNodes, -1, -1);
|
|
||||||
|
|
||||||
assertEagerConsumerPluginDisableNotification(1);
|
|
||||||
assertLazyConsumerPluginDisableNotification(1);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOneTrialAndNonTrialConsumer() throws Exception {
|
|
||||||
int nNodes = randomIntBetween(2, 10);
|
|
||||||
int consumer2TrialLicenseDuration = 5;
|
|
||||||
startNodesWithConsumerPlugins(nNodes, -1, consumer2TrialLicenseDuration);
|
|
||||||
|
|
||||||
logger.info(" --> trial license generated for " + FEATURE_NAME_2 + " no trial license for " + FEATURE_NAME_1);
|
|
||||||
// managerService should report feature to be enabled on all data nodes
|
|
||||||
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_2);
|
|
||||||
// consumer plugin service should return enabled on all data nodes
|
|
||||||
assertEagerConsumerPluginDisableNotification(1);
|
|
||||||
assertLazyConsumerPluginEnableNotification(1);
|
|
||||||
|
|
||||||
logger.info(" --> put signed license for " + FEATURE_NAME_1);
|
|
||||||
putLicense(FEATURE_NAME_1, TimeValue.timeValueSeconds(consumer2TrialLicenseDuration));
|
|
||||||
|
|
||||||
logger.info(" --> check that both " + FEATURE_NAME_1 + " and " + FEATURE_NAME_2 + " are enabled");
|
|
||||||
assertEagerConsumerPluginEnableNotification(1);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_1);
|
|
||||||
|
|
||||||
logger.info(" --> check signed license expiry notification");
|
|
||||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
|
||||||
assertEagerConsumerPluginDisableNotification(consumer2TrialLicenseDuration * 2);
|
|
||||||
assertLazyConsumerPluginDisableNotification(consumer2TrialLicenseDuration * 2);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMultipleConsumerPlugins() throws Exception {
|
|
||||||
|
|
||||||
int nNodes = randomIntBetween(2, 10);
|
|
||||||
int consumer1TrialLicenseExpiry = 5;
|
|
||||||
int consumer2TrialLicenseExpiry = 5;
|
|
||||||
startNodesWithConsumerPlugins(nNodes, consumer1TrialLicenseExpiry, consumer2TrialLicenseExpiry);
|
|
||||||
|
|
||||||
logger.info(" --> trial license generated");
|
|
||||||
// managerService should report feature to be enabled on all data nodes
|
|
||||||
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_1);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_2);
|
|
||||||
// consumer plugin service should return enabled on all data nodes
|
|
||||||
assertEagerConsumerPluginEnableNotification(1);
|
|
||||||
assertLazyConsumerPluginEnableNotification(1);
|
|
||||||
|
|
||||||
logger.info(" --> check trial license expiry notification");
|
|
||||||
// consumer plugin should notify onDisabled on all data nodes (expired trial license)
|
|
||||||
assertEagerConsumerPluginDisableNotification(consumer1TrialLicenseExpiry * 2);
|
|
||||||
assertLazyConsumerPluginDisableNotification(consumer2TrialLicenseExpiry * 2);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_2);
|
|
||||||
|
|
||||||
logger.info(" --> put signed license");
|
|
||||||
putLicense(FEATURE_NAME_1, TimeValue.timeValueSeconds(consumer1TrialLicenseExpiry));
|
|
||||||
putLicense(FEATURE_NAME_2, TimeValue.timeValueSeconds(consumer2TrialLicenseExpiry));
|
|
||||||
|
|
||||||
logger.info(" --> check signed license enabled notification");
|
|
||||||
// consumer plugin should notify onEnabled on all data nodes (signed license)
|
|
||||||
assertEagerConsumerPluginEnableNotification(1);
|
|
||||||
assertLazyConsumerPluginEnableNotification(1);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_1);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_2);
|
|
||||||
|
|
||||||
logger.info(" --> check signed license expiry notification");
|
|
||||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
|
||||||
assertEagerConsumerPluginDisableNotification(consumer1TrialLicenseExpiry * 2);
|
|
||||||
assertLazyConsumerPluginDisableNotification(consumer2TrialLicenseExpiry * 2);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRandomFeatureLicensesActions() throws Exception {
|
|
||||||
int nNodes = randomIntBetween(2, 10);
|
|
||||||
int trialLicenseDuration1 = rarely() ? -1 : randomIntBetween(6, 8);
|
|
||||||
int trialLicenseDuration2 = rarely() ? -1 : randomIntBetween(6, 8);
|
|
||||||
|
|
||||||
startNodesWithConsumerPlugins(nNodes, trialLicenseDuration1, trialLicenseDuration2);
|
|
||||||
|
|
||||||
if (trialLicenseDuration1 != -1) {
|
|
||||||
assertEagerConsumerPluginEnableNotification(trialLicenseDuration1);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_1);
|
|
||||||
} else {
|
|
||||||
assertEagerConsumerPluginDisableNotification(3 * 2);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
|
|
||||||
putLicense(FEATURE_NAME_1, TimeValue.timeValueSeconds(5));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trialLicenseDuration2 != -1) {
|
|
||||||
assertLazyConsumerPluginEnableNotification(trialLicenseDuration2);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_2);
|
|
||||||
} else {
|
|
||||||
assertLazyConsumerPluginDisableNotification(3 * 2);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_2);
|
|
||||||
putLicense(FEATURE_NAME_2, TimeValue.timeValueSeconds(5));
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(" --> check license enabled notification");
|
|
||||||
assertEagerConsumerPluginEnableNotification(1);
|
|
||||||
assertLazyConsumerPluginEnableNotification(1);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_1);
|
|
||||||
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_2);
|
|
||||||
|
|
||||||
logger.info(" --> check license expiry notification");
|
|
||||||
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
|
|
||||||
assertEagerConsumerPluginDisableNotification(8 * 2);
|
|
||||||
assertLazyConsumerPluginDisableNotification(8 * 2);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
|
|
||||||
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_2);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startNodesWithConsumerPlugins(int nNodes, int consumer1TrialLicenseDuration, int consumer2TrialLicenseDuration) {
|
|
||||||
for (int i = 0; i < nNodes; i++) {
|
|
||||||
internalCluster().startNode(nodeSettingsWithConsumerPlugin(consumer1TrialLicenseDuration, consumer2TrialLicenseDuration));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,172 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import org.elasticsearch.client.ClusterAdminClient;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.plugin.action.get.GetLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.action.get.GetLicenseRequestBuilder;
|
|
||||||
import org.elasticsearch.license.plugin.action.get.GetLicenseResponse;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationConsumerPlugin;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationPluginService;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesMetaData;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesStatus;
|
|
||||||
import org.elasticsearch.node.Node;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
|
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
|
||||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
|
||||||
|
|
||||||
@ClusterScope(scope = TEST, numDataNodes = 0, numClientNodes = 0, maxNumDataNodes = 0, transportClientRatio = 0)
|
|
||||||
public class LicensesServiceClusterTest extends AbstractLicensesIntegrationTests {
|
|
||||||
|
|
||||||
private final String[] FEATURES = {EagerLicenseRegistrationPluginService.FEATURE_NAME, LazyLicenseRegistrationPluginService.FEATURE_NAME};
|
|
||||||
|
|
||||||
protected Settings transportClientSettings() {
|
|
||||||
return super.transportClientSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Settings nodeSettings(int nodeOrdinal) {
|
|
||||||
return nodeSettingsBuilder(nodeOrdinal).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Settings.Builder nodeSettingsBuilder(int nodeOrdinal) {
|
|
||||||
return Settings.builder()
|
|
||||||
.put(super.nodeSettings(nodeOrdinal))
|
|
||||||
.put("gateway.type", "local")
|
|
||||||
.put("plugins.load_classpath_plugins", false)
|
|
||||||
.put("node.data", true)
|
|
||||||
.put("format", "json")
|
|
||||||
.put(EagerLicenseRegistrationConsumerPlugin.NAME + ".trial_license_duration_in_seconds", 2)
|
|
||||||
.put(LazyLicenseRegistrationConsumerPlugin.NAME + ".trial_license_duration_in_seconds", 2)
|
|
||||||
.putArray("plugin.types", LicensePlugin.class.getName(), EagerLicenseRegistrationConsumerPlugin.class.getName(), LazyLicenseRegistrationConsumerPlugin.class.getName())
|
|
||||||
.put(Node.HTTP_ENABLED, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testClusterRestart() throws Exception {
|
|
||||||
wipeAllLicenses();
|
|
||||||
|
|
||||||
int numNodes = randomIntBetween(1, 5);
|
|
||||||
logger.info("--> starting " + numNodes + " node(s)");
|
|
||||||
for (int i = 0; i < numNodes; i++) {
|
|
||||||
internalCluster().startNode();
|
|
||||||
}
|
|
||||||
ensureGreen();
|
|
||||||
|
|
||||||
logger.info("--> put signed license");
|
|
||||||
final List<License> licenses = generateAndPutLicenses();
|
|
||||||
getAndCheckLicense(licenses);
|
|
||||||
logger.info("--> restart all nodes");
|
|
||||||
internalCluster().fullRestart();
|
|
||||||
ensureYellow();
|
|
||||||
|
|
||||||
logger.info("--> get and check signed license");
|
|
||||||
getAndCheckLicense(licenses);
|
|
||||||
|
|
||||||
wipeAllLicenses();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testClusterNotRecovered() throws Exception {
|
|
||||||
logger.info("--> start one master out of two [recovery state]");
|
|
||||||
internalCluster().startNode(nodeSettingsBuilder(0).put("discovery.zen.minimum_master_nodes", 2).put("node.master", true));
|
|
||||||
// license plugin should not be active when cluster is still recovering
|
|
||||||
assertLicenseManagerFeatureDisabled();
|
|
||||||
assertConsumerPluginDisabledNotification(1);
|
|
||||||
|
|
||||||
logger.info("--> start second master out of two [recovered state]");
|
|
||||||
internalCluster().startNode(nodeSettingsBuilder(1).put("discovery.zen.minimum_master_nodes", 2).put("node.master", true));
|
|
||||||
assertLicenseManagerFeatureEnabled();
|
|
||||||
assertConsumerPluginEnabledNotification(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAtMostOnceTrialLicenseGeneration() throws Exception {
|
|
||||||
wipeAllLicenses();
|
|
||||||
logger.info("--> start one node [trial license should be generated & enabled]");
|
|
||||||
internalCluster().startNode(nodeSettingsBuilder(0));
|
|
||||||
assertLicenseManagerFeatureEnabled();
|
|
||||||
assertConsumerPluginEnabledNotification(1);
|
|
||||||
|
|
||||||
logger.info("--> start another node [trial license should be propagated from the old master not generated]");
|
|
||||||
internalCluster().startNode(nodeSettings(1));
|
|
||||||
assertLicenseManagerFeatureEnabled();
|
|
||||||
assertConsumerPluginEnabledNotification(1);
|
|
||||||
|
|
||||||
logger.info("--> check if multiple trial licenses are found for a feature");
|
|
||||||
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
|
|
||||||
assertThat(licensesMetaData.getTrialLicenses().size(), equalTo(FEATURES.length));
|
|
||||||
|
|
||||||
wipeAllLicenses();
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<License> generateAndPutLicenses() throws Exception {
|
|
||||||
ClusterAdminClient cluster = internalCluster().client().admin().cluster();
|
|
||||||
List<License> putLicenses = new ArrayList<>(FEATURES.length);
|
|
||||||
for (String feature : FEATURES) {
|
|
||||||
putLicenses.add(generateSignedLicense(feature, TimeValue.timeValueMinutes(1)));
|
|
||||||
}
|
|
||||||
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(cluster, PutLicenseAction.INSTANCE);
|
|
||||||
putLicenseRequestBuilder.setLicense(putLicenses);
|
|
||||||
ensureGreen();
|
|
||||||
|
|
||||||
final PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
|
|
||||||
|
|
||||||
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
|
|
||||||
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
|
|
||||||
|
|
||||||
return putLicenses;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void getAndCheckLicense(List<License> licenses) {
|
|
||||||
ClusterAdminClient cluster = internalCluster().client().admin().cluster();
|
|
||||||
final GetLicenseResponse response = new GetLicenseRequestBuilder(cluster, GetLicenseAction.INSTANCE).get();
|
|
||||||
assertThat(response.licenses().size(), equalTo(licenses.size()));
|
|
||||||
TestUtils.isSame(licenses, response.licenses());
|
|
||||||
|
|
||||||
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
|
|
||||||
assertThat(licensesMetaData, notNullValue());
|
|
||||||
assertThat(licensesMetaData.getTrialLicenses().size(), equalTo(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertLicenseManagerFeatureEnabled() throws Exception {
|
|
||||||
for (String feature : FEATURES) {
|
|
||||||
assertLicenseManagerEnabledFeatureFor(feature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertLicenseManagerFeatureDisabled() throws Exception {
|
|
||||||
for (String feature : FEATURES) {
|
|
||||||
assertLicenseManagerDisabledFeatureFor(feature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertConsumerPluginEnabledNotification(int timeoutInSec) throws InterruptedException {
|
|
||||||
assertEagerConsumerPluginEnableNotification(timeoutInSec);
|
|
||||||
assertLazyConsumerPluginEnableNotification(timeoutInSec);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertConsumerPluginDisabledNotification(int timeoutInSec) throws InterruptedException {
|
|
||||||
assertEagerConsumerPluginDisableNotification(timeoutInSec);
|
|
||||||
assertLazyConsumerPluginDisableNotification(timeoutInSec);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
|
|
||||||
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
|
|
||||||
import org.elasticsearch.node.Node;
|
|
||||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
|
||||||
import org.elasticsearch.test.junit.annotations.TestLogging;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
@ElasticsearchIntegrationTest.ClusterScope(scope = TEST, numDataNodes = 10, numClientNodes = 0)
|
|
||||||
public class LicensesServiceNodeTests extends AbstractLicensesIntegrationTests {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Settings nodeSettings(int nodeOrdinal) {
|
|
||||||
return Settings.settingsBuilder()
|
|
||||||
.put(super.nodeSettings(nodeOrdinal))
|
|
||||||
.put(EagerLicenseRegistrationConsumerPlugin.NAME + ".trial_license_duration_in_seconds", 60 * 5)
|
|
||||||
.putArray("plugin.types", LicensePlugin.class.getName(), EagerLicenseRegistrationConsumerPlugin.class.getName())
|
|
||||||
.put(Node.HTTP_ENABLED, true)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@TestLogging("_root:DEBUG")
|
|
||||||
public void testPluginStatus() throws Exception {
|
|
||||||
final Iterable<EagerLicenseRegistrationPluginService> testPluginServices = internalCluster().getDataNodeInstances(EagerLicenseRegistrationPluginService.class);
|
|
||||||
assertThat(awaitBusy(new Predicate<Object>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(Object o) {
|
|
||||||
for (EagerLicenseRegistrationPluginService pluginService : testPluginServices) {
|
|
||||||
if (!pluginService.enabled()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}, 10, TimeUnit.SECONDS), equalTo(true));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,265 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import org.elasticsearch.action.ActionFuture;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequestBuilder;
|
|
||||||
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseResponse;
|
|
||||||
import org.elasticsearch.license.plugin.action.get.GetLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.action.get.GetLicenseRequestBuilder;
|
|
||||||
import org.elasticsearch.license.plugin.action.get.GetLicenseResponse;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseAction;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
|
|
||||||
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesStatus;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
|
|
||||||
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
|
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
|
||||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
|
||||||
|
|
||||||
@ClusterScope(scope = TEST, numDataNodes = 10)
|
|
||||||
public class LicensesTransportTests extends AbstractLicensesIntegrationTests {
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void beforeTest() throws Exception {
|
|
||||||
wipeAllLicenses();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testEmptyGetLicense() throws Exception {
|
|
||||||
final ActionFuture<GetLicenseResponse> getLicenseFuture = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).execute();
|
|
||||||
final GetLicenseResponse getLicenseResponse = getLicenseFuture.get();
|
|
||||||
assertThat("expected 0 licenses; but got: " + getLicenseResponse.licenses().size(), getLicenseResponse.licenses().size(), equalTo(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPutLicense() throws Exception {
|
|
||||||
License signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
|
|
||||||
List<License> actualLicenses = Collections.singletonList(signedLicense);
|
|
||||||
|
|
||||||
// put license
|
|
||||||
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE)
|
|
||||||
.setLicense(actualLicenses);
|
|
||||||
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
|
|
||||||
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
|
|
||||||
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
|
|
||||||
|
|
||||||
// get license
|
|
||||||
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
|
|
||||||
assertThat(getLicenseResponse.licenses(), notNullValue());
|
|
||||||
assertThat(getLicenseResponse.licenses().size(), equalTo(1));
|
|
||||||
|
|
||||||
// check license
|
|
||||||
TestUtils.isSame(signedLicense, getLicenseResponse.licenses().get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPutLicenseFromString() throws Exception {
|
|
||||||
License signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
|
|
||||||
String licenseString = TestUtils.dumpLicense(signedLicense);
|
|
||||||
|
|
||||||
// put license source
|
|
||||||
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE)
|
|
||||||
.setLicense(licenseString);
|
|
||||||
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
|
|
||||||
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
|
|
||||||
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
|
|
||||||
|
|
||||||
// get license
|
|
||||||
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
|
|
||||||
assertThat(getLicenseResponse.licenses(), notNullValue());
|
|
||||||
assertThat(getLicenseResponse.licenses().size(), equalTo(1));
|
|
||||||
|
|
||||||
// check license
|
|
||||||
TestUtils.isSame(signedLicense, getLicenseResponse.licenses().get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPutInvalidLicense() throws Exception {
|
|
||||||
License signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
|
|
||||||
|
|
||||||
// modify content of signed license
|
|
||||||
License tamperedLicense = License.builder()
|
|
||||||
.fromLicenseSpec(signedLicense, signedLicense.signature())
|
|
||||||
.expiryDate(signedLicense.expiryDate() + 10 * 24 * 60 * 60 * 1000l)
|
|
||||||
.validate()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
PutLicenseRequestBuilder builder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE);
|
|
||||||
builder.setLicense(Collections.singletonList(tamperedLicense));
|
|
||||||
|
|
||||||
// try to put license (should be invalid)
|
|
||||||
final PutLicenseResponse putLicenseResponse = builder.get();
|
|
||||||
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.INVALID));
|
|
||||||
|
|
||||||
// try to get invalid license
|
|
||||||
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
|
|
||||||
assertThat(getLicenseResponse.licenses().size(), equalTo(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPutExpiredLicense() throws Exception {
|
|
||||||
License expiredLicense = generateSignedLicense("feature", dateMath("now-10d/d", System.currentTimeMillis()), TimeValue.timeValueMinutes(2));
|
|
||||||
License signedLicense = generateSignedLicense("feature", TimeValue.timeValueMinutes(2));
|
|
||||||
|
|
||||||
PutLicenseRequestBuilder builder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE);
|
|
||||||
builder.setLicense(Arrays.asList(signedLicense, expiredLicense));
|
|
||||||
|
|
||||||
// put license should return valid (as there is one valid license)
|
|
||||||
final PutLicenseResponse putLicenseResponse = builder.get();
|
|
||||||
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
|
|
||||||
|
|
||||||
// get license should not return the expired license
|
|
||||||
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
|
|
||||||
assertThat(getLicenseResponse.licenses().size(), equalTo(1));
|
|
||||||
|
|
||||||
TestUtils.isSame(getLicenseResponse.licenses().get(0), signedLicense);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPutLicensesForSameFeature() throws Exception {
|
|
||||||
License shortedSignedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
|
|
||||||
License longerSignedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(5));
|
|
||||||
List<License> actualLicenses = Arrays.asList(longerSignedLicense, shortedSignedLicense);
|
|
||||||
|
|
||||||
// put license
|
|
||||||
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE)
|
|
||||||
.setLicense(actualLicenses);
|
|
||||||
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
|
|
||||||
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
|
|
||||||
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
|
|
||||||
|
|
||||||
// get should return only one license (with longer expiry date)
|
|
||||||
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
|
|
||||||
assertThat(getLicenseResponse.licenses(), notNullValue());
|
|
||||||
assertThat(getLicenseResponse.licenses().size(), equalTo(1));
|
|
||||||
|
|
||||||
// check license
|
|
||||||
TestUtils.isSame(longerSignedLicense, getLicenseResponse.licenses().get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPutLicensesForMultipleFeatures() throws Exception {
|
|
||||||
License shieldLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
|
|
||||||
License marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5));
|
|
||||||
List<License> actualLicenses = Arrays.asList(marvelLicense, shieldLicense);
|
|
||||||
|
|
||||||
// put license
|
|
||||||
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE)
|
|
||||||
.setLicense(actualLicenses);
|
|
||||||
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
|
|
||||||
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
|
|
||||||
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
|
|
||||||
|
|
||||||
// get should return both the licenses
|
|
||||||
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
|
|
||||||
assertThat(getLicenseResponse.licenses(), notNullValue());
|
|
||||||
|
|
||||||
// check license
|
|
||||||
TestUtils.isSame(actualLicenses, getLicenseResponse.licenses());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testPutMultipleLicensesForMultipleFeatures() throws Exception {
|
|
||||||
License shortedSignedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
|
|
||||||
License longerSignedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(5));
|
|
||||||
License marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5));
|
|
||||||
List<License> actualLicenses = Arrays.asList(marvelLicense, shortedSignedLicense, longerSignedLicense);
|
|
||||||
|
|
||||||
// put license
|
|
||||||
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE)
|
|
||||||
.setLicense(actualLicenses);
|
|
||||||
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
|
|
||||||
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
|
|
||||||
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
|
|
||||||
|
|
||||||
// get should return both the licenses
|
|
||||||
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
|
|
||||||
assertThat(getLicenseResponse.licenses(), notNullValue());
|
|
||||||
assertThat(getLicenseResponse.licenses().size(), equalTo(2));
|
|
||||||
|
|
||||||
// check license (should get the longest expiry time for all unique features)
|
|
||||||
TestUtils.isSame(Arrays.asList(marvelLicense, longerSignedLicense), getLicenseResponse.licenses());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRemoveLicenseSimple() throws Exception {
|
|
||||||
License shieldLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
|
|
||||||
License marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5));
|
|
||||||
List<License> actualLicenses = Arrays.asList(marvelLicense, shieldLicense);
|
|
||||||
|
|
||||||
// put two licenses
|
|
||||||
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE)
|
|
||||||
.setLicense(actualLicenses);
|
|
||||||
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
|
|
||||||
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
|
|
||||||
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
|
|
||||||
|
|
||||||
// get and check licenses
|
|
||||||
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
|
|
||||||
assertThat(getLicenseResponse.licenses(), notNullValue());
|
|
||||||
assertThat(getLicenseResponse.licenses().size(), equalTo(2));
|
|
||||||
|
|
||||||
// delete all licenses
|
|
||||||
DeleteLicenseRequestBuilder deleteLicenseRequestBuilder = new DeleteLicenseRequestBuilder(client().admin().cluster(), DeleteLicenseAction.INSTANCE)
|
|
||||||
.setFeatures(Sets.newHashSet("shield", "marvel"));
|
|
||||||
DeleteLicenseResponse deleteLicenseResponse = deleteLicenseRequestBuilder.get();
|
|
||||||
assertThat(deleteLicenseResponse.isAcknowledged(), equalTo(true));
|
|
||||||
|
|
||||||
// get licenses (expected no licenses)
|
|
||||||
getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
|
|
||||||
assertThat(getLicenseResponse.licenses(), notNullValue());
|
|
||||||
assertThat(getLicenseResponse.licenses().size(), equalTo(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRemoveLicenses() throws Exception {
|
|
||||||
License shieldLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
|
|
||||||
License marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5));
|
|
||||||
List<License> actualLicenses = Arrays.asList(marvelLicense, shieldLicense);
|
|
||||||
|
|
||||||
// put two licenses
|
|
||||||
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE)
|
|
||||||
.setLicense(actualLicenses);
|
|
||||||
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
|
|
||||||
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
|
|
||||||
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
|
|
||||||
|
|
||||||
// delete one license
|
|
||||||
DeleteLicenseRequestBuilder deleteLicenseRequestBuilder = new DeleteLicenseRequestBuilder(client().admin().cluster(), DeleteLicenseAction.INSTANCE)
|
|
||||||
.setFeatures(Sets.newHashSet("shield"));
|
|
||||||
DeleteLicenseResponse deleteLicenseResponse = deleteLicenseRequestBuilder.get();
|
|
||||||
assertThat(deleteLicenseResponse.isAcknowledged(), equalTo(true));
|
|
||||||
|
|
||||||
// check other license
|
|
||||||
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
|
|
||||||
assertThat(getLicenseResponse.licenses(), notNullValue());
|
|
||||||
assertThat(getLicenseResponse.licenses().size(), equalTo(1));
|
|
||||||
|
|
||||||
// delete another license
|
|
||||||
deleteLicenseRequestBuilder = new DeleteLicenseRequestBuilder(client().admin().cluster(), DeleteLicenseAction.INSTANCE)
|
|
||||||
.setFeatures(Sets.newHashSet("marvel"));
|
|
||||||
deleteLicenseResponse = deleteLicenseRequestBuilder.get();
|
|
||||||
assertThat(deleteLicenseResponse.isAcknowledged(), equalTo(true));
|
|
||||||
|
|
||||||
// check no license
|
|
||||||
getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
|
|
||||||
assertThat(getLicenseResponse.licenses(), notNullValue());
|
|
||||||
assertThat(getLicenseResponse.licenses().size(), equalTo(0));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.core.Licenses;
|
|
||||||
import org.elasticsearch.license.licensor.LicenseSigner;
|
|
||||||
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import static org.hamcrest.core.IsEqual.equalTo;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
public class TestUtils {
|
|
||||||
|
|
||||||
public static String getTestPriKeyPath() throws Exception {
|
|
||||||
return getResourcePath("/private.key");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getTestPubKeyPath() throws Exception {
|
|
||||||
return getResourcePath("/public.key");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void isSame(Collection<License> firstLicenses, Collection<License> secondLicenses) {
|
|
||||||
isSame(new HashSet<>(firstLicenses), new HashSet<>(secondLicenses));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void isSame(Set<License> firstLicenses, Set<License> secondLicenses) {
|
|
||||||
|
|
||||||
// we do the verifyAndBuild to make sure we weed out any expired licenses
|
|
||||||
final Map<String, License> licenses1 = Licenses.reduceAndMap(firstLicenses);
|
|
||||||
final Map<String, License> licenses2 = Licenses.reduceAndMap(secondLicenses);
|
|
||||||
|
|
||||||
// check if the effective licenses have the same feature set
|
|
||||||
assertThat(licenses1.size(), equalTo(licenses2.size()));
|
|
||||||
|
|
||||||
// for every feature license, check if all the attributes are the same
|
|
||||||
for (String featureType : licenses1.keySet()) {
|
|
||||||
License license1 = licenses1.get(featureType);
|
|
||||||
License license2 = licenses2.get(featureType);
|
|
||||||
isSame(license1, license2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void isSame(License license1, License license2) {
|
|
||||||
assertThat(license1.uid(), equalTo(license2.uid()));
|
|
||||||
assertThat(license1.feature(), equalTo(license2.feature()));
|
|
||||||
assertThat(license1.subscriptionType(), equalTo(license2.subscriptionType()));
|
|
||||||
assertThat(license1.type(), equalTo(license2.type()));
|
|
||||||
assertThat(license1.issuedTo(), equalTo(license2.issuedTo()));
|
|
||||||
assertThat(license1.signature(), equalTo(license2.signature()));
|
|
||||||
assertThat(license1.expiryDate(), equalTo(license2.expiryDate()));
|
|
||||||
assertThat(license1.issueDate(), equalTo(license2.issueDate()));
|
|
||||||
assertThat(license1.maxNodes(), equalTo(license2.maxNodes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String dumpLicense(License license) throws Exception {
|
|
||||||
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
|
|
||||||
Licenses.toXContent(Collections.singletonList(license), builder, ToXContent.EMPTY_PARAMS);
|
|
||||||
builder.flush();
|
|
||||||
return builder.string();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static License generateSignedLicense(String feature, TimeValue expiryDuration) throws Exception {
|
|
||||||
return generateSignedLicense(feature, -1, expiryDuration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static License generateSignedLicense(String feature, long issueDate, TimeValue expiryDuration) throws Exception {
|
|
||||||
long issue = (issueDate != -1l) ? issueDate : System.currentTimeMillis();
|
|
||||||
final License licenseSpec = License.builder()
|
|
||||||
.uid(UUID.randomUUID().toString())
|
|
||||||
.feature(feature)
|
|
||||||
.expiryDate(issue + expiryDuration.getMillis())
|
|
||||||
.issueDate(issue)
|
|
||||||
.type("subscription")
|
|
||||||
.subscriptionType("gold")
|
|
||||||
.issuedTo("customer")
|
|
||||||
.issuer("elasticsearch")
|
|
||||||
.maxNodes(5)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
LicenseSigner signer = new LicenseSigner(getTestPriKeyPath(), getTestPubKeyPath());
|
|
||||||
return signer.sign(licenseSpec);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getResourcePath(String resource) throws Exception {
|
|
||||||
URL url = TestUtils.class.getResource(resource);
|
|
||||||
return url.toURI().getPath();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.plugin.core.TrialLicenseUtils;
|
|
||||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
public class TrailLicenseSerializationTests extends ElasticsearchTestCase {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSerialization() throws Exception {
|
|
||||||
final TrialLicenseUtils.TrialLicenseBuilder trialLicenseBuilder = TrialLicenseUtils.builder()
|
|
||||||
.duration(TimeValue.timeValueHours(2))
|
|
||||||
.maxNodes(5)
|
|
||||||
.issuedTo("customer")
|
|
||||||
.issueDate(System.currentTimeMillis());
|
|
||||||
int n = randomIntBetween(2, 5);
|
|
||||||
List<License> trialLicenses = new ArrayList<>(n);
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
trialLicenses.add(trialLicenseBuilder.feature("feature__" + String.valueOf(i)).build());
|
|
||||||
}
|
|
||||||
for (License trialLicense : trialLicenses) {
|
|
||||||
String encodedTrialLicense = TrialLicenseUtils.toEncodedTrialLicense(trialLicense);
|
|
||||||
final License fromEncodedTrialLicense = TrialLicenseUtils.fromEncodedTrialLicense(encodedTrialLicense);
|
|
||||||
TestUtils.isSame(fromEncodedTrialLicense, trialLicense);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.consumer;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers licenses upon the start of the service lifecycle
|
|
||||||
* see {@link EagerLicenseRegistrationPluginService}
|
|
||||||
* <p/>
|
|
||||||
* License registration might happen before clusterService start()
|
|
||||||
*/
|
|
||||||
public class EagerLicenseRegistrationConsumerPlugin extends TestConsumerPluginBase {
|
|
||||||
|
|
||||||
public final static String NAME = "test_consumer_plugin_1";
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public EagerLicenseRegistrationConsumerPlugin(Settings settings) {
|
|
||||||
super(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<? extends TestPluginServiceBase> service() {
|
|
||||||
return EagerLicenseRegistrationPluginService.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String pluginName() {
|
|
||||||
return NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String featureName() {
|
|
||||||
return EagerLicenseRegistrationPluginService.FEATURE_NAME;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.consumer;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.inject.Singleton;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesClientService;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class EagerLicenseRegistrationPluginService extends TestPluginServiceBase {
|
|
||||||
|
|
||||||
public static String FEATURE_NAME = "feature1";
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public EagerLicenseRegistrationPluginService(Settings settings, LicensesClientService licensesClientService) {
|
|
||||||
super(true, settings, licensesClientService, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String featureName() {
|
|
||||||
return FEATURE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String settingPrefix() {
|
|
||||||
return EagerLicenseRegistrationConsumerPlugin.NAME;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.consumer;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers licenses only after cluster has recovered
|
|
||||||
* see {@link LazyLicenseRegistrationPluginService}
|
|
||||||
* <p/>
|
|
||||||
* License registration happens after clusterservice start()
|
|
||||||
*/
|
|
||||||
public class LazyLicenseRegistrationConsumerPlugin extends TestConsumerPluginBase {
|
|
||||||
|
|
||||||
public static String NAME = "test_consumer_plugin_2";
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public LazyLicenseRegistrationConsumerPlugin(Settings settings) {
|
|
||||||
super(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<? extends TestPluginServiceBase> service() {
|
|
||||||
return LazyLicenseRegistrationPluginService.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String pluginName() {
|
|
||||||
return NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String featureName() {
|
|
||||||
return LazyLicenseRegistrationPluginService.FEATURE_NAME;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.consumer;
|
|
||||||
|
|
||||||
import org.elasticsearch.cluster.ClusterService;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.inject.Singleton;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesClientService;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
public class LazyLicenseRegistrationPluginService extends TestPluginServiceBase {
|
|
||||||
|
|
||||||
|
|
||||||
public static String FEATURE_NAME = "feature2";
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public LazyLicenseRegistrationPluginService(Settings settings, LicensesClientService licensesClientService, ClusterService clusterService) {
|
|
||||||
super(false, settings, licensesClientService, clusterService);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String featureName() {
|
|
||||||
return FEATURE_NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String settingPrefix() {
|
|
||||||
return LazyLicenseRegistrationConsumerPlugin.NAME;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.consumer;
|
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import org.elasticsearch.client.Client;
|
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
|
||||||
import org.elasticsearch.common.component.LifecycleComponent;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.plugins.AbstractPlugin;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
public abstract class TestConsumerPluginBase extends AbstractPlugin {
|
|
||||||
|
|
||||||
private final boolean isEnabled;
|
|
||||||
|
|
||||||
public TestConsumerPluginBase(Settings settings) {
|
|
||||||
if (DiscoveryNode.clientNode(settings)) {
|
|
||||||
// Enable plugin only on node clients
|
|
||||||
this.isEnabled = "node".equals(settings.get(Client.CLIENT_TYPE_SETTING));
|
|
||||||
} else {
|
|
||||||
this.isEnabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String name() {
|
|
||||||
return pluginName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String description() {
|
|
||||||
return "test licensing consumer plugin";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Class<? extends LifecycleComponent>> services() {
|
|
||||||
Collection<Class<? extends LifecycleComponent>> services = Lists.newArrayList();
|
|
||||||
if (isEnabled) {
|
|
||||||
services.add(service());
|
|
||||||
}
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract Class<? extends TestPluginServiceBase> service();
|
|
||||||
|
|
||||||
protected abstract String pluginName();
|
|
||||||
|
|
||||||
public abstract String featureName();
|
|
||||||
}
|
|
|
@ -1,147 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.consumer;
|
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
|
||||||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
|
||||||
import org.elasticsearch.cluster.ClusterService;
|
|
||||||
import org.elasticsearch.cluster.ClusterStateListener;
|
|
||||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.gateway.GatewayService;
|
|
||||||
import org.elasticsearch.license.core.License;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesClientService;
|
|
||||||
import org.elasticsearch.license.plugin.core.LicensesService;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
public abstract class TestPluginServiceBase extends AbstractLifecycleComponent<TestPluginServiceBase> implements ClusterStateListener {
|
|
||||||
|
|
||||||
private LicensesClientService licensesClientService;
|
|
||||||
|
|
||||||
private final ClusterService clusterService;
|
|
||||||
// specify the trial license spec for the feature
|
|
||||||
// example: 30 day trial on 1000 nodes
|
|
||||||
final LicensesService.TrialLicenseOptions trialLicenseOptions;
|
|
||||||
|
|
||||||
final boolean eagerLicenseRegistration;
|
|
||||||
|
|
||||||
final static Collection<LicensesService.ExpirationCallback> expirationCallbacks;
|
|
||||||
|
|
||||||
static {
|
|
||||||
// Callback triggered every 24 hours from 30 days to 7 days of license expiry
|
|
||||||
final LicensesService.ExpirationCallback.Pre LEVEL_1 = new LicensesService.ExpirationCallback.Pre(TimeValue.timeValueHours(7 * 24), TimeValue.timeValueHours(30 * 24), TimeValue.timeValueHours(24)) {
|
|
||||||
@Override
|
|
||||||
public void on(License license, LicensesService.ExpirationStatus status) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Callback triggered every 10 minutes from 7 days to license expiry
|
|
||||||
final LicensesService.ExpirationCallback.Pre LEVEL_2 = new LicensesService.ExpirationCallback.Pre(null, TimeValue.timeValueHours(7 * 24), TimeValue.timeValueMinutes(10)) {
|
|
||||||
@Override
|
|
||||||
public void on(License license, LicensesService.ExpirationStatus status) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Callback triggered every 10 minutes after license expiry
|
|
||||||
final LicensesService.ExpirationCallback.Post LEVEL_3 = new LicensesService.ExpirationCallback.Post(TimeValue.timeValueMillis(0), null, TimeValue.timeValueMinutes(10)) {
|
|
||||||
@Override
|
|
||||||
public void on(License license, LicensesService.ExpirationStatus status) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
expirationCallbacks = new ArrayList<>();
|
|
||||||
expirationCallbacks.add(LEVEL_1);
|
|
||||||
expirationCallbacks.add(LEVEL_2);
|
|
||||||
expirationCallbacks.add(LEVEL_3);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public final AtomicBoolean registered = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
private volatile AtomicBoolean enabled = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
public TestPluginServiceBase(boolean eagerLicenseRegistration, Settings settings, LicensesClientService licensesClientService, ClusterService clusterService) {
|
|
||||||
super(settings);
|
|
||||||
this.eagerLicenseRegistration = eagerLicenseRegistration;
|
|
||||||
this.licensesClientService = licensesClientService;
|
|
||||||
int durationInSec = settings.getAsInt(settingPrefix() + ".trial_license_duration_in_seconds", -1);
|
|
||||||
if (durationInSec == -1) {
|
|
||||||
this.trialLicenseOptions = null;
|
|
||||||
} else {
|
|
||||||
this.trialLicenseOptions = new LicensesService.TrialLicenseOptions(TimeValue.timeValueSeconds(durationInSec), 1000);
|
|
||||||
}
|
|
||||||
if (!eagerLicenseRegistration) {
|
|
||||||
this.clusterService = clusterService;
|
|
||||||
clusterService.add(this);
|
|
||||||
} else {
|
|
||||||
this.clusterService = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// should be the same string used by the license Manger to generate
|
|
||||||
// signed license
|
|
||||||
public abstract String featureName();
|
|
||||||
|
|
||||||
public abstract String settingPrefix();
|
|
||||||
|
|
||||||
// check if feature is enabled
|
|
||||||
public boolean enabled() {
|
|
||||||
return enabled.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clusterChanged(ClusterChangedEvent event) {
|
|
||||||
if (!eagerLicenseRegistration && !event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
|
|
||||||
if (registered.compareAndSet(false, true)) {
|
|
||||||
logger.info("Registering to licensesService [lazy]");
|
|
||||||
licensesClientService.register(featureName(),
|
|
||||||
trialLicenseOptions, expirationCallbacks,
|
|
||||||
new LicensingClientListener());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void doStart() throws ElasticsearchException {
|
|
||||||
if (eagerLicenseRegistration) {
|
|
||||||
if (registered.compareAndSet(false, true)) {
|
|
||||||
logger.info("Registering to licensesService [eager]");
|
|
||||||
licensesClientService.register(featureName(),
|
|
||||||
trialLicenseOptions, expirationCallbacks,
|
|
||||||
new LicensingClientListener());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStop() throws ElasticsearchException {
|
|
||||||
if (clusterService != null) {
|
|
||||||
clusterService.remove(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doClose() throws ElasticsearchException {
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LicensingClientListener implements LicensesClientService.Listener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnabled(License license) {
|
|
||||||
enabled.set(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisabled(License license) {
|
|
||||||
enabled.set(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.license.plugin.rest;
|
|
||||||
|
|
||||||
|
|
||||||
import com.carrotsearch.randomizedtesting.annotations.Name;
|
|
||||||
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
|
||||||
import org.elasticsearch.test.rest.ElasticsearchRestTestCase;
|
|
||||||
import org.elasticsearch.test.rest.RestTestCandidate;
|
|
||||||
import org.elasticsearch.test.rest.parser.RestTestParseException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class LicensesRestIT extends ElasticsearchRestTestCase {
|
|
||||||
|
|
||||||
public LicensesRestIT(@Name("yaml") RestTestCandidate testCandidate) {
|
|
||||||
super(testCandidate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ParametersFactory
|
|
||||||
public static Iterable<Object[]> parameters() throws IOException, RestTestParseException {
|
|
||||||
return ElasticsearchRestTestCase.createParameters(0, 1);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
es.logger.level=DEBUG
|
|
||||||
log4j.rootLogger=${es.logger.level}, out
|
|
||||||
|
|
||||||
log4j.logger.org.apache.http=INFO, out
|
|
||||||
log4j.additivity.org.apache.http=false
|
|
||||||
|
|
||||||
log4j.logger.org.elasticsearch.license=TRACE
|
|
||||||
|
|
||||||
log4j.appender.out=org.apache.log4j.ConsoleAppender
|
|
||||||
log4j.appender.out.layout=org.apache.log4j.PatternLayout
|
|
||||||
log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n
|
|
Binary file not shown.
|
@ -1,3 +0,0 @@
|
||||||
ýŽÇqÝnęÄĚgŠśwM}Ťą‡UiKŠ•0âbÖ2Řşqö]â쇴ŻÖĎĂcĚ+IŇđÔ &IJ†fÉ~ßlj <09>ş]d™}o§OčľId®Č
|
|
||||||
5A(ěµ´^ŘöW©DŤŞJµë}ů-Oîë?u N5ľŰvpŰ{’Ľ˛Áôśát–ť¤7ůřĂę#˛Vqöó»ktwm’Ś]ĎLőŁz"| Q‹lźňQđsâ>ů<}Ź[Á2ÖÓŕZÖ|5‹ŻŤĘĘ7%ŘęD
|
|
||||||
Yĺ‘xn:ĽlúLČćHň˘«Ë2<C38B>źHvEEWÇ\¦H:“6Žh9 [!š…Űć©Š¤+;Ö.w7Cě©_|ŢÓŞĎÁ*ń§D`<60>Ú?‚ůxU/3>xUÓ“+ č
|
|
82
pom.xml
82
pom.xml
|
@ -1,82 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>org.elasticsearch.plugin</groupId>
|
|
||||||
<artifactId>elasticsearch-plugin</artifactId>
|
|
||||||
<version>2.0.0.beta1-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<groupId>org.elasticsearch</groupId>
|
|
||||||
<artifactId>elasticsearch-license</artifactId>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
<version>2.0.0.beta1-SNAPSHOT</version>
|
|
||||||
<modules>
|
|
||||||
<module>core</module>
|
|
||||||
<module>licensor</module>
|
|
||||||
<module>plugin</module>
|
|
||||||
<module>plugin-api</module>
|
|
||||||
</modules>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<license.basedir combine.self="override">${project.basedir}</license.basedir>
|
|
||||||
<elasticsearch.license.header>${license.basedir}/dev-tools/license/elasticsearch_license_header.txt</elasticsearch.license.header>
|
|
||||||
<elasticsearch.license.headerDefinition>${license.basedir}/dev-tools/license/license_header_definition.xml</elasticsearch.license.headerDefinition>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<repositories>
|
|
||||||
<repository>
|
|
||||||
<id>oss-snapshots</id>
|
|
||||||
<name>Sonatype OSS Snapshots</name>
|
|
||||||
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
|
|
||||||
<profiles>
|
|
||||||
<profile>
|
|
||||||
<id>deploy-internal</id>
|
|
||||||
<distributionManagement>
|
|
||||||
<repository>
|
|
||||||
<id>elasticsearch-internal-releases</id>
|
|
||||||
<name>Elasticsearch Internal Releases</name>
|
|
||||||
<url>http://maven.elasticsearch.org/artifactory/internal-releases</url>
|
|
||||||
</repository>
|
|
||||||
<snapshotRepository>
|
|
||||||
<id>elasticsearch-internal-snapshots</id>
|
|
||||||
<name>Elasticsearch Internal Snapshots</name>
|
|
||||||
<url>http://maven.elasticsearch.org/artifactory/internal-snapshots</url>
|
|
||||||
</snapshotRepository>
|
|
||||||
</distributionManagement>
|
|
||||||
</profile>
|
|
||||||
<profile>
|
|
||||||
<id>deploy-public</id>
|
|
||||||
<distributionManagement>
|
|
||||||
<repository>
|
|
||||||
<id>elasticsearch-public-releases</id>
|
|
||||||
<name>Elasticsearch Public Releases</name>
|
|
||||||
<url>http://maven.elasticsearch.org/artifactory/public-releases</url>
|
|
||||||
</repository>
|
|
||||||
</distributionManagement>
|
|
||||||
</profile>
|
|
||||||
<profile>
|
|
||||||
<id>default</id>
|
|
||||||
<activation>
|
|
||||||
<activeByDefault>true</activeByDefault>
|
|
||||||
</activation>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>com.carrotsearch.randomizedtesting</groupId>
|
|
||||||
<artifactId>junit4-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<argLine>${tests.jvm.argline}</argLine>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</profile>
|
|
||||||
</profiles>
|
|
||||||
</project>
|
|
Loading…
Reference in New Issue