1 /*
2   Copyright (c) DataStax, Inc.
3 
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7 
8   http://www.apache.org/licenses/LICENSE-2.0
9 
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15 */
16 
17 #ifndef __INTEGRATION_HPP__
18 #define __INTEGRATION_HPP__
19 
20 #include <gtest/gtest.h>
21 
22 #include <iostream>
23 #include <string>
24 #include <vector>
25 
26 #include "cassandra.h"
27 
28 #include "bridge.hpp"
29 #include "logger.hpp"
30 #include "objects.hpp"
31 #include "options.hpp"
32 #include "policies.hpp"
33 #include "pretty_print.hpp"
34 #include "test_utils.hpp"
35 #include "tlog.hpp"
36 #include "values.hpp"
37 
38 // Macros for grouping tests together
39 #define GROUP_TEST_F(group_name, test_case, test_name) TEST_F(test_case, group_name##_##test_name)
40 #define GROUP_TEST(group_name, test_case, test_name) TEST(test_case, group_name##_##test_name)
41 #define GROUP_TYPED_TEST_P(group_name, test_case, test_name) \
42   TYPED_TEST_P(test_case, group_name##_##test_name)
43 
44 // Macros to use for grouping integration tests together
45 #define INTEGRATION_TEST(server_type, test_case, test_name) \
46   GROUP_TEST(Integration##_##server_type, test_case, test_name)
47 #define INTEGRATION_TEST_F(server_type, test_case, test_name) \
48   GROUP_TEST_F(Integration##_##server_type, test_case, test_name)
49 #define INTEGRATION_TYPED_TEST_P(server_type, test_case, test_name) \
50   GROUP_TYPED_TEST_P(Integration##_##server_type, test_case, test_name)
51 #define INTEGRATION_DISABLED_TEST_F(server_type, test_case, test_name) \
52   GROUP_TEST_F(DISABLED##_##Integration##_##server_type, test_case, test_name)
53 #define INTEGRATION_DISABLED_TYPED_TEST_P(server_type, test_case, test_name) \
54   GROUP_TYPED_TEST_P(DISABLED##_##Integration##_##server_type, test_case, est_name)
55 
56 // Macros to use for grouping Cassandra integration tests together
57 #define CASSANDRA_INTEGRATION_TEST(test_case, test_name) \
58   INTEGRATION_TEST(Cassandra, test_case, test_name)
59 #define CASSANDRA_INTEGRATION_TEST_F(test_case, test_name) \
60   INTEGRATION_TEST_F(Cassandra, test_case, test_name)
61 #define CASSANDRA_INTEGRATION_TYPED_TEST_P(test_case, test_name) \
62   INTEGRATION_TYPED_TEST_P(Cassandra, test_case, test_name)
63 #define CASSANDRA_INTEGRATION_DISABLED_TEST_F(test_case, test_name) \
64   INTEGRATION_DISABLED_TEST_F(Cassandra, test_case, test_name)
65 #define CASSANDRA_INTEGRATION_DISABLED_TYPED_TEST_P(test_case, test_name) \
66   INTEGRATION_DISABLED_TYPED_TEST_P(Cassandra, test_case, test_name)
67 
68 // TODO: Create SKIP_SUITE macro; reduces noise and makes sense for certain suites
69 
70 #define SKIP_TEST(message)                                \
71   if (!Integration::skipped_message_displayed_) {         \
72     std::cout << "[ SKIPPED  ] " << message << std::endl; \
73     Integration::skipped_message_displayed_ = true;       \
74   }                                                       \
75   return;
76 
77 #define CHECK_FAILURE       \
78   if (this->HasFailure()) { \
79     return;                 \
80   }
81 
82 #define SKIP_TEST_VERSION(server_version_string, version_string) \
83   SKIP_TEST("Unsupported for Apache Cassandra Version "          \
84             << server_version_string << ": Server version " << version_string << "+ is required")
85 
86 #define CHECK_VERSION(version)                                                      \
87   do {                                                                              \
88     CCM::CassVersion cass_version = this->server_version_;                          \
89     if (!Options::is_cassandra()) {                                                 \
90       cass_version = static_cast<CCM::DseVersion>(cass_version).get_cass_version(); \
91     }                                                                               \
92     if (cass_version < #version) {                                                  \
93       SKIP_TEST_VERSION(cass_version.to_string(), #version)                         \
94     }                                                                               \
95   } while (0)
96 
97 #define CHECK_OPTIONS_VERSION(version)                                 \
98   if (Options::server_version() < #version) {                          \
99     SKIP_TEST_VERSION(Options::server_version().to_string(), #version) \
100   }
101 
102 #define CHECK_VALUE_TYPE_VERSION(type)                                            \
103   CCM::CassVersion cass_version = this->server_version_;                          \
104   if (!Options::is_cassandra()) {                                                 \
105     cass_version = static_cast<CCM::DseVersion>(cass_version).get_cass_version(); \
106   }                                                                               \
107   if (cass_version < type::supported_server_version()) {                          \
108     SKIP_TEST_VERSION(cass_version.to_string(), type::supported_server_version()) \
109   }
110 
111 #define CHECK_CONTINUE(flag, message) ASSERT_TRUE(flag) << message;
112 
113 #define CASSANDRA_KEY_VALUE_TABLE_FORMAT \
114   "CREATE TABLE IF NOT EXISTS %s (key %s PRIMARY KEY, value %s)"
115 #define CASSANDRA_KEY_VALUE_QUALIFIED_TABLE_FORMAT \
116   "CREATE TABLE IF NOT EXISTS %s.%s (key %s PRIMARY KEY, value %s)"
117 #define CASSANDRA_KEY_VALUE_INSERT_FORMAT "INSERT INTO %s (key, value) VALUES(%s, %s)"
118 #define CASSANDRA_KEY_VALUE_QUALIFIED_INSERT_FORMAT "INSERT INTO %s.%s (key, value) VALUES(%s, %s)"
119 #define CASSANDRA_SELECT_VALUE_FORMAT "SELECT value FROM %s WHERE key=%s"
120 #define CASSANDRA_DELETE_ROW_FORMAT "DELETE FROM %s WHERE key=%s"
121 #define CASSANDRA_UPDATE_VALUE_FORMAT "UPDATE %s SET value=%s WHERE key=%s"
122 #define SELECT_ALL_SYSTEM_LOCAL_CQL "SELECT * FROM system.local"
123 #define SELECT_COUNT_FORMAT "SELECT COUNT(*) FROM %s LIMIT 1000000"
124 
125 #define CASSANDRA_COMPOSITE_KEY_VALUE_TABLE_FORMAT                                         \
126   "CREATE TABLE IF NOT EXISTS %s (primary_key %s, column_key timeuuid, value %s, PRIMARY " \
127   "KEY(primary_key, column_key))"
128 #define CASSANDRA_COMPOSITE_KEY_VALUE_INSERT_FORMAT \
129   "INSERT INTO %s (primary_key, column_key, value) VALUES(%s, %s, %s)"
130 #define CASSANDRA_COMPOSITE_SELECT_VALUE_FORMAT "SELECT value FROM %s WHERE primary_key=%s"
131 
132 using namespace test;
133 using namespace test::driver;
134 
135 /**
136  * Statement type enumeration to use for specifying type of statement to use
137  * when executing queries
138  */
139 enum StatementType {
140   /**
141    * Batch statement
142    */
143   STATEMENT_TYPE_BATCH,
144   /**
145    * Prepared statement
146    */
147   STATEMENT_TYPE_PREPARED,
148   /**
149    * Simple statement
150    */
151   STATEMENT_TYPE_SIMPLE
152 };
153 
154 /**
155  * Base class to provide common integration test functionality
156  */
157 class Integration : public testing::Test {
158 public:
159   Integration();
160 
161   virtual ~Integration();
162 
163   virtual void SetUp();
164 
165   virtual void TearDown();
166 
167 protected:
168   /**
169    * Flag to indicate the skipped message display state
170    */
171   static bool skipped_message_displayed_;
172   /**
173    * Handle for interacting with CCM
174    */
175   SharedPtr<CCM::Bridge, StdDeleter<CCM::Bridge> > ccm_;
176   /**
177    * Logger instance for handling driver log messages
178    */
179   driver::Logger logger_;
180   /**
181    * Cluster instance
182    */
183   Cluster cluster_;
184   /**
185    * Connected database session
186    */
187   Session session_;
188   /**
189    * Generated keyspace name for the integration test
190    */
191   std::string keyspace_name_;
192   /**
193    * Generated table name for the integration test
194    */
195   std::string table_name_;
196   /**
197    * Keyspaces schema table
198    */
199   std::string system_schema_keyspaces_;
200   /**
201    * UUID generator
202    */
203   UuidGen uuid_generator_;
204   /**
205    * Version of Cassandra/DSE the session is connected to
206    */
207   CCM::CassVersion server_version_;
208   /**
209    * Number of nodes in data center one
210    * (DEFAULT: 1)
211    */
212   unsigned short number_dc1_nodes_;
213   /**
214    * Number of nodes in data center two
215    * (DEFAULT: 0)
216    */
217   unsigned short number_dc2_nodes_;
218   /**
219    * Replication factor override; default is calculated based on number of data
220    * center nodes; single data center is (nodes / 2) rounded up
221    */
222   unsigned short replication_factor_;
223   /**
224    * Replication configuration strategy
225    */
226   std::string replication_strategy_;
227   /**
228    * Default contact points generated based on the number of nodes requested
229    */
230   std::string contact_points_;
231   /**
232    * Setting for password authenticator. True if password authenticator should
233    * be enabled; false otherwise.
234    * (DEFAULT: false)
235    *
236    * NOTE: Username and password is 'cassandra'
237    */
238   bool is_password_authenticator_;
239   /**
240    * Setting for client authentication. True if client authentication should be
241    * enabled; false otherwise.
242    * (DEFAULT: false)
243    */
244   bool is_client_authentication_;
245   /**
246    * Setting for SSL authentication. True if SSL should be enabled; false
247    * otherwise.
248    * (DEFAULT: false)
249    */
250   bool is_ssl_;
251   /**
252    * Setting for v-nodes usage. True if v-nodes should be enabled; false
253    * otherwise.
254    * (DEFAULT: false)
255    */
256   bool is_with_vnodes_;
257   /**
258    * Setting for randomized contact points. True if randomized contact points
259    * should be enabled; false otherwise.
260    * (DEFAULT: false)
261    */
262   bool is_randomized_contact_points_;
263   /**
264    * Setting for schema metadata. True if schema metadata should be enabled;
265    * false otherwise.
266    * (DEFAULT: false)
267    */
268   bool is_schema_metadata_;
269   /**
270    * Setting to determine if CCM instance should be created. True if CCM instance
271    * should be created; false otherwise.
272    * (DEFAULT: true)
273    */
274   bool is_ccm_requested_;
275   /**
276    * Setting to determine if CCM cluster should be started. True if CCM cluster
277    * should be started; false otherwise.
278    * (DEFAULT: true)
279    */
280   bool is_ccm_start_requested_;
281   /**
282    * Setting to determine if CCM cluster should be started normally or nodes
283    * should be started individually (and in order). True if CCM cluster should
284    * be started by starting all node individually; false otherwise (start
285    * cluster normally).
286    * (DEFAULT: false)
287    */
288   bool is_ccm_start_node_individually_;
289   /**
290    * Setting to determine if session connection should be established. True if
291    * session connection should be established; false otherwise.
292    * (DEFAULT: true)
293    */
294   bool is_session_requested_;
295   /**
296    * Flag to indicate if the newly created keyspace should be set for the session connection.
297    * (DEFAULT: true)
298    */
299   bool is_keyspace_change_requested_;
300   /**
301    * Flag to indicate if a test is chaotic and should have its CCM cluster
302    * destroyed
303    */
304   bool is_test_chaotic_;
305   /**
306    * Flag to indicate if the beta protocol should be enabled. True if beta
307    * protocol should be enabled (Cassandra must be >= v3.10.0); false
308    * otherwise.
309    * (DEFAULT: true)
310    */
311   bool is_beta_protocol_;
312   /**
313    * Workload to apply to the cluster
314    */
315   std::vector<CCM::DseWorkload> dse_workload_;
316   /**
317    * Execution profiles to associate with default cluster
318    */
319   ExecutionProfile::Map profiles_;
320   /**
321    * Protocol version to associate with default cluster
322    */
323   int protocol_version_;
324   /**
325    * Name of the test case/suite
326    */
327   std::string test_case_name_;
328   /**
329    * Name of the test
330    */
331   std::string test_name_;
332   /**
333    * Vector of nodes that have been stopped
334    */
335   std::vector<unsigned int> stopped_nodes_;
336   /**
337    * Vector of nodes that have been paused
338    */
339   std::vector<unsigned int> paused_nodes_;
340 
341   /**
342    * Get the default keyspace name (based on the current test case and test
343    * name)
344    *
345    * @return Default keyspace name
346    */
347   virtual std::string default_keyspace();
348 
349   /**
350    * Get the default replication factor (based on the number of nodes in the
351    * standard two data center configuration for the test harness)
352    *
353    * @return Default replication factor
354    */
355   virtual unsigned short default_replication_factor();
356 
357   /**
358    * Get the default replication strategy for the keyspace (based on the
359    * default replication factor or the overridden value assigned during the test
360    * case setup process)
361    *
362    * @return  Default replication strategy
363    */
364   virtual std::string default_replication_strategy();
365 
366   /**
367    * Get the default select all CQL statement
368    *
369    * @return Default CQL statement to select all elements in the row
370    */
371   virtual std::string default_select_all();
372 
373   /**
374    * Get the row count using the default table name (based on the test name)
375    *
376    * @return The number of rows in the table
377    */
378   virtual int64_t default_select_count();
379 
380   /**
381    * Get the default table name (based on the test name)
382    *
383    * @return  Default table name
384    */
385   virtual std::string default_table();
386 
387   /**
388    * Drop a table from the current keyspace
389    *
390    * @param table_name Table to drop from the current keyspace
391    */
392   virtual void drop_table(const std::string& table_name);
393 
394   /**
395    * Drop a type from the current keyspace
396    *
397    * @param type_name Table to drop from the current keyspace
398    */
399   virtual void drop_type(const std::string& type_name);
400 
401   /**
402    * Update the current keyspace used by the session
403    *
404    * @param keyspace_name Keyspace to use
405    * @return True if keyspace was changed; false otherwise
406    */
407   virtual bool use_keyspace(const std::string& keyspace_name);
408 
409   /**
410    * Establish the session connection using provided cluster object.
411    *
412    * @param cluster Cluster object to use when creating session connection
413    */
414   virtual void connect(Cluster cluster);
415 
416   /**
417    * Create the cluster configuration and establish the session connection using
418    * provided cluster object.
419    */
420   virtual void connect();
421 
422   /**
423    * Get the default cluster configuration
424    *
425    * @param is_with_default_contact_points True if default contact points
426                                            should be added to the cluster
427    * @return Cluster object (default)
428    */
429   virtual Cluster default_cluster(bool is_with_default_contact_points = true);
430 
431   /**
432    * Enable/Disable tracing on the cluster
433    *
434    * @param enable True if tracing should be enabled on the cluster; false
435    *               otherwise (default: true)
436    */
437   virtual void enable_cluster_tracing(bool enable = true);
438 
439   /**
440    * Decommission a node and force the cluster to be removed after the test is
441    * completed
442    *
443    * @param node Node that should be decommissioned
444    * @oaram is_force True if decommission should be forced; false otherwise
445    *                 (default: false)
446    * @return True if node was decommissioned; false otherwise (the node is
447    *         invalid or was already decommissioned)
448    */
449   virtual bool decommission_node(unsigned int node, bool is_force = false);
450 
451   /**
452    * Decommission a node by force and ensure the cluster is set to be removed
453    * after the test is completed
454    *
455    * NOTE: Alias for decommission_node(node, true)
456    *
457    * @param node Node that should be decommissioned forcefully
458    * @return True if node was decommissioned; false otherwise (the node is
459    *         invalid or was already decommissioned)
460    */
461   virtual bool force_decommission_node(unsigned int node);
462 
463   /**
464    * Start a node that was previously stopped to ensure that it is not restarted after test is
465    * completed; paused nodes are ignored
466    *
467    * @param node Node that should be started
468    * @return True if node was started; false otherwise (the node is invalid or was already
469    *         started or is paused)
470    */
471   virtual bool start_node(unsigned int node);
472 
473   /**
474    * Stop a node that should be restarted after test is completed
475    *
476    * @param node Node that should be stopped
477    * @param is_kill True if forced termination requested; false otherwise
478    *                (default: false)
479    * @return True if node was stopped; false otherwise (the node is invalid or
480    *         was already stopped)
481    */
482   virtual bool stop_node(unsigned int node, bool is_kill = false);
483 
484   /**
485    * Pause a node that should be resumed after test is completed
486    *
487    * @param node Node that should be paused
488    * @return True if node was paused; false otherwise (the node is invalid or
489    *         was already paused)
490    */
491   virtual bool pause_node(unsigned int node);
492 
493   /**
494    * Resume a node that was previously paused to ensure that it is not resumed after test is
495    * completed
496    *
497    * @param node Node that should be resumed
498    * @return True if node was resumed; false otherwise
499    */
500   virtual bool resume_node(unsigned int node);
501 
502   /**
503    * Generate the contact points for the cluster
504    *
505    * @param ip_address IP address prefix
506    * @param number_of_nodes Total number of nodes in the cluster
507    * @return Comma delimited IP address (e.g. contact points)
508    */
509   std::string generate_contact_points(const std::string& ip_prefix, size_t number_of_nodes);
510 
511   /**
512    * Variable argument string formatter
513    *
514    * @param format Format string that follows printf specifications
515    * @param ... Additional arguments; depends on the format string
516    */
517   std::string format_string(const char* format, ...) const;
518 
519   /**
520    * Calculate the elapsed time in milliseconds
521    *
522    * @return Elapsed time in milliseconds
523    */
elapsed_time()524   inline uint64_t elapsed_time() {
525     if (start_time_ > 0) {
526       return (uv_hrtime() - start_time_) / 1000000UL;
527     }
528     return 0;
529   }
530 
531   /**
532    * Start the timer to calculate the elapsed time
533    */
start_timer()534   inline void start_timer() { start_time_ = uv_hrtime(); }
535 
536   /**
537    * Stop the timer - Calculate the elapsed time and reset the timer
538    *
539    * @return Elapsed time in milliseconds
540    */
stop_timer()541   inline uint64_t stop_timer() {
542     uint64_t duration = elapsed_time();
543     start_time_ = 0ull;
544     return duration;
545   }
546 
547   /**
548    * Get the current working directory
549    *
550    * @return Current working directory
551    */
cwd()552   inline static std::string cwd() { return Utils::cwd(); }
553 
554   /**
555    * Determine if a string contains another string
556    *
557    * @param input String being evaluated
558    * @param search String to find
559    * @return True if string is contained in other string; false otherwise
560    */
contains(const std::string & input,const std::string & search)561   inline static bool contains(const std::string& input, const std::string& search) {
562     return Utils::contains(input, search);
563   }
564 
565   /**
566    * Split a string into an array/vector
567    *
568    * @param input String to convert to array/vector
569    * @param delimiter Character to use split into elements (default: <space>)
570    * @return An array/vector representation of the string
571    */
explode(const std::string & input,const char delimiter=' ')572   inline static std::vector<std::string> explode(const std::string& input,
573                                                  const char delimiter = ' ') {
574     return Utils::explode(input, delimiter);
575   }
576 
577   /**
578    * Check to see if a file exists
579    *
580    * @param filename Absolute/Relative filename
581    * @return True if file exists; false otherwise
582    */
file_exists(const std::string & filename)583   inline static bool file_exists(const std::string& filename) {
584     return Utils::file_exists(filename);
585   }
586 
587   /**
588    * Concatenate or join a vector into a string
589    *
590    * @param elements Vector of strings to concatenate
591    * @param delimiter Character to use between elements (default: <space>)
592    * @return A string concatenating all the vector strings with delimiter
593    *         separation
594    */
implode(const std::vector<std::string> & elements,const char delimiter=' ')595   inline static std::string implode(const std::vector<std::string>& elements,
596                                     const char delimiter = ' ') {
597     return Utils::implode<std::string>(elements, delimiter);
598   }
599 
600   /**
601    * Create the directory from a path
602    *
603    * @param path Directory/Path to create
604    */
mkdir(const std::string & path)605   inline static void mkdir(const std::string& path) { Utils::mkdir(path); }
606 
607   /**
608    * Cross platform millisecond granularity sleep
609    *
610    * @param milliseconds Time in milliseconds to sleep
611    */
msleep(unsigned int milliseconds)612   inline static void msleep(unsigned int milliseconds) { Utils::msleep(milliseconds); }
613 
614   /**
615    * Replace all occurrences of a string from the input string
616    *
617    * @param input String having occurrences being replaced
618    * @param from String to find for replacement
619    * @param to String to replace with
620    * @return Input string with replacement
621    */
replace_all(const std::string & input,const std::string & from,const std::string & to)622   inline static std::string replace_all(const std::string& input, const std::string& from,
623                                         const std::string& to) {
624     return Utils::replace_all(input, from, to);
625   }
626 
627   /**
628    * Convert a string to lowercase
629    *
630    * @param input String to convert to lowercase
631    */
to_lower(const std::string & input)632   inline static std::string to_lower(const std::string& input) { return Utils::to_lower(input); }
633 
634   /**
635    * Remove the leading and trailing whitespace from a string
636    *
637    * @param input String to trim
638    * @return Trimmed string
639    */
trim(const std::string & input)640   inline static std::string trim(const std::string& input) { return Utils::trim(input); }
641 
642   /**
643    * Shrink the given name if the name is longer than allowable by the server
644    *
645    * NOTE: This is for keyspaces, tables, and other misc server side items
646    *
647    * @param name Name to shrink if name too long
648    */
649   void maybe_shrink_name(std::string& name);
650 
651   /**
652    * Wait for the logger count to reach expected count
653    *
654    * NOTE: This may wait up to LOGGER_MAXIMUM_WAIT_TIME_MS
655    *
656    * @param expected_count Expected logger count
657    * @return True if expected count is equal to logger count; false otherwise
658    */
659   bool wait_for_logger(size_t expected_count);
660 
661   /**
662    * Get the Murmur3 hash for a given value
663    *
664    * @param value Value to calculate Murmur3 hash for
665    * @return Murmur3 hash for value
666    */
667   int64_t murmur3_hash(const std::string& value);
668 
669   /**
670    * Get the time since epoch in microseconds
671    *
672    * @return Time since epoch in microseconds
673    */
674   uint64_t time_since_epoch_in_ms();
675 
676   /**
677    * Get the time since epoch in microseconds
678    *
679    * @return Time since epoch in microseconds
680    */
681   uint64_t time_since_epoch_us();
682 
683   /**
684    * Get the driver name as configured by the driver
685    *
686    * @return Driver name
687    */
688   std::string driver_name();
689 
690   /**
691    * Get the driver version as configured by the driver
692    *
693    * @return Driver version
694    */
695   std::string driver_version();
696 
697 private:
698   /**
699    * Keyspace creation query (generated via SetUp)
700    */
701   std::string create_keyspace_query_;
702   /**
703    * High-resolution real time when the timer was started (in nanoseconds)
704    */
705   uint64_t start_time_;
706 };
707 
708 #endif //__INTEGRATION_HPP__
709