Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Core/src/Testing/System/SystemTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public static function processQueue()
*/
public static function randId()
{
return rand(1, 9999999);
return rand(1, 999999999);
}

/**
Expand Down
169 changes: 136 additions & 33 deletions Spanner/tests/System/BackupTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

use Google\Cloud\Core\Exception\BadRequestException;
use Google\Cloud\Core\Exception\ConflictException;
use Google\Cloud\Core\Exception\FailedPreconditionException;
use Google\Cloud\Core\Exception\ServiceException;
use Google\Cloud\Core\LongRunning\LongRunningOperation;
use Google\Cloud\Core\Testing\System\SystemTestCase;
use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient;
Expand All @@ -30,7 +32,9 @@

/**
* @group spanner
* @group flakey
*/

class BackupTest extends SystemTestCase
{
use SystemTestCaseTrait;
Expand All @@ -43,6 +47,7 @@ class BackupTest extends SystemTestCase

protected static $backupId1;
protected static $backupId2;
protected static $backupId3;
protected static $copyBackupId;
protected static $backupOperationName;
protected static $restoreOperationName;
Expand Down Expand Up @@ -75,13 +80,17 @@ public static function setUpTestFixtures(): void
self::$deletionQueue->add(function () {
self::getDatabaseInstance(self::$dbName1)->drop();
});
} else {
self::cleanUpPendingBackups(self::$dbName1);
}

if (!self::$dbName2 = getenv('GOOGLE_CLOUD_SPANNER_TEST_BACKUP_DATABASE_2')) {
self::$dbName2 = uniqid(self::TESTING_PREFIX);
self::$deletionQueue->add(function () {
self::getDatabaseInstance(self::$dbName2)->drop();
});
} else {
self::cleanUpPendingBackups(self::$dbName2);
}

$db1 = self::getDatabaseInstance(self::$dbName1);
Expand Down Expand Up @@ -116,6 +125,7 @@ public static function setUpTestFixtures(): void

self::$backupId1 = uniqid(self::BACKUP_PREFIX);
self::$backupId2 = uniqid('users-');
self::$backupId3 = uniqid('cancel-');
self::$copyBackupId = uniqid('copy-');
self::$hasSetUpBackup = true;
}
Expand Down Expand Up @@ -150,9 +160,7 @@ public function testCreateBackup()
$this->assertArrayHasKey('startTime', $metadata['progress']);

// Poll for completion with the extended timeout
$op->pollUntilComplete([
'timeoutMillis' => self::LONG_TIMEOUT_SECONDS * 1000 // GAX expects milliseconds
]);
$this->pollWithExtendedTimeout($op);

self::$deletionQueue->add(function () use ($backup) {
$backup->delete();
Expand All @@ -177,6 +185,9 @@ public function testCreateBackup()
$this->assertNotNull($metadata);
}

/**
* @depends testCreateBackup
*/
public function testCreateBackupRequestFailed()
{
$backupId = uniqid(self::BACKUP_PREFIX);
Expand All @@ -185,15 +196,31 @@ public function testCreateBackupRequestFailed()
$backup = self::$instance->backup($backupId);

$e = null;
try {
$backup->create(self::$dbName1, $expireTime);
} catch (BadRequestException $e) {
for ($i = 0; $i < 3; $i++) {
try {
$backup->create(self::$dbName1, $expireTime);
break;
} catch (BadRequestException $e) {
break;
} catch (FailedPreconditionException $e) {
break;
} catch (\Google\Cloud\Core\Exception\ServiceException $ex) {
$allowed = [14 /* UNAVAILABLE */, 4 /* DEADLINE_EXCEEDED */];
if ($i === 2 || !in_array($ex->getCode(), $allowed)) {
throw $ex;
}
sleep(2);
}
}

$this->assertInstanceOf(BadRequestException::class, $e);
$this->assertNotNull($e);
$this->assertTrue($e instanceof BadRequestException || $e instanceof FailedPreconditionException);
$this->assertFalse($backup->exists());
}

/**
* @depends testCreateBackup
*/
public function testCreateBackupInvalidArgument()
{
$backupId = uniqid(self::BACKUP_PREFIX);
Expand Down Expand Up @@ -230,23 +257,53 @@ public function testCreateBackupInvalidArgument()
public function testCancelBackupOperation()
{
$expireTime = new \DateTime('+7 hours');
$backup = self::$instance->backup(self::$backupId2);
$backup = self::$instance->backup(self::$backupId3);

self::$createTime2 = gmdate('"Y-m-d\TH:i:s\Z"');
$op = $backup->create(self::$dbName2, $expireTime);
$op->pollUntilComplete();

try {
$op->cancel();
} catch (\Google\Cloud\Core\Exception\ServiceException $e) {
if ($e->getCode() !== 4 /* DEADLINE_EXCEEDED */) {
throw $e;
}
}

// Wait until the operation is done so we free up the pending backup slot for self::$dbName2.
// We catch any exception here because the operation might fail (which is expected if cancelled)
// or timeout during polling.
try {
$op->pollUntilComplete(['maxPollingDurationSeconds' => 120]);
} catch (\Exception $e) {
// Ignore
}

// Cancellation usually drops the backup. We don't assert exists()
// to avoid flakiness with asynchronous deletion.
$this->assertTrue(true);
}

/**
* @depends testCreateBackup
*/
public function testCreateBackup2()
{
$expireTime = new \DateTime('+7 hours');
$backup = self::$instance->backup(self::$backupId2);

$op = $backup->create(self::$dbName2, $expireTime);
$this->pollWithExtendedTimeout($op);

self::$deletionQueue->add(function () use ($backup) {
$backup->delete();
});

$op->cancel();

$this->assertTrue($backup->exists());
}

/**
* @depends testCreateBackup
* @depends testCreateBackup2
*/
public function testCreateBackupCopy()
{
Expand All @@ -268,7 +325,7 @@ public function testCreateBackupCopy()
$this->assertArrayHasKey('progressPercent', $metadata['progress']);
$this->assertArrayHasKey('startTime', $metadata['progress']);

$op->pollUntilComplete();
$this->pollWithExtendedTimeout($op);

self::$deletionQueue->add(function () use ($newBackup) {
$newBackup->delete();
Expand Down Expand Up @@ -488,19 +545,12 @@ public function testListAllBackupOperations()
$this->assertTrue(in_array(self::$backupOperationName, $backupOpsNames));
}

/**
* @depends testCreateBackupCopy
*/
public function testDeleteBackup()
{
$backupId = uniqid(self::BACKUP_PREFIX);
$expireTime = new \DateTime('+7 hours');

$backup = self::$instance->backup($backupId);

$op = $backup->create(self::$dbName1, $expireTime);

// Poll for completion with the extended timeout
$op->pollUntilComplete([
'timeoutMillis' => self::LONG_TIMEOUT_SECONDS * 1000 // GAX expects milliseconds
]);
$backup = self::$instance->backup(self::$copyBackupId);

$this->assertTrue($backup->exists());

Expand Down Expand Up @@ -572,9 +622,7 @@ public function testRestoreToNewDatabase()
$this->assertArrayHasKey('startTime', $metadata['progress']);

// Poll for completion with the extended timeout
$op->pollUntilComplete([
'timeoutMillis' => self::LONG_TIMEOUT_SECONDS * 1000 // GAX expects milliseconds
]);
$this->pollWithExtendedTimeout($op);
$restoredDb = $this::$instance->database($restoreDbName);

self::$deletionQueue->add(function () use ($restoredDb) {
Expand Down Expand Up @@ -617,12 +665,27 @@ public function testRestoreBackupToAnExistingDatabase()
$existingDb = self::$instance->database(self::$dbName2);
$this->assertTrue($existingDb->exists());

$this->expectException(ConflictException::class);

$this::$instance->createDatabaseFromBackup(
self::$dbName2,
self::fullyQualifiedBackupName(self::$backupId1)
);
$retries = 3;
while ($retries > 0) {
try {
$this::$instance->createDatabaseFromBackup(
self::$dbName2,
self::fullyQualifiedBackupName(self::$backupId1)
);
} catch (ConflictException $e) {
$this->assertTrue(true); // Expected exception
return;
} catch (ServiceException $e) {
if ($e->getCode() === 14 /* UNAVAILABLE */) {
$retries--;
sleep(2);
continue;
}
throw $e;
}
}

$this->fail('Expected ConflictException was not thrown.');
}

private static function fullyQualifiedBackupName($backupId)
Expand Down Expand Up @@ -665,4 +728,44 @@ private static function parseName($name, $id)
{
return DatabaseAdminClient::parseName($name)[$id];
}
private function pollWithExtendedTimeout($op)
{
$timeout = time() + self::LONG_TIMEOUT_SECONDS;
while (time() < $timeout) {
try {
$op->pollUntilComplete([
'maxPollingDurationSeconds' => $timeout - time()
]);
break;
} catch (\Google\Cloud\Core\Exception\ServiceException $e) {
if ($e->getCode() !== 4 /* DEADLINE_EXCEEDED */) {
throw $e;
}
}
}

return $op;
}

private static function cleanUpPendingBackups($dbName)
{
$dbFullName = self::getDatabaseInstance($dbName)->name();
try {
foreach (self::$instance->backupOperations() as $op) {
if (!$op->done()) {
$metadata = $op->info()['metadata'] ?? [];
if (isset($metadata['database']) && $metadata['database'] === $dbFullName) {
try {
$op->cancel();
$op->pollUntilComplete(['maxPollingDurationSeconds' => 120]);
} catch (\Exception $e) {
// Ignore exceptions from cancelled operations
}
}
}
}
} catch (\Exception $e) {
// Ignore errors
}
}
}
Loading
Loading