From d478294b5a4b8fe986bb270a5299c2291956bd14 Mon Sep 17 00:00:00 2001 From: Lukasz Antoniak Date: Thu, 21 Nov 2024 13:14:19 +0100 Subject: [PATCH] Test shaded driver core in OSGi environement --- .../driver/api/osgi/service/TweetMessage.java | 82 ++++++++++ .../driver/api/osgi/service/TweetService.java | 28 ++++ .../driver/internal/osgi/BaseActivator.java | 151 ++++++++++++++++++ .../internal/osgi/MailboxActivator.java | 126 +-------------- .../driver/internal/osgi/TweetActivator.java | 41 +++++ .../osgi/service/TweetServiceImpl.java | 101 ++++++++++++ .../internal/osgi/OsgiCoreShadedIT.java | 55 +++++++ .../osgi/checks/DefaultServiceChecks.java | 6 + .../internal/osgi/support/BundleOptions.java | 40 +++++ .../driver/api/testinfra/ccm/CcmBridge.java | 10 +- .../oss/driver/api/testinfra/ccm/CcmRule.java | 4 +- .../ccm/DistributionCassandraVersions.java | 22 ++- .../loadbalancing/NodeComparator.java | 4 +- .../driver/api/testinfra/utils/ByteUtils.java | 84 ++++++++++ .../api/testinfra/utils/StringUtils.java | 30 ++++ .../requirement/VersionRequirementTest.java | 12 +- 16 files changed, 645 insertions(+), 151 deletions(-) create mode 100644 osgi-tests/src/main/java/com/datastax/oss/driver/api/osgi/service/TweetMessage.java create mode 100644 osgi-tests/src/main/java/com/datastax/oss/driver/api/osgi/service/TweetService.java create mode 100644 osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/BaseActivator.java create mode 100644 osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/TweetActivator.java create mode 100644 osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/service/TweetServiceImpl.java create mode 100644 osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/OsgiCoreShadedIT.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ByteUtils.java create mode 100644 test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/StringUtils.java diff --git a/osgi-tests/src/main/java/com/datastax/oss/driver/api/osgi/service/TweetMessage.java b/osgi-tests/src/main/java/com/datastax/oss/driver/api/osgi/service/TweetMessage.java new file mode 100644 index 00000000000..cb24e4ec732 --- /dev/null +++ b/osgi-tests/src/main/java/com/datastax/oss/driver/api/osgi/service/TweetMessage.java @@ -0,0 +1,82 @@ +/* + * 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 com.datastax.oss.driver.api.osgi.service; + +import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Instant; +import java.util.Objects; + +public class TweetMessage { + + private String sender; + + private Instant timestamp; + + private String body; + + public TweetMessage() {} + + public TweetMessage(@NonNull String sender, @NonNull Instant timestamp, @NonNull String body) { + this.sender = sender; + this.timestamp = timestamp; + this.body = body; + } + + public Instant getTimestamp() { + return timestamp; + } + + public void setTimestamp(Instant timestamp) { + this.timestamp = timestamp; + } + + public String getSender() { + return sender; + } + + public void setSender(String sender) { + this.sender = sender; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TweetMessage)) { + return false; + } + TweetMessage that = (TweetMessage) o; + return Objects.equals(sender, that.sender) + && Objects.equals(timestamp, that.timestamp) + && Objects.equals(body, that.body); + } + + @Override + public int hashCode() { + return Objects.hash(sender, timestamp, body); + } +} diff --git a/osgi-tests/src/main/java/com/datastax/oss/driver/api/osgi/service/TweetService.java b/osgi-tests/src/main/java/com/datastax/oss/driver/api/osgi/service/TweetService.java new file mode 100644 index 00000000000..30db4419767 --- /dev/null +++ b/osgi-tests/src/main/java/com/datastax/oss/driver/api/osgi/service/TweetService.java @@ -0,0 +1,28 @@ +/* + * 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 com.datastax.oss.driver.api.osgi.service; + +public interface TweetService { + + /** + * Stores the given tweet message. + * + * @param message Message to store. + */ + void sendMessage(TweetMessage message); +} diff --git a/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/BaseActivator.java b/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/BaseActivator.java new file mode 100644 index 00000000000..9441e88325d --- /dev/null +++ b/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/BaseActivator.java @@ -0,0 +1,151 @@ +/* + * 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 com.datastax.oss.driver.internal.osgi; + +import com.datastax.dse.driver.api.core.config.DseDriverOption; +import com.datastax.dse.driver.internal.core.graph.GraphProtocol; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.CqlSessionBuilder; +import com.datastax.oss.driver.api.core.config.DefaultDriverOption; +import com.datastax.oss.driver.api.core.config.DriverConfigLoader; +import com.datastax.oss.driver.api.core.config.ProgrammaticDriverConfigLoaderBuilder; +import java.net.InetSocketAddress; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.wiring.BundleWiring; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class BaseActivator implements BundleActivator { + + private static final Logger LOGGER = LoggerFactory.getLogger(TweetActivator.class); + + protected CqlSession session; + protected CqlIdentifier keyspace; + protected String graphName; + + @Override + public void start(BundleContext context) { + buildSession(context); + registerService(context); + } + + private void buildSession(BundleContext context) { + Bundle bundle = context.getBundle(); + BundleWiring bundleWiring = bundle.adapt(BundleWiring.class); + ClassLoader classLoader = bundleWiring.getClassLoader(); + + LOGGER.info("Application class loader: {}", classLoader); + + // Use the application bundle class loader to load classes by reflection when + // they are located in the application bundle. This is not strictly required + // as the driver has a "Dynamic-Import:*" directive which makes it capable + // of loading classes outside its bundle. + CqlSessionBuilder builder = CqlSession.builder().withClassLoader(classLoader); + + // Use the application bundle class loader to load configuration resources located + // in the application bundle. This is required, otherwise these resources will + // not be found. + ProgrammaticDriverConfigLoaderBuilder configLoaderBuilder = + DriverConfigLoader.programmaticBuilder(classLoader); + + String contactPointsStr = context.getProperty("cassandra.contactpoints"); + if (contactPointsStr == null) { + contactPointsStr = "127.0.0.1"; + } + LOGGER.info("Contact points: {}", contactPointsStr); + + String portStr = context.getProperty("cassandra.port"); + if (portStr == null) { + portStr = "9042"; + } + LOGGER.info("Port: {}", portStr); + int port = Integer.parseInt(portStr); + + List contactPoints = + Stream.of(contactPointsStr.split(",")) + .map((String host) -> InetSocketAddress.createUnresolved(host, port)) + .collect(Collectors.toList()); + builder.addContactPoints(contactPoints); + + String keyspaceStr = context.getProperty("cassandra.keyspace"); + if (keyspaceStr == null) { + keyspaceStr = "mailbox"; + } + LOGGER.info("Keyspace: {}", keyspaceStr); + keyspace = CqlIdentifier.fromCql(keyspaceStr); + + String lbp = context.getProperty("cassandra.lbp"); + if (lbp != null) { + LOGGER.info("Custom LBP: " + lbp); + configLoaderBuilder.withString(DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS, lbp); + } else { + LOGGER.info("Custom LBP: NO"); + } + + String datacenter = context.getProperty("cassandra.datacenter"); + if (datacenter != null) { + LOGGER.info("Custom datacenter: " + datacenter); + configLoaderBuilder.withString( + DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, datacenter); + } else { + LOGGER.info("Custom datacenter: NO"); + } + + String compression = context.getProperty("cassandra.compression"); + if (compression != null) { + LOGGER.info("Compression: {}", compression); + configLoaderBuilder.withString(DefaultDriverOption.PROTOCOL_COMPRESSION, compression); + } else { + LOGGER.info("Compression: NONE"); + } + + graphName = context.getProperty("cassandra.graph.name"); + if (graphName != null) { + LOGGER.info("Graph name: {}", graphName); + configLoaderBuilder.withString(DseDriverOption.GRAPH_NAME, graphName); + configLoaderBuilder.withString( + DseDriverOption.GRAPH_SUB_PROTOCOL, GraphProtocol.GRAPH_BINARY_1_0.toInternalCode()); + } else { + LOGGER.info("Graph: NONE"); + } + + builder.withConfigLoader(configLoaderBuilder.build()); + + LOGGER.info("Initializing session"); + session = builder.build(); + LOGGER.info("Session initialized"); + } + + @Override + public void stop(BundleContext context) { + if (session != null) { + LOGGER.info("Closing session"); + session.close(); + session = null; + LOGGER.info("Session closed"); + } + } + + protected abstract void registerService(BundleContext context); +} diff --git a/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/MailboxActivator.java b/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/MailboxActivator.java index 8dff11520af..f562fb843b7 100644 --- a/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/MailboxActivator.java +++ b/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/MailboxActivator.java @@ -17,135 +17,23 @@ */ package com.datastax.oss.driver.internal.osgi; -import com.datastax.dse.driver.api.core.config.DseDriverOption; -import com.datastax.dse.driver.internal.core.graph.GraphProtocol; -import com.datastax.oss.driver.api.core.CqlIdentifier; -import com.datastax.oss.driver.api.core.CqlSession; -import com.datastax.oss.driver.api.core.CqlSessionBuilder; -import com.datastax.oss.driver.api.core.config.DefaultDriverOption; -import com.datastax.oss.driver.api.core.config.DriverConfigLoader; -import com.datastax.oss.driver.api.core.config.ProgrammaticDriverConfigLoaderBuilder; import com.datastax.oss.driver.api.osgi.service.MailboxService; import com.datastax.oss.driver.internal.osgi.service.MailboxServiceImpl; import com.datastax.oss.driver.internal.osgi.service.geo.GeoMailboxServiceImpl; import com.datastax.oss.driver.internal.osgi.service.graph.GraphMailboxServiceImpl; import com.datastax.oss.driver.internal.osgi.service.reactive.ReactiveMailboxServiceImpl; -import java.net.InetSocketAddress; import java.util.Dictionary; import java.util.Hashtable; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; -import org.osgi.framework.wiring.BundleWiring; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class MailboxActivator implements BundleActivator { +public class MailboxActivator extends BaseActivator { private static final Logger LOGGER = LoggerFactory.getLogger(MailboxActivator.class); - private CqlSession session; - private CqlIdentifier keyspace; - private String graphName; - @Override - public void start(BundleContext context) { - buildSession(context); - registerService(context); - } - - private void buildSession(BundleContext context) { - - Bundle bundle = context.getBundle(); - BundleWiring bundleWiring = bundle.adapt(BundleWiring.class); - ClassLoader classLoader = bundleWiring.getClassLoader(); - - LOGGER.info("Application class loader: {}", classLoader); - - // Use the application bundle class loader to load classes by reflection when - // they are located in the application bundle. This is not strictly required - // as the driver has a "Dynamic-Import:*" directive which makes it capable - // of loading classes outside its bundle. - CqlSessionBuilder builder = CqlSession.builder().withClassLoader(classLoader); - - // Use the application bundle class loader to load configuration resources located - // in the application bundle. This is required, otherwise these resources will - // not be found. - ProgrammaticDriverConfigLoaderBuilder configLoaderBuilder = - DriverConfigLoader.programmaticBuilder(classLoader); - - String contactPointsStr = context.getProperty("cassandra.contactpoints"); - if (contactPointsStr == null) { - contactPointsStr = "127.0.0.1"; - } - LOGGER.info("Contact points: {}", contactPointsStr); - - String portStr = context.getProperty("cassandra.port"); - if (portStr == null) { - portStr = "9042"; - } - LOGGER.info("Port: {}", portStr); - int port = Integer.parseInt(portStr); - - List contactPoints = - Stream.of(contactPointsStr.split(",")) - .map((String host) -> InetSocketAddress.createUnresolved(host, port)) - .collect(Collectors.toList()); - builder.addContactPoints(contactPoints); - - String keyspaceStr = context.getProperty("cassandra.keyspace"); - if (keyspaceStr == null) { - keyspaceStr = "mailbox"; - } - LOGGER.info("Keyspace: {}", keyspaceStr); - keyspace = CqlIdentifier.fromCql(keyspaceStr); - - String lbp = context.getProperty("cassandra.lbp"); - if (lbp != null) { - LOGGER.info("Custom LBP: " + lbp); - configLoaderBuilder.withString(DefaultDriverOption.LOAD_BALANCING_POLICY_CLASS, lbp); - } else { - LOGGER.info("Custom LBP: NO"); - } - - String datacenter = context.getProperty("cassandra.datacenter"); - if (datacenter != null) { - LOGGER.info("Custom datacenter: " + datacenter); - configLoaderBuilder.withString( - DefaultDriverOption.LOAD_BALANCING_LOCAL_DATACENTER, datacenter); - } else { - LOGGER.info("Custom datacenter: NO"); - } - - String compression = context.getProperty("cassandra.compression"); - if (compression != null) { - LOGGER.info("Compression: {}", compression); - configLoaderBuilder.withString(DefaultDriverOption.PROTOCOL_COMPRESSION, compression); - } else { - LOGGER.info("Compression: NONE"); - } - - graphName = context.getProperty("cassandra.graph.name"); - if (graphName != null) { - LOGGER.info("Graph name: {}", graphName); - configLoaderBuilder.withString(DseDriverOption.GRAPH_NAME, graphName); - configLoaderBuilder.withString( - DseDriverOption.GRAPH_SUB_PROTOCOL, GraphProtocol.GRAPH_BINARY_1_0.toInternalCode()); - } else { - LOGGER.info("Graph: NONE"); - } - - builder.withConfigLoader(configLoaderBuilder.build()); - - LOGGER.info("Initializing session"); - session = builder.build(); - LOGGER.info("Session initialized"); - } - - private void registerService(BundleContext context) { + protected void registerService(BundleContext context) { MailboxServiceImpl mailbox; if ("true".equalsIgnoreCase(context.getProperty("cassandra.reactive"))) { mailbox = new ReactiveMailboxServiceImpl(session, keyspace); @@ -162,14 +50,4 @@ private void registerService(BundleContext context) { context.registerService(MailboxService.class.getName(), mailbox, properties); LOGGER.info("Mailbox Service successfully initialized"); } - - @Override - public void stop(BundleContext context) { - if (session != null) { - LOGGER.info("Closing session"); - session.close(); - session = null; - LOGGER.info("Session closed"); - } - } } diff --git a/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/TweetActivator.java b/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/TweetActivator.java new file mode 100644 index 00000000000..f2aff260004 --- /dev/null +++ b/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/TweetActivator.java @@ -0,0 +1,41 @@ +/* + * 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 com.datastax.oss.driver.internal.osgi; + +import com.datastax.oss.driver.api.osgi.service.TweetService; +import com.datastax.oss.driver.internal.osgi.service.TweetServiceImpl; +import java.util.Dictionary; +import java.util.Hashtable; +import org.osgi.framework.BundleContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TweetActivator extends BaseActivator { + + private static final Logger LOGGER = LoggerFactory.getLogger(TweetActivator.class); + + @Override + protected void registerService(BundleContext context) { + TweetServiceImpl tweet = new TweetServiceImpl(session, keyspace); + tweet.init(); + @SuppressWarnings("JdkObsolete") + Dictionary properties = new Hashtable<>(); + context.registerService(TweetService.class.getName(), tweet, properties); + LOGGER.info("Tweet Service successfully initialized"); + } +} diff --git a/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/service/TweetServiceImpl.java b/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/service/TweetServiceImpl.java new file mode 100644 index 00000000000..dc429fd509b --- /dev/null +++ b/osgi-tests/src/main/java/com/datastax/oss/driver/internal/osgi/service/TweetServiceImpl.java @@ -0,0 +1,101 @@ +/* + * 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 com.datastax.oss.driver.internal.osgi.service; + +import com.codahale.metrics.Timer; +import com.datastax.oss.driver.api.core.CqlIdentifier; +import com.datastax.oss.driver.api.core.CqlSession; +import com.datastax.oss.driver.api.core.cql.BoundStatement; +import com.datastax.oss.driver.api.core.cql.PreparedStatement; +import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric; +import com.datastax.oss.driver.api.core.metrics.Metrics; +import com.datastax.oss.driver.api.osgi.service.TweetMessage; +import com.datastax.oss.driver.api.osgi.service.TweetService; +import java.util.Optional; +import net.jcip.annotations.GuardedBy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TweetServiceImpl implements TweetService { + + private static final Logger LOGGER = LoggerFactory.getLogger(TweetServiceImpl.class); + + protected final CqlSession session; + protected final CqlIdentifier keyspace; + + @GuardedBy("this") + protected boolean initialized = false; + + private PreparedStatement insertStatement; + + public TweetServiceImpl(CqlSession session, CqlIdentifier keyspace) { + this.session = session; + this.keyspace = keyspace; + } + + public synchronized void init() { + if (initialized) { + return; + } + createSchema(); + prepareStatements(); + printMetrics(); + initialized = true; + } + + protected void createSchema() { + session.execute("DROP KEYSPACE IF EXISTS test_osgi"); + session.execute( + "CREATE KEYSPACE IF NOT EXISTS test_osgi with replication = {'class': 'SimpleStrategy', 'replication_factor' : 1}"); + session.execute( + "CREATE TABLE " + + keyspace + + ".tweets (" + + "sender text," + + "timestamp timestamp," + + "body text," + + "PRIMARY KEY (sender, timestamp))"); + } + + protected void prepareStatements() { + insertStatement = + session.prepare( + "INSERT INTO " + keyspace + ".tweets(sender, timestamp, body) VALUES (?, ?, ?)"); + } + + protected void printMetrics() { + // Exercise metrics + if (session.getMetrics().isPresent()) { + Metrics metrics = session.getMetrics().get(); + Optional cqlRequests = metrics.getSessionMetric(DefaultSessionMetric.CQL_REQUESTS); + cqlRequests.ifPresent( + counter -> LOGGER.info("Number of CQL requests: {}", counter.getCount())); + } + } + + @Override + public void sendMessage(TweetMessage message) { + try { + BoundStatement statement = + insertStatement.bind(message.getSender(), message.getTimestamp(), message.getBody()); + session.execute(statement); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/OsgiCoreShadedIT.java b/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/OsgiCoreShadedIT.java new file mode 100644 index 00000000000..e61ce0ee91f --- /dev/null +++ b/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/OsgiCoreShadedIT.java @@ -0,0 +1,55 @@ +/* + * 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 com.datastax.oss.driver.internal.osgi; + +import com.datastax.oss.driver.api.osgi.service.TweetService; +import com.datastax.oss.driver.internal.osgi.checks.DefaultServiceChecks; +import com.datastax.oss.driver.internal.osgi.support.BundleOptions; +import com.datastax.oss.driver.internal.osgi.support.CcmExamReactorFactory; +import com.datastax.oss.driver.internal.osgi.support.CcmPaxExam; +import javax.inject.Inject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.Configuration; +import org.ops4j.pax.exam.CoreOptions; +import org.ops4j.pax.exam.Option; +import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; + +@RunWith(CcmPaxExam.class) +@ExamReactorStrategy(CcmExamReactorFactory.class) +public class OsgiCoreShadedIT { + @Inject TweetService service; + + @Configuration + public Option[] config() { + return CoreOptions.options( + BundleOptions.applicationCoreBundle(), + BundleOptions.driverCoreShadedBundle(), + // Guava is required by query builder, runtime mapper and test infra + BundleOptions.commonBundles(), + // Netty and Jackson are shaded + // If this tests breaks due to Guava bundle, it may be the case + // that test infrastructure uses Guava + BundleOptions.testBundles()); + } + + @Test + public void test_shaded() throws Exception { + DefaultServiceChecks.checkService(service); + } +} diff --git a/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/checks/DefaultServiceChecks.java b/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/checks/DefaultServiceChecks.java index 90a6a2e4c8b..ec3fc405d0a 100644 --- a/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/checks/DefaultServiceChecks.java +++ b/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/checks/DefaultServiceChecks.java @@ -21,6 +21,8 @@ import com.datastax.oss.driver.api.osgi.service.MailboxMessage; import com.datastax.oss.driver.api.osgi.service.MailboxService; +import com.datastax.oss.driver.api.osgi.service.TweetMessage; +import com.datastax.oss.driver.api.osgi.service.TweetService; import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -48,4 +50,8 @@ public static void checkService(MailboxService service) throws Exception { service.clearMailbox(recipient); } } + + public static void checkService(TweetService service) throws Exception { + service.sendMessage(new TweetMessage("user@datastax.com", Instant.now(), "body")); + } } diff --git a/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/support/BundleOptions.java b/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/support/BundleOptions.java index 67410cc29f2..bbbc70f96c4 100644 --- a/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/support/BundleOptions.java +++ b/osgi-tests/src/test/java/com/datastax/oss/driver/internal/osgi/support/BundleOptions.java @@ -21,14 +21,23 @@ import static org.ops4j.pax.exam.CoreOptions.junitBundles; import static org.ops4j.pax.exam.CoreOptions.mavenBundle; import static org.ops4j.pax.exam.CoreOptions.options; +import static org.ops4j.pax.exam.CoreOptions.provision; import static org.ops4j.pax.exam.CoreOptions.systemProperty; import static org.ops4j.pax.exam.CoreOptions.systemTimeout; import static org.ops4j.pax.exam.CoreOptions.vmOption; +import com.datastax.oss.driver.api.osgi.service.TweetMessage; +import com.datastax.oss.driver.api.osgi.service.TweetService; +import com.datastax.oss.driver.internal.osgi.BaseActivator; +import com.datastax.oss.driver.internal.osgi.TweetActivator; +import com.datastax.oss.driver.internal.osgi.service.TweetServiceImpl; +import java.io.InputStream; import org.ops4j.pax.exam.CoreOptions; import org.ops4j.pax.exam.options.CompositeOption; import org.ops4j.pax.exam.options.UrlProvisionOption; import org.ops4j.pax.exam.options.WrappedUrlProvisionOption; +import org.ops4j.pax.tinybundles.core.TinyBundles; +import org.osgi.framework.Constants; public class BundleOptions { @@ -53,6 +62,37 @@ public static CompositeOption applicationBundle() { bundle("reference:file:target/classes")); } + /** + * Tweet service has been implemented to use only driver core module (no mapper and query builder) + * and validate that shaded core does not require Guava dependency. Mapper and query builder + * modules do require Guava bundle. + */ + public static CompositeOption applicationCoreBundle() { + return () -> { + InputStream is = + TinyBundles.bundle() + .activator(TweetActivator.class) + .add(BaseActivator.class) + .add(TweetMessage.class) + .add(TweetService.class) + .add(TweetServiceImpl.class) + .symbolicName("application-core") + .set( + Constants.IMPORT_PACKAGE, + "org.osgi.framework,com.datastax.oss.driver.api.core,com.datastax.oss.driver.internal.core,com.datastax.oss.driver.api.core.config,org.slf4j,org.osgi.framework.wiring,com.datastax.oss.driver.api.core.metrics,com.datastax.oss.driver.api.core.cql") + .set( + Constants.EXPORT_PACKAGE, + "com.datastax.oss.driver.api.osgi,com.datastax.oss.driver.api.osgi.service") + .build(); + return options( + provision(is), + systemProperty("cassandra.contactpoints").value("127.0.0.1"), + systemProperty("cassandra.port").value("9042"), + systemProperty("cassandra.keyspace").value("test_osgi"), + systemProperty("cassandra.datacenter").value("dc1")); + }; + } + public static UrlProvisionOption driverCoreBundle() { return bundle("reference:file:../core/target/classes"); } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java index ba4e4ca7ad7..d82a2b24510 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmBridge.java @@ -19,8 +19,8 @@ import com.datastax.oss.driver.api.core.Version; import com.datastax.oss.driver.api.testinfra.requirement.BackendType; -import com.google.common.base.Joiner; -import com.google.common.io.Resources; +import com.datastax.oss.driver.api.testinfra.utils.ByteUtils; +import com.datastax.oss.driver.api.testinfra.utils.StringUtils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -277,11 +277,11 @@ public void create() { } public void nodetool(int node, String... args) { - execute(String.format("node%d nodetool %s", node, Joiner.on(" ").join(args))); + execute(String.format("node%d nodetool %s", node, StringUtils.join(" ", args))); } public void dsetool(int node, String... args) { - execute(String.format("node%d dsetool %s", node, Joiner.on(" ").join(args))); + execute(String.format("node%d dsetool %s", node, StringUtils.join(" ", args))); } public void reloadCore(int node, String keyspace, String table, boolean reindex) { @@ -442,7 +442,7 @@ private static File createTempStore(String storePath) { File f = null; try (OutputStream os = new FileOutputStream(f = File.createTempFile("server", ".store"))) { f.deleteOnExit(); - Resources.copy(CcmBridge.class.getResource(storePath), os); + ByteUtils.copy(CcmBridge.class.getResourceAsStream(storePath), os); } catch (IOException e) { LOG.warn("Failure to write keystore, SSL-enabled servers may fail to start.", e); } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java index e6483c37877..fb6fbac2878 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/CcmRule.java @@ -63,7 +63,7 @@ public static CcmBridge.Builder configureCcmBridge(CcmBridge.Builder builder) { } @Override - protected synchronized void before() { + public synchronized void before() { if (!started) { // synchronize before so blocks on other before() call waiting to finish. super.before(); @@ -72,7 +72,7 @@ protected synchronized void before() { } @Override - protected void after() { + public void after() { // override after so we don't remove when done. } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/DistributionCassandraVersions.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/DistributionCassandraVersions.java index b042fbb8422..04679c08194 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/DistributionCassandraVersions.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/ccm/DistributionCassandraVersions.java @@ -19,36 +19,34 @@ import com.datastax.oss.driver.api.core.Version; import com.datastax.oss.driver.api.testinfra.requirement.BackendType; -import com.google.common.collect.ImmutableSortedMap; import java.util.HashMap; import java.util.Map; +import java.util.TreeMap; /** Defines mapping of various distributions to shipped Apache Cassandra version. */ public abstract class DistributionCassandraVersions { - private static final Map> mappings = - new HashMap<>(); + private static final Map> mappings = new HashMap<>(); static { { // DSE - ImmutableSortedMap dse = - ImmutableSortedMap.of( - Version.V1_0_0, CcmBridge.V2_1_19, - Version.V5_0_0, CcmBridge.V3_0_15, - CcmBridge.V5_1_0, CcmBridge.V3_10, - CcmBridge.V6_0_0, CcmBridge.V4_0_0); + TreeMap dse = new TreeMap<>(); + dse.put(Version.V1_0_0, CcmBridge.V2_1_19); + dse.put(Version.V5_0_0, CcmBridge.V3_0_15); + dse.put(CcmBridge.V5_1_0, CcmBridge.V3_10); + dse.put(CcmBridge.V6_0_0, CcmBridge.V4_0_0); mappings.put(BackendType.DSE, dse); } { // HCD - ImmutableSortedMap hcd = - ImmutableSortedMap.of(Version.V1_0_0, CcmBridge.V4_0_11); + TreeMap hcd = new TreeMap<>(); + hcd.put(Version.V1_0_0, CcmBridge.V4_0_11); mappings.put(BackendType.HCD, hcd); } } public static Version getCassandraVersion(BackendType type, Version version) { - ImmutableSortedMap mapping = mappings.get(type); + TreeMap mapping = mappings.get(type); if (mapping == null) { return null; } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/NodeComparator.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/NodeComparator.java index 0c8f56d2aca..bf4a6ebb867 100644 --- a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/NodeComparator.java +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/loadbalancing/NodeComparator.java @@ -18,7 +18,7 @@ package com.datastax.oss.driver.api.testinfra.loadbalancing; import com.datastax.oss.driver.api.core.metadata.Node; -import com.google.common.primitives.UnsignedBytes; +import com.datastax.oss.driver.api.testinfra.utils.ByteUtils; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Comparator; @@ -47,7 +47,7 @@ public int compare(Node node1, Node node2) { .map(InetAddress::getAddress) .orElse(EMPTY); - int result = UnsignedBytes.lexicographicalComparator().compare(address1, address2); + int result = ByteUtils.LexicographicalByteComparator.INSTANCE.compare(address1, address2); if (result != 0) { return result; } diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ByteUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ByteUtils.java new file mode 100644 index 00000000000..8512d6d316d --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/ByteUtils.java @@ -0,0 +1,84 @@ +/* + * 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 com.datastax.oss.driver.api.testinfra.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Comparator; + +public class ByteUtils { + public static void copy(InputStream is, OutputStream os) throws IOException { + try { + byte[] buf = new byte[1024]; + int length; + while ((length = is.read(buf)) != -1) { + os.write(buf, 0, length); + } + os.flush(); + } finally { + closeQuietly(is); + closeQuietly(os); + } + } + + public static void closeQuietly(InputStream is) { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + // ignore + } + } + } + + public static void closeQuietly(OutputStream os) { + if (os != null) { + try { + os.close(); + } catch (IOException e) { + // ignore + } + } + } + + public static class LexicographicalByteComparator implements Comparator { + public static final LexicographicalByteComparator INSTANCE = + new LexicographicalByteComparator(); + + @Override + public int compare(byte[] left, byte[] right) { + int minLength = Math.min(left.length, right.length); + for (int i = 0; i < minLength; i++) { + int result = compare(left[i], right[i]); + if (result != 0) { + return result; + } + } + return left.length - right.length; + } + + private int compare(byte a, byte b) { + return toInt(a) - toInt(b); + } + + private int toInt(byte value) { + return value & 255; + } + } +} diff --git a/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/StringUtils.java b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/StringUtils.java new file mode 100644 index 00000000000..69bbe8a5f6c --- /dev/null +++ b/test-infra/src/main/java/com/datastax/oss/driver/api/testinfra/utils/StringUtils.java @@ -0,0 +1,30 @@ +/* + * 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 com.datastax.oss.driver.api.testinfra.utils; + +import java.util.StringJoiner; + +public class StringUtils { + public static String join(String separator, String... parts) { + StringJoiner joiner = new StringJoiner(separator); + for (Object arg : parts) { + joiner.add(arg.toString()); + } + return joiner.toString(); + } +} diff --git a/test-infra/src/test/java/com/datastax/oss/driver/api/testinfra/requirement/VersionRequirementTest.java b/test-infra/src/test/java/com/datastax/oss/driver/api/testinfra/requirement/VersionRequirementTest.java index 916cb106696..1f1e1638163 100644 --- a/test-infra/src/test/java/com/datastax/oss/driver/api/testinfra/requirement/VersionRequirementTest.java +++ b/test-infra/src/test/java/com/datastax/oss/driver/api/testinfra/requirement/VersionRequirementTest.java @@ -20,7 +20,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.datastax.oss.driver.api.core.Version; -import com.google.common.collect.ImmutableList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.Test; @@ -125,7 +125,7 @@ public void single_requirement_min_and_max() { @Test public void multi_requirement_any_version() { - List req = ImmutableList.of(CASSANDRA_ANY, DSE_ANY); + List req = Arrays.asList(CASSANDRA_ANY, DSE_ANY); assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_0)).isTrue(); assertThat(VersionRequirement.meetsAny(req, DSE, V_1_0_0)).isTrue(); @@ -133,7 +133,7 @@ public void multi_requirement_any_version() { @Test public void multi_db_requirement_min_one_any_other() { - List req = ImmutableList.of(CASSANDRA_FROM_1_0_0, DSE_ANY); + List req = Arrays.asList(CASSANDRA_FROM_1_0_0, DSE_ANY); assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_0)).isTrue(); assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_2_0_0)).isTrue(); @@ -146,7 +146,7 @@ public void multi_db_requirement_min_one_any_other() { @Test public void multi_requirement_two_ranges() { List req = - ImmutableList.of(CASSANDRA_FROM_1_0_0_TO_2_0_0, CASSANDRA_FROM_3_0_0_TO_3_1_0); + Arrays.asList(CASSANDRA_FROM_1_0_0_TO_2_0_0, CASSANDRA_FROM_3_0_0_TO_3_1_0); assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_0)).isTrue(); assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_1_0)).isTrue(); @@ -162,7 +162,7 @@ public void multi_requirement_two_ranges() { @Test public void multi_requirement_overlapping() { List req = - ImmutableList.of(CASSANDRA_FROM_1_0_0_TO_2_0_0, CASSANDRA_FROM_1_1_0); + Arrays.asList(CASSANDRA_FROM_1_0_0_TO_2_0_0, CASSANDRA_FROM_1_1_0); assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_0_0)).isTrue(); assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_1_0)).isTrue(); @@ -174,7 +174,7 @@ public void multi_requirement_overlapping() { @Test public void multi_requirement_not_range() { - List req = ImmutableList.of(CASSANDRA_TO_1_0_0, CASSANDRA_FROM_1_1_0); + List req = Arrays.asList(CASSANDRA_TO_1_0_0, CASSANDRA_FROM_1_1_0); assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_0_0_0)).isTrue(); assertThat(VersionRequirement.meetsAny(req, CASSANDRA, V_1_1_0)).isTrue();