Fix copying of JAR of a trigger to temporary file

While testing the fix for trigger, we fixed the corresponding
trigger example. While on it, we detected that ssl-factory example was
not working either so it is fixed in this commit as well.

patch by Stefan Miklosovic; reviewed by Brandon Williams for CASSANDRA-18264
This commit is contained in:
Stefan Miklosovic 2023-02-15 22:33:21 +01:00
parent cfe9641fbe
commit 9860c1e9d9
No known key found for this signature in database
GPG Key ID: 32F35CB2F546D93E
11 changed files with 116 additions and 70 deletions

2
.gitignore vendored
View File

@ -84,3 +84,5 @@ venv/
# build-scripts will put cassandra-builds into the directory # build-scripts will put cassandra-builds into the directory
cassandra-builds/ cassandra-builds/
cassandra-dtest/ cassandra-dtest/
conf/triggers/trigger-example.jar

View File

@ -1,4 +1,5 @@
4.1.1 4.1.1
* Fix copying of JAR of a trigger to temporary file (CASSANDRA-18264)
* Fix possible NoSuchFileException when removing a snapshot (CASSANDRA-18211) * Fix possible NoSuchFileException when removing a snapshot (CASSANDRA-18211)
* PaxosPrepare may add instances to the Electorate that are not in gossip (CASSANDRA-18194) * PaxosPrepare may add instances to the Electorate that are not in gossip (CASSANDRA-18194)
* Fix PAXOS2_COMMIT_AND_PREPARE_RSP serialisation AssertionError (CASSANDRA-18164) * Fix PAXOS2_COMMIT_AND_PREPARE_RSP serialisation AssertionError (CASSANDRA-18164)

View File

@ -0,0 +1,22 @@
Cassandra Custom SslContextFactory Example
==========================================
This example shows custom SslContextFactory implementation based on Kubernetes secrets
For the documentation please refer to the javadocs for `K8SecretsSslContextFactory.java.`
Installation
-------------
Step 1: Build the Cassandra classes locally
----
$ cd <cassandra_src_dir>
$ ant build
----
Step 2: Run tests for the security examples
----
$ cd <cassandra_src_dir>/examples/ssl-factory
$ ant test
----

View File

@ -1,19 +0,0 @@
Cassandra Custom SslContextFactory Example:
==========================================
Example-1: Custom SslContextFactory implementation based on Kubernetes secrets
------------------------------------------------------------------------------
For the documentation please refer to the javadocs for the K8SecretsSslContextFactory.java.
Installation:
============
Step 1: Build the Cassandra classes locally
change directory to <cassandra_src_dir>
run "ant build"
Step 2: Run tests for the security examples
change directory to <cassandra_src_dir>/examples/ssl-factory
run "ant test"

View File

@ -38,7 +38,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.EncryptionOptions; import org.apache.cassandra.config.EncryptionOptions;
import org.apache.cassandra.io.util.PathUtils; import org.apache.cassandra.io.util.File;
import static org.apache.cassandra.security.KubernetesSecretsSslContextFactory.ConfigKeys.KEYSTORE_PASSWORD_ENV_VAR; import static org.apache.cassandra.security.KubernetesSecretsSslContextFactory.ConfigKeys.KEYSTORE_PASSWORD_ENV_VAR;
import static org.apache.cassandra.security.KubernetesSecretsSslContextFactory.ConfigKeys.KEYSTORE_UPDATED_TIMESTAMP_PATH; import static org.apache.cassandra.security.KubernetesSecretsSslContextFactory.ConfigKeys.KEYSTORE_UPDATED_TIMESTAMP_PATH;
@ -64,7 +64,7 @@ public class KubernetesSecretsSslContextFactoryTest
private static void deleteFileIfExists(String file) private static void deleteFileIfExists(String file)
{ {
Path filePath = Paths.get(file); Path filePath = Paths.get(file);
boolean deleted = PathUtils.tryDelete(filePath); boolean deleted = new File(filePath).toJavaIOFile().delete();
if (!deleted) if (!deleted)
{ {
logger.warn("File {} could not be deleted.", filePath); logger.warn("File {} could not be deleted.", filePath);

View File

@ -0,0 +1,63 @@
Cassandra Trigger Example
==========================
The `AuditTrigger` class will create a basic audit of
activity on a table.
Installation
-------------
----
$ cd <cassandra_src_dir>/examples/triggers
$ ant install
----
It will build the trigger and copy it to `conf/triggers`. `AuditTrigger.properties`
in `conf` directory of this example will be automatically bundled into built jar file.
It is not needed to copy it to `conf/triggers` directory.
Usage
-----
Create the keyspace and table configured in `AuditTrigger.properties`:
----
cqlsh> CREATE KEYSPACE test WITH REPLICATION =
{ 'class' : 'SimpleStrategy', 'replication_factor' : '1' };
cqlsh> CREATE TABLE test.audit (key timeuuid, keyspace_name text,
table_name text, primary_key text, PRIMARY KEY(key));
----
Create a table to add the trigger to:
Note: The example currently only handles non-composite partition keys
----
cqlsh> CREATE TABLE test.test (key text, value text, PRIMARY KEY(key));
----
Configure the trigger on the table:
----
cqlsh> CREATE TRIGGER test1 ON test.test USING 'org.apache.cassandra.triggers.AuditTrigger';
----
Start inserting data to the table that has the trigger. For each
partition added to the table an entry should appear in the 'audit' table:
----
cqlsh> INSERT INTO test.test (key, value) VALUES ('1', '1');
----
An entry will be automatically added to `test.audit` table:
----
cqlsh> SELECT * FROM test.audit ;
@ Row 1
---------------+--------------------------------------
key | 885141d0-ad7c-11ed-b917-9958320828b8
keyspace_name | test
primary_key | 1
table_name | test
----

View File

@ -1,36 +0,0 @@
Cassandra Trigger Example:
==========================
The AuditTrigger class will create a basic audit of
activity on a table.
Installation:
============
change directory to <cassandra_src_dir>/examples/triggers
run "ant jar"
Copy build/trigger-example.jar to <cassandra_conf>/triggers/
Copy conf/* to <cassandra_home>/conf/
Create the keyspace and table configured in AuditTrigger.properties:
CREATE KEYSPACE test WITH REPLICATION =
{ 'class' : 'SimpleStrategy', 'replication_factor' : '1' };
CREATE TABLE test.audit (key timeuuid, keyspace_name text,
table_name text, primary_key text, PRIMARY KEY(key));
Create a table to add the trigger to:
CREATE TABLE test.test (key text, value text, PRIMARY KEY(key));
Note: The example currently only handles non-composite partition keys
Configure the trigger on the table:
CREATE TRIGGER test1 ON test.test
USING 'org.apache.cassandra.triggers.AuditTrigger';
Start inserting data to the table that has the trigger. For each
partition added to the table an entry should appear in the 'audit' table:
INSERT INTO test.test (key, value) values ('1', '1');
SELECT * FROM test.audit;
key | keyspace_name | primary_key | table_name
--------------------------------------+---------------+-------------+------------
7dc75b60-770f-11e5-9019-033d8af33e6f | test | 1 | test

View File

@ -57,7 +57,12 @@
</jar> </jar>
</target> </target>
<target name="install" depends="jar">
<copy verbose="true" file="${build.dir}/${final.name}.jar" todir="${cassandra.dir}/conf/triggers" overwrite="true"/>
</target>
<target name="clean"> <target name="clean">
<delete dir="${build.dir}" /> <delete dir="${build.dir}" />
<delete file="${cassandra.dir}/conf/triggers/${final.name}.jar"/>
</target> </target>
</project> </project>

View File

@ -29,19 +29,27 @@ import org.apache.cassandra.db.partitions.Partition;
import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.UUIDGen; import org.apache.cassandra.utils.TimeUUID;
public class AuditTrigger implements ITrigger public class AuditTrigger implements ITrigger
{ {
private Properties properties = loadProperties(); private static final String AUDIT_PROPERTIES_FILE_NAME = "AuditTrigger.properties";
private final Properties properties;
private final String auditKeyspace;
private final String auditTable;
public AuditTrigger()
{
properties = loadProperties();
auditKeyspace = properties.getProperty("keyspace");
auditTable = properties.getProperty("table");
}
public Collection<Mutation> augment(Partition update) public Collection<Mutation> augment(Partition update)
{ {
String auditKeyspace = properties.getProperty("keyspace");
String auditTable = properties.getProperty("table");
TableMetadata metadata = Schema.instance.getTableMetadata(auditKeyspace, auditTable); TableMetadata metadata = Schema.instance.getTableMetadata(auditKeyspace, auditTable);
PartitionUpdate.SimpleBuilder audit = PartitionUpdate.simpleBuilder(metadata, UUIDGen.getTimeUUID()); PartitionUpdate.SimpleBuilder audit = PartitionUpdate.simpleBuilder(metadata, TimeUUID.Generator.nextTimeUUID());
audit.row() audit.row()
.add("keyspace_name", update.metadata().keyspace) .add("keyspace_name", update.metadata().keyspace)
@ -54,7 +62,7 @@ public class AuditTrigger implements ITrigger
private static Properties loadProperties() private static Properties loadProperties()
{ {
Properties properties = new Properties(); Properties properties = new Properties();
InputStream stream = AuditTrigger.class.getClassLoader().getResourceAsStream("AuditTrigger.properties"); InputStream stream = AuditTrigger.class.getClassLoader().getResourceAsStream(AUDIT_PROPERTIES_FILE_NAME);
try try
{ {
properties.load(stream); properties.load(stream);

View File

@ -227,6 +227,7 @@
-Dcassandra.logdir=$PROJECT_DIR$/data/logs -Dcassandra.logdir=$PROJECT_DIR$/data/logs
-Dcassandra.reads.thresholds.coordinator.defensive_checks_enabled=true -Dcassandra.reads.thresholds.coordinator.defensive_checks_enabled=true
-Dcassandra.storagedir=$PROJECT_DIR$/data -Dcassandra.storagedir=$PROJECT_DIR$/data
-Dcassandra.triggers_dir=$PROJECT_DIR$/conf/triggers
-Djava.library.path=$PROJECT_DIR$/lib/sigar-bin -Djava.library.path=$PROJECT_DIR$/lib/sigar-bin
-Dlogback.configurationFile=file://$PROJECT_DIR$/conf/logback.xml -Dlogback.configurationFile=file://$PROJECT_DIR$/conf/logback.xml
-Xmx1G -Xmx1G

View File

@ -19,11 +19,10 @@ package org.apache.cassandra.triggers;
* under the License. * under the License.
* *
*/ */
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.nio.file.StandardCopyOption;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiPredicate; import java.util.function.BiPredicate;
@ -39,15 +38,15 @@ import static java.nio.file.Files.*;
/** /**
* Custom class loader will load the classes from the class path, CCL will load * Custom class loader will load the classes from the class path, CCL will load
* the classes from the the URL first, if it cannot find the required class it * the classes from the URL first, if it cannot find the required class it
* will let the parent class loader do the its job. * will let the parent class loader do its job.
* *
* Note: If the CCL is GC'ed then the associated classes will be unloaded. * Note: If the CCL is GC'ed then the associated classes will be unloaded.
*/ */
public class CustomClassLoader extends URLClassLoader public class CustomClassLoader extends URLClassLoader
{ {
private static final Logger logger = LoggerFactory.getLogger(CustomClassLoader.class); private static final Logger logger = LoggerFactory.getLogger(CustomClassLoader.class);
private final Map<String, Class<?>> cache = new ConcurrentHashMap<String, Class<?>>(); private final Map<String, Class<?>> cache = new ConcurrentHashMap<>();
private final ClassLoader parent; private final ClassLoader parent;
public CustomClassLoader(ClassLoader parent) public CustomClassLoader(ClassLoader parent)
@ -83,7 +82,7 @@ public class CustomClassLoader extends URLClassLoader
logger.info("Loading new jar {}", inputJar.absolutePath()); logger.info("Loading new jar {}", inputJar.absolutePath());
try try
{ {
copy(inputJar.toPath(), out.toPath()); copy(inputJar.toPath(), out.toPath(), StandardCopyOption.REPLACE_EXISTING);
addURL(out.toPath().toUri().toURL()); addURL(out.toPath().toUri().toURL());
} }
catch (IOException ex) catch (IOException ex)