blob: 20644a38a46d7b8241393f3105ed445303c5137e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.util;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
/**
* Marker annotation indicating when SSL Randomization should be used for a test class, and if so what
* the typical odds of using SSL should for that test class.
* @see SSLRandomizer#getSSLRandomizerForClass
* @see SuppressSSL
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RandomizeSSL {
// we don't choose ssl that often by default because of SOLR-5776
public static final double DEFAULT_ODDS = 0.2D;
/** Comment to inlcude when logging details of SSL randomization */
public String reason() default "";
/**
* Odds (as ratio relative to 1) that SSL should be selected in a typical run.
* Must either be betwen 0.0 and 1.0 (inclusively) or NaN in which case a sensible should be used.
* Actual Odds used for randomization may be higher depending on runner options such as
* <code>tests.multiplier</code> or <code>tests.nightly</code>
*
* @see #DEFAULT_ODDS
* @see LuceneTestCase#TEST_NIGHTLY
* @see LuceneTestCase#RANDOM_MULTIPLIER
*/
public double ssl() default Double.NaN;
/**
* Odds (as ratio relative to 1) that SSL should be selected in a typical run.
* Must either be betwen 0.0 and 1.0 (inclusively) or NaN in which case the effective value of
* {@link #ssl} should be used.
* Actual Odds used for randomization may be higher depending on runner options such as
* <code>tests.multiplier</code> or <code>tests.nightly</code>
* <p>
* NOTE: clientAuth is useless unless ssl is also in used, but we randomize it independently
* just in case it might find bugs in our test/ssl client code (ie: attempting to use
* SSL w/client cert to non-ssl servers)
* </p>
* @see #DEFAULT_ODDS
* @see LuceneTestCase#TEST_NIGHTLY
* @see LuceneTestCase#RANDOM_MULTIPLIER
*/
public double clientAuth() default Double.NaN;
/**
* A shorthand option for controlling both {@link #ssl} and {@link #clientAuth} with a single numeric
* value, For example: <code>@RandomizeSSL(0.5)</code>.
*
* Ignored if {@link #ssl} is set explicitly.
*/
public double value() default Double.NaN;
/**
* A simple data structure for encapsulating the effective values to be used when randomizing
* SSL in a test, based on the configured values in the {@link RandomizeSSL} annotation.
*/
public static final class SSLRandomizer {
public final double ssl;
public final double clientAuth;
public final String debug;
/** @lucene.internal */
public SSLRandomizer(double ssl, double clientAuth, String debug) {
this.ssl = ssl;
this.clientAuth = clientAuth;
this.debug = debug;
}
/**
* Randomly produces an SSLTestConfig taking into account various factors
*
* @see LuceneTestCase#TEST_NIGHTLY
* @see LuceneTestCase#RANDOM_MULTIPLIER
* @see LuceneTestCase#random()
*/
public SSLTestConfig createSSLTestConfig() {
// even if we know SSL is disabled, always consume the same amount of randomness
// that way all other test behavior should be consistent even if a user adds/removes @SuppressSSL
final boolean useSSL = TestUtil.nextInt(LuceneTestCase.random(), 0, 999) <
(int)(1000 * getEffectiveOdds(ssl, LuceneTestCase.TEST_NIGHTLY, LuceneTestCase.RANDOM_MULTIPLIER));
final boolean useClientAuth = TestUtil.nextInt(LuceneTestCase.random(), 0, 999) <
(int)(1000 * getEffectiveOdds(clientAuth, LuceneTestCase.TEST_NIGHTLY, LuceneTestCase.RANDOM_MULTIPLIER));
return new SSLTestConfig(useSSL, useClientAuth);
}
/** @lucene.internal Public only for testing */
public static double getEffectiveOdds(final double declaredOdds,
final boolean nightly,
final int multiplier) {
assert declaredOdds <= 1.0D;
assert 0.0D <= declaredOdds;
if (declaredOdds == 0.0D || declaredOdds == 1.0D ) {
return declaredOdds;
}
assert 0 < multiplier;
// negate the odds so we can then divide it by our multipling factors
// to increase the final odds
return 1.0D - ((1.0D - declaredOdds)
/ ((nightly ? 1.1D : 1.0D) * (1.0D + Math.log(multiplier))));
}
/**
* Returns an SSLRandomizer suitable for the specified (test) class
*/
public static final SSLRandomizer getSSLRandomizerForClass(@SuppressWarnings({"rawtypes"})Class clazz) {
@SuppressWarnings({"unchecked"})
final SuppressSSL suppression = (SuppressSSL) clazz.getAnnotation(SuppressSSL.class);
if (null != suppression) {
// Even if this class has a RandomizeSSL annotation, any usage of SuppressSSL -- even in a
// super class -- overrules that.
//
// (If it didn't work this way, it would be a pain in the ass to quickly disable SSL for a
// broad hierarchy of tests)
return new SSLRandomizer(0.0D, 0.0D, suppression.toString());
}
@SuppressWarnings({"unchecked"})
final RandomizeSSL annotation = (RandomizeSSL) clazz.getAnnotation(RandomizeSSL.class);
if (null == annotation) {
return new SSLRandomizer(0.0D, 0.0D, RandomizeSSL.class.getName() + " annotation not specified");
}
final double def = Double.isNaN(annotation.value()) ? DEFAULT_ODDS : annotation.value();
if (def < 0.0D || 1.0D < def) {
throw new IllegalArgumentException
(clazz.getName() + ": default value is not a ratio between 0 and 1: " + annotation.toString());
}
final double ssl = Double.isNaN(annotation.ssl()) ? def : annotation.ssl();
if (ssl < 0.0D || 1.0D < ssl) {
throw new IllegalArgumentException
(clazz.getName() + ": ssl value is not a ratio between 0 and 1: " + annotation.toString());
}
final double clientAuth = Double.isNaN(annotation.clientAuth()) ? ssl : annotation.clientAuth();
if (clientAuth < 0.0D || 1 < clientAuth) {
throw new IllegalArgumentException
(clazz.getName() + ": clientAuth value is not a ratio between 0 and 1: " + annotation.toString());
}
return new SSLRandomizer(ssl, clientAuth, annotation.toString());
}
}
}