/* * 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(Class clazz) { 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()); } 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()); } } }