Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/backups samples with restore #2

Open
wants to merge 12 commits into
base: feature/backups-samples-part-1
Choose a base branch
from
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
39 changes: 38 additions & 1 deletion spanner/spanner_snippets/snippet.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ var (
"listdatabaseoperations": listDatabaseOperations,
"updatebackup": updateBackup,
"deletebackup": deleteBackup,
"restorebackup": restoreBackup,
}
)

Expand Down Expand Up @@ -1926,6 +1927,42 @@ func updateBackup(ctx context.Context, w io.Writer, adminClient *database.Databa

// [END spanner_update_backup]

// [START spanner_restore_backup]

func restoreBackup(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, databaseName string) error {
backupID := "my-backup"
matches := regexp.MustCompile("^(.*)/databases/(.*)$").FindStringSubmatch(databaseName)
if matches == nil || len(matches) != 3 {
return fmt.Errorf("Invalid database id %s", databaseName)
}
instanceName := matches[1]
databaseId := matches[2]
backupName := instanceName + "/backups/" + backupID

restoreOp, err := adminClient.RestoreDatabase(ctx, &adminpb.RestoreDatabaseRequest{
Parent: instanceName,
DatabaseId: databaseId,
Source: &adminpb.RestoreDatabaseRequest_Backup{
Backup: backupName,
},
})
if err != nil {
return err
}
database, err := restoreOp.Wait(ctx)
if err != nil {
return err
}
backupInfoFromRestore := database.RestoreInfo.GetBackupInfo()
if backupInfoFromRestore != nil {
fmt.Fprintf(w, "Restored backup [%s] to database [%s]\n", backupInfoFromRestore.Backup, database.Name)
}

return nil
}

// [END spanner_restore_backup]

// [START spanner_list_backups]

func listBackups(ctx context.Context, w io.Writer, adminClient *database.DatabaseAdminClient, database string) error {
Expand Down Expand Up @@ -2230,7 +2267,7 @@ func main() {
dmlwithtimestamp, dmlwriteread, dmlwrite, dmlwritetxn, querywithparameter, dmlupdatepart,
dmldeletepart, dmlbatchupdate, createtablewithdatatypes, writedatatypesdata, querywitharray,
querywithbool, querywithbytes, querywithdate, querywithfloat, querywithint, querywithstring,
querywithtimestampparameter, createbackup, listbackups, updatebackup, deletebackup
querywithtimestampparameter, createbackup, listbackups, updatebackup, deletebackup, restorebackup

Examples:
spanner_snippets createdatabase projects/my-project/instances/my-instance/databases/example-db
Expand Down
33 changes: 33 additions & 0 deletions spanner/spanner_snippets/snippet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,21 @@ func TestSample(t *testing.T) {
t.Fatal("Spanner instance ref must be in the form of 'projects/PROJECT_ID/instances/INSTANCE_ID'")
}
databaseId := fmt.Sprintf("test-%s", tc.ProjectID)
restoreDatabaseId := fmt.Sprintf("test-restore-%s", tc.ProjectID)

// Maximum length of database name is 30 characters, so trim if the generated name is too long
if len(databaseId) > 30 {
trimmedDatabaseId := databaseId[:30]
t.Logf("Database name '%s' trimmed to '%s'", databaseId, trimmedDatabaseId)
databaseId = trimmedDatabaseId
}
if len(restoreDatabaseId) > 30 {
trimmedDatabaseId := restoreDatabaseId[:30]
t.Logf("Restore database name '%s' trimmed to '%s'", restoreDatabaseId, trimmedDatabaseId)
restoreDatabaseId = trimmedDatabaseId
}
dbName := fmt.Sprintf("%s/databases/%s", instance, databaseId)
restoreDbName := fmt.Sprintf("%s/databases/%s", instance, restoreDatabaseId)

ctx := context.Background()
adminClient, dataClient := createClients(ctx, dbName)
Expand All @@ -59,6 +66,10 @@ func TestSample(t *testing.T) {
t.Logf("database %s exists in state %s. delete result: %v", db.GetName(), db.GetState().String(),
adminClient.DropDatabase(ctx, &adminpb.DropDatabaseRequest{Database: dbName}))
}
if db, err := adminClient.GetDatabase(ctx, &adminpb.GetDatabaseRequest{Name: restoreDbName}); err == nil {
t.Logf("database %s exists in state %s. delete result: %v", db.GetName(), db.GetState().String(),
adminClient.DropDatabase(ctx, &adminpb.DropDatabaseRequest{Database: restoreDbName}))
}

// Check for any backups that were created from that database and delete those as well
backupsIterator := adminClient.ListBackups(ctx, &adminpb.ListBackupsRequest{
Expand Down Expand Up @@ -107,6 +118,12 @@ func TestSample(t *testing.T) {
r.Errorf("DropDatabase(%q): %v", dbName, err)
}
})
testutil.Retry(t, 10, time.Second, func(r *testutil.R) {
err := adminClient.DropDatabase(ctx, &adminpb.DropDatabaseRequest{Database: restoreDbName})
if err != nil {
r.Errorf("DropDatabase(%q): %v", restoreDbName, err)
}
})
}()

// We execute all the commands of the tutorial code. These commands have to be run in a specific
Expand Down Expand Up @@ -296,6 +313,22 @@ func TestSample(t *testing.T) {
assertContains(t, out, "Database operation count: ")
out = runCommand(t, "updatebackup", dbName)
assertContains(t, out, "Updated backup [my-backup]")
out = runCommand(t, "restorebackup", restoreDbName)
assertContains(t, out, "Restored backup [")
assertContains(t, out, "/backups/my-backup]")

// Wait for database to finish optimizing - cannot delete a backup if a database restored from it
for {
restoreDb, err := adminClient.GetDatabase(ctx, &adminpb.GetDatabaseRequest{Name: restoreDbName})
if err != nil {
t.Errorf("GetDatabase(%q): %v", restoreDbName, err)
}
if restoreDb.GetState() != adminpb.Database_READY_OPTIMIZING {
break
}
time.Sleep(1 * time.Minute)
}

out = runCommand(t, "deletebackup", dbName)
assertContains(t, out, "Deleted backup [my-backup]")
}