Skip to content

Commit

Permalink
Add support for migrating views with the migration plugin
Browse files Browse the repository at this point in the history
- Add a warning that events, procedures, and triggers are NOT migrated

[#157373092]

Signed-off-by: Andrew Garner <[email protected]>
  • Loading branch information
andrewgilbert12 authored and abg committed Nov 19, 2018
1 parent ead4d13 commit 40563a1
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 36 deletions.
1 change: 1 addition & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions mysql-tools/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ func Migrate(migrator Migrator, args []string) error {
return err
}

log.Printf("Warning: The mysql-tools migrate command will not migrate any triggers, routines or events.")
productName := os.Getenv("RECIPIENT_PRODUCT_NAME")
if productName == "" {
productName = "p.mysql"
Expand Down
12 changes: 12 additions & 0 deletions mysql-tools/plugin/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
package plugin_test

import (
"bytes"
"errors"
"io"
"log"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
Expand All @@ -24,12 +27,17 @@ import (
var _ = Describe("Plugin Commands", func() {
var (
fakeMigrator *pluginfakes.FakeMigrator
logOutput *bytes.Buffer
)

const usage = `Usage: cf mysql-tools migrate [-h] [--no-cleanup] <v1-service-instance> <plan-type>`

BeforeEach(func() {
fakeMigrator = new(pluginfakes.FakeMigrator)
logOutput = &bytes.Buffer{}

w := io.MultiWriter(GinkgoWriter, logOutput)
log.SetOutput(w)
})

Context("Migrate", func() {
Expand All @@ -39,6 +47,10 @@ var _ = Describe("Plugin Commands", func() {
}
Expect(plugin.Migrate(fakeMigrator, args)).To(Succeed())

By("log a message that we don't migrate triggers, routines and events", func() {
Expect(logOutput.String()).To(ContainSubstring(`Warning: The mysql-tools migrate command will not migrate any triggers, routines or events`))
})

By("checking that donor exists", func() {
Expect(fakeMigrator.CheckServiceExistsCallCount()).
To(Equal(1))
Expand Down
95 changes: 90 additions & 5 deletions specs/acceptance/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ var _ = Describe("Migrate Integration Tests", func() {
})

Context("When a valid donor service instance exists", func() {
BeforeEach(func() {
setupStoredCodeFixtures(sourceInstance)
})

AfterEach(func() {
if springAppName != "" {
test_helpers.DeleteApp(springAppName)
Expand Down Expand Up @@ -166,6 +170,10 @@ var _ = Describe("Migrate Integration Tests", func() {
NotTo(BeEmpty(),
"Expected recipient service instance to be TLS enabled, but it was not")
})

By("Verifying the views get migrated with invoker security type and procedures are not migrated", func() {
validateMigratedStoredCode(sourceInstance)
})
})

Context("when the --no-cleanup flag is specified", func() {
Expand Down Expand Up @@ -246,21 +254,57 @@ var _ = Describe("Migrate Integration Tests", func() {
})
})

func createInvalidMigrationState(sourceInstance string) {
func setupStoredCodeFixtures(instanceName string) {
appName := generator.PrefixedRandomName("MYSQL", "INVALID_MIGRATION")
serviceKey := generator.PrefixedRandomName("MYSQL", "SERVICE_KEY")

test_helpers.PushApp(appName, "../assets/spring-music")
test_helpers.BindAppToService(appName, instanceName)
defer func() {
test_helpers.DeleteApp(appName)
test_helpers.AssertAppIsDeleted(appName)
}()

test_helpers.StartApp(appName)

serviceKeyCreds := test_helpers.GetServiceKey(instanceName, serviceKey)
defer test_helpers.DeleteServiceKey(instanceName, serviceKey)

closeTunnel := test_helpers.OpenDatabaseTunnelToApp(63308, appName, serviceKeyCreds)
defer closeTunnel()

dsn := fmt.Sprintf("%s:%s@tcp(127.0.0.1:63308)/%s",
serviceKeyCreds.Username,
serviceKeyCreds.Password,
serviceKeyCreds.Name,
)

db, err := sql.Open("mysql", dsn)
defer db.Close()
Expect(err).NotTo(HaveOccurred())

_, err = db.Exec("CREATE VIEW migrate_view AS SELECT 1")
Expect(err).NotTo(HaveOccurred())

_, err = db.Exec("CREATE PROCEDURE migrate_procedure() BEGIN END")
Expect(err).NotTo(HaveOccurred())
}

func validateMigratedStoredCode(instanceName string) {
appName := generator.PrefixedRandomName("MYSQL", "INVALID_MIGRATION")
sourceServiceKey := generator.PrefixedRandomName("MYSQL", "SERVICE_KEY")
serviceKey := generator.PrefixedRandomName("MYSQL", "SERVICE_KEY")

test_helpers.PushApp(appName, "../assets/spring-music")
test_helpers.BindAppToService(appName, sourceInstance)
test_helpers.BindAppToService(appName, instanceName)
defer func() {
test_helpers.DeleteApp(appName)
test_helpers.AssertAppIsDeleted(appName)
}()

test_helpers.StartApp(appName)

serviceKeyCreds := test_helpers.GetServiceKey(sourceInstance, sourceServiceKey)
defer test_helpers.DeleteServiceKey(sourceInstance, sourceServiceKey)
serviceKeyCreds := test_helpers.GetServiceKey(instanceName, serviceKey)
defer test_helpers.DeleteServiceKey(instanceName, serviceKey)

closeTunnel := test_helpers.OpenDatabaseTunnelToApp(63308, appName, serviceKeyCreds)
defer closeTunnel()
Expand All @@ -270,6 +314,47 @@ func createInvalidMigrationState(sourceInstance string) {
serviceKeyCreds.Password,
serviceKeyCreds.Name,
)

db, err := sql.Open("mysql", dsn)
defer db.Close()
Expect(err).NotTo(HaveOccurred())

var routineCount int
routineCountSQL := `SELECT COUNT(*) FROM information_schema.routines WHERE ROUTINE_SCHEMA = 'service_instance_db'`
Expect(db.QueryRow(routineCountSQL).Scan(&routineCount)).To(Succeed())
Expect(routineCount).To(BeZero())

var viewSecurityType string
checkViewSQL := `SELECT SECURITY_TYPE FROM information_schema.views WHERE table_schema = 'service_instance_db' and table_name = 'migrate_view'`
Expect(db.QueryRow(checkViewSQL).Scan(&viewSecurityType)).To(Succeed())
Expect(viewSecurityType).To(Equal("INVOKER"))
}

func createInvalidMigrationState(instanceName string) {
appName := generator.PrefixedRandomName("MYSQL", "INVALID_MIGRATION")
serviceKey := generator.PrefixedRandomName("MYSQL", "SERVICE_KEY")

test_helpers.PushApp(appName, "../assets/spring-music")
test_helpers.BindAppToService(appName, instanceName)
defer func() {
test_helpers.DeleteApp(appName)
test_helpers.AssertAppIsDeleted(appName)
}()

test_helpers.StartApp(appName)

serviceKeyCreds := test_helpers.GetServiceKey(instanceName, serviceKey)
defer test_helpers.DeleteServiceKey(instanceName, serviceKey)

closeTunnel := test_helpers.OpenDatabaseTunnelToApp(63308, appName, serviceKeyCreds)
defer closeTunnel()

dsn := fmt.Sprintf("%s:%s@tcp(127.0.0.1:63308)/%s",
serviceKeyCreds.Username,
serviceKeyCreds.Password,
serviceKeyCreds.Name,
)

db, err := sql.Open("mysql", dsn)
defer db.Close()
Expect(err).NotTo(HaveOccurred())
Expand Down
35 changes: 31 additions & 4 deletions tasks/migrate/backup_restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ func MySQLDumpCmd(credentials Credentials, schemas ...string) *exec.Cmd {
cmd.Args = append(cmd.Args,
"--max-allowed-packet=1G",
"--single-transaction",
"--routines",
"--events",
"--skip-routines",
"--skip-events",
"--set-gtid-purged=off",
"--skip-triggers",
)

if len(schemas) > 1 {
Expand All @@ -70,18 +71,40 @@ func MySQLCmd(credentials Credentials) *exec.Cmd {
return cmd
}

func CopyData(mysqldump, mysql *exec.Cmd) error {
func ReplaceDefinerCmd() *exec.Cmd {
args := []string{
"-e",
"s/DEFINER=`.*`@`%` SQL SECURITY DEFINER/SQL SECURITY INVOKER/",
}

cmd := exec.Command("sed", args...)
cmd.Stderr = os.Stderr
return cmd
}

func CopyData(mysqldump, replaceDefinerCmd, mysql *exec.Cmd) error {
dumpOut, err := mysqldump.StdoutPipe()
if err != nil {
return errors.Wrap(err, "couldn't pipe the output of mysqldump")
}

mysql.Stdin = dumpOut
replaceDefinerCmd.Stdin = dumpOut

replaceOut, err := replaceDefinerCmd.StdoutPipe()
if err != nil {
return errors.Wrap(err, "couldn't pipe the output of sed")
}

mysql.Stdin = replaceOut

if err := mysqldump.Start(); err != nil {
return errors.Wrap(err, "couldn't start mysqldump")
}

if err := replaceDefinerCmd.Start(); err != nil {
return errors.Wrap(err, "couldn't start sed")
}

if err := mysql.Start(); err != nil {
return errors.Wrap(err, "couldn't start mysql")
}
Expand All @@ -90,6 +113,10 @@ func CopyData(mysqldump, mysql *exec.Cmd) error {
return errors.Wrap(err, "mysql command failed")
}

if err := replaceDefinerCmd.Wait(); err != nil {
return errors.Wrap(err, "sed command failed")
}

if err := mysqldump.Wait(); err != nil {
return errors.Wrap(err, "mysqldump command failed")
}
Expand Down
Loading

0 comments on commit 40563a1

Please sign in to comment.