1<?php 2 3/** 4 * Copyright 2015-2017 DataStax, Inc. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19namespace Cassandra; 20 21/** 22 * Retry policy integration tests. 23 */ 24class RetryPolicyIntegrationTest extends BasicIntegrationTest { 25 /** 26 * The number of inserts and asserts to perform during testing. 27 */ 28 const NUMBER_OF_INSERTS = 25; 29 /** 30 * Maximum number of TimeoutExceptions to bound the insert/assert functions 31 * recursive calls/ 32 */ 33 const NUMBER_OF_TIMEOUT_EXCEPTIONS = 5; 34 /** 35 * Batch statement type 36 */ 37 const BATCH_STATEMENT = 1; 38 /** 39 * Prepared statement type 40 */ 41 const PREPARED_STATEMENT = 2; 42 /** 43 * Simple statement type 44 */ 45 const SIMPLE_STATEMENT = 3; 46 47 /** 48 * Insert query generated for a retry policy test. 49 * 50 * @var string 51 */ 52 private $insertQuery; 53 54 /** 55 * Setup the retry policy for multiple nodes. 56 */ 57 public function setUp() { 58 // Ensure RF = 3 (anything greater than 1 is good) 59 $this->replicationFactor = 3; 60 61 // Process parent setup steps 62 parent::setUp(); 63 64 // Create the table 65 $query = "CREATE TABLE {$this->tableNamePrefix} (key int, value_int int, PRIMARY KEY(key, value_int))"; 66 $this->session->execute($query); 67 68 // Create the insert query 69 $this->insertQuery = "INSERT INTO {$this->tableNamePrefix} (key, value_int) VALUES (?, ?)"; 70 } 71 72 /** 73 * Insert n values into the table for a given key. 74 * 75 * @param $statementType Type of statement to create for inserts 76 * @param RetryPolicy $policy RetryPolicy to use when executing statements 77 * @param $key Key value 78 * @param $numberOfInserts Number of inserts to perform 79 * @param $consistency Consistency level to execute statement 80 * @param int $retries Number of TimeoutException retries 81 * (DEFAULT: self::NUMBER_OF_TIMEOUT_EXCEPTIONS) 82 */ 83 private function insert($statementType, RetryPolicy $policy, $key, $numberOfInserts, $consistency, $retries = self::NUMBER_OF_TIMEOUT_EXCEPTIONS) { 84 try { 85 // Create all statement types 86 $batch = new BatchStatement(\Cassandra::BATCH_UNLOGGED); 87 $prepare = $this->session->prepare($this->insertQuery); 88 89 // Create the default execution options 90 $options = array( 91 "consistency" => $consistency, 92 "retry_policy" => $policy 93 ); 94 95 // Create the inserts 96 foreach (range(1, $numberOfInserts) as $i) { 97 $values = array( 98 $key, 99 $i 100 ); 101 if ($statementType == self::BATCH_STATEMENT) { 102 if ($i % 2 == 0) { 103 $batch->add($prepare, $values); 104 } else { 105 $batch->add($this->insertQuery, $values); 106 } 107 } else { 108 // Execute either the prepare or simple statment 109 $statement = $prepare; 110 if ($statementType == self::SIMPLE_STATEMENT) { 111 $statement = $this->insertQuery; 112 } 113 $options["arguments"] = $values; 114 $this->session->execute($statement, $options); 115 } 116 } 117 118 // Execute the batched insert 119 if ($statementType == self::BATCH_STATEMENT) { 120 $this->session->execute($batch, $options); 121 } 122 } catch (Exception\TimeoutException $te) { 123 if (Integration::isDebug()) { 124 fprintf(STDOUT, "Insert TimeoutException: %s (%s:%d)" . PHP_EOL, 125 $te->getMessage(), $te->getFile(), $te->getLine()); 126 } 127 if ($retries > 0) { 128 $this->insert($policy, $key, $numberOfInserts, $consistency, ($retries - 1)); 129 } else { 130 throw $te; 131 } 132 } 133 } 134 135 /** 136 * Assert n values in the table for a given key. 137 * 138 * @param RetryPolicy $policy RetryPolicy to use when executing statements 139 * @param $key Key value 140 * @param $numberOfAsserts Number of inserts to perform 141 * @param $consistency Consistency level to execute statement 142 * @param int $retries Number of TimeoutException retries 143 * (DEFAULT: self::NUMBER_OF_TIMEOUT_EXCEPTIONS) 144 */ 145 private function assert(RetryPolicy $policy, $key, $numberOfAsserts, $consistency, $retries = self::NUMBER_OF_TIMEOUT_EXCEPTIONS) { 146 try { 147 // Select the values 148 $options = array( 149 "consistency" => $consistency, 150 "retry_policy" => $policy 151 ); 152 $rows = $this->session->execute( 153 "SELECT value_int FROM {$this->tableNamePrefix} WHERE key = {$key}", 154 $options 155 ); 156 157 // Assert the values 158 $this->assertCount($numberOfAsserts, $rows); 159 foreach ($rows as $i => $row) { 160 $this->assertEquals(($i + 1), $row["value_int"]); 161 } 162 } catch (Exception\TimeoutException $te) { 163 if (Integration::isDebug()) { 164 fprintf(STDOUT, "Assert TimeoutException: %s (%s:%d)" . PHP_EOL, 165 $te->getMessage(), $te->getFile(), $te->getLine()); 166 } 167 if ($retries > 0) { 168 $this->assert($policy, $key, $numberOfAsserts, $consistency, ($retries - 1)); 169 } else { 170 throw $te; 171 } 172 } 173 } 174 175 /** 176 * Statement execution supports downgrading consistency retry policy. 177 * 178 * This test will ensure that the PHP driver supports the downgrading 179 * retry policy when executing statements. 180 * 181 * @test 182 * @ticket PHP-60 183 * 184 * @cassandra-version-2.0 185 */ 186 public function testDowngradingPolicy() { 187 // Create the retry policy (RF = 3 with 1 node) 188 $policy = new RetryPolicy\DowngradingConsistency(); 189 190 // Iterate over each statement type 191 foreach (range(1, 3) as $statementType) { 192 // Determine if the statement type should be skipped 193 if ($statementType == self::BATCH_STATEMENT 194 && version_compare(\Cassandra::CPP_DRIVER_VERSION, "2.2.3") < 0) { 195 if (Integration::isDebug()) { 196 fprintf(STDOUT, "Skipping Batch Statements in %s: Issue fixed in DataStax C/C++ v2.2.3" . PHP_EOL, 197 $this->getName()); 198 } 199 } else { 200 // Insert and assert values with CONSISTENCY_ALL 201 $this->insert($statementType, $policy, 0, self::NUMBER_OF_INSERTS, \Cassandra::CONSISTENCY_ALL); 202 $this->assert($policy, 0, self::NUMBER_OF_INSERTS, \Cassandra::CONSISTENCY_ALL); 203 204 // Insert and assert values with CONSISTENCY_QUORUM 205 $this->insert($statementType, $policy, 1, self::NUMBER_OF_INSERTS, \Cassandra::CONSISTENCY_QUORUM); 206 $this->assert($policy, 1, self::NUMBER_OF_INSERTS, \Cassandra::CONSISTENCY_QUORUM); 207 208 // Insert and assert values with CONSISTENCY_TWO 209 $this->insert($statementType, $policy, 2, self::NUMBER_OF_INSERTS, \Cassandra::CONSISTENCY_TWO); 210 $this->assert($policy, 2, self::NUMBER_OF_INSERTS, \Cassandra::CONSISTENCY_TWO); 211 } 212 } 213 } 214 215 /** 216 * Statement execution supports fallthrough retry policy (write exception). 217 * 218 * This test will ensure that the PHP driver supports the ability to 219 * provide any exception that occurs when executing statements. This test 220 * will ensure that a WriteTimeoutException occurs when a consistency level 221 * cannot be achieved. 222 * 223 * @test 224 * @ticket PHP-60 225 * 226 * @cassandra-version-2.0 227 * 228 * @expectedException \Cassandra\Exception\UnavailableException 229 * @expectedExceptionMessageRegExp |Cannot achieve consistency level .*| 230 */ 231 public function testFallThroughPolicyWrite() { 232 // Create the retry policy (RF = 3 with 1 node) 233 $policy = new RetryPolicy\Fallthrough(); 234 235 // Iterate over each statement type 236 foreach (range(1, 3) as $statementType) { 237 // Determine if the statement type should be skipped 238 if ($statementType == self::BATCH_STATEMENT 239 && version_compare(\Cassandra::CPP_DRIVER_VERSION, "2.2.3") < 0) { 240 if (Integration::isDebug()) { 241 fprintf(STDOUT, "Skipping Batch Statements in %s: Issue fixed in DataStax C/C++ v2.2.3" . PHP_EOL, 242 $this->getName()); 243 } 244 } else { 245 // Create an exception during write 246 $this->insert($statementType, $policy, 0, self::NUMBER_OF_INSERTS, \Cassandra::CONSISTENCY_ALL); 247 } 248 } 249 } 250 251 /** 252 * Statement execution supports fallthrough retry policy (read exception). 253 * 254 * This test will ensure that the PHP driver supports the ability to 255 * provide any exception that occurs when executing statements. This test 256 * will ensure that a ReadTimeoutException occurs when a consistency level 257 * cannot be achieved. 258 * 259 * @test 260 * @ticket PHP-60 261 * 262 * @cassandra-version-2.0 263 * 264 * @expectedException \Cassandra\Exception\UnavailableException 265 * @expectedExceptionMessageRegExp |Cannot achieve consistency level .*| 266 */ 267 public function testFallThroughPolicyRead() { 268 // Create the retry policy (RF = 3 with 1 node) 269 $policy = new RetryPolicy\Fallthrough(); 270 271 // Iterate over each statement type 272 foreach (range(1, 3) as $statementType) { 273 // Determine if the statement type should be skipped 274 if ($statementType == self::BATCH_STATEMENT 275 && version_compare(\Cassandra::CPP_DRIVER_VERSION, "2.2.3") < 0) { 276 if (Integration::isDebug()) { 277 fprintf(STDOUT, "Skipping Batch Statements in %s: Issue fixed in DataStax C/C++ v2.2.3" . PHP_EOL, 278 $this->getName()); 279 } 280 } else { 281 // Create an exception during read 282 $this->insert($statementType, $policy, 0, self::NUMBER_OF_INSERTS, \Cassandra::CONSISTENCY_ONE); 283 $this->assert($policy, 0, self::NUMBER_OF_INSERTS, \Cassandra::CONSISTENCY_ALL); 284 } 285 } 286 } 287} 288