From daeb96fe0adc133d393e4c6afd6111b146e29f5d Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Mon, 14 Jun 2021 16:24:02 +0200 Subject: [PATCH] postgres: add itest --- .travis.yml | 5 ++ Makefile | 17 ++++- go.mod | 1 + go.sum | 1 + lntest/harness.go | 74 +++++++++++++++---- lntest/itest/lnd_misc_test.go | 4 + lntest/itest/lnd_revocation_test.go | 16 ++++ lntest/itest/lnd_test.go | 6 +- lntest/node.go | 67 ++++++++++++++++- lntest/timeouts.go | 2 +- lntest/timeouts_darwin.go | 2 +- ...timeouts_etcd.go => timeouts_remote_db.go} | 4 +- 12 files changed, 174 insertions(+), 25 deletions(-) rename lntest/{timeouts_etcd.go => timeouts_remote_db.go} (91%) diff --git a/.travis.yml b/.travis.yml index 6c98a4d3..1f34be55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,6 +61,11 @@ jobs: - bash ./scripts/install_bitcoind.sh - make itest-parallel backend=bitcoind dbbackend=etcd + - name: Bitcoind Integration with postgres (txindex enabled) + script: + - bash ./scripts/install_bitcoind.sh + - make itest-parallel backend=bitcoind dbbackend=postgres + - name: Bitcoind Integration (txindex disabled) script: - bash ./scripts/install_bitcoind.sh diff --git a/Makefile b/Makefile index cb81b179..abdc3c2b 100644 --- a/Makefile +++ b/Makefile @@ -187,7 +187,20 @@ scratch: build check: unit itest -itest-only: +db-instance: +ifeq ($(dbbackend),postgres) + # Remove a previous postgres instance if it exists. + docker rm lnd-postgres --force || echo "Starting new postgres container" + + # Start a fresh postgres instance. Allow a maximum of 500 connections. + # This is required for the async benchmark to pass. + docker run --name lnd-postgres -e POSTGRES_PASSWORD=postgres -p 6432:5432 -d postgres -N 500 + + # Wait for the instance to be started. + sleep 3 +endif + +itest-only: db-instance @$(call print, "Running integration tests with ${backend} backend.") rm -rf lntest/itest/*.log lntest/itest/.logs-*; date EXEC_SUFFIX=$(EXEC_SUFFIX) scripts/itest_part.sh 0 1 $(TEST_FLAGS) $(ITEST_FLAGS) @@ -196,7 +209,7 @@ itest: build-itest itest-only itest-race: build-itest-race itest-only -itest-parallel: build-itest +itest-parallel: build-itest db-instance @$(call print, "Running tests") rm -rf lntest/itest/*.log lntest/itest/.logs-*; date EXEC_SUFFIX=$(EXEC_SUFFIX) echo "$$(seq 0 $$(expr $(ITEST_PARALLELISM) - 1))" | xargs -P $(ITEST_PARALLELISM) -n 1 -I {} scripts/itest_part.sh {} $(NUM_ITEST_TRANCHES) $(TEST_FLAGS) $(ITEST_FLAGS) diff --git a/go.mod b/go.mod index 88d4aea0..e7cce191 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 + github.com/jackc/pgx/v4 v4.13.0 github.com/jackpal/gateway v1.0.5 github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad github.com/jedib0t/go-pretty v4.3.0+incompatible diff --git a/go.sum b/go.sum index eb6fc86a..08479789 100644 --- a/go.sum +++ b/go.sum @@ -363,6 +363,7 @@ github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570= github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3 h1:JnPg/5Q9xVJGfjsO5CPUOjnJps1JaRUm8I9FXVCFK94= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= diff --git a/lntest/harness.go b/lntest/harness.go index 994be64c..1b03a4ef 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -88,6 +88,7 @@ type DatabaseBackend int const ( BackendBbolt DatabaseBackend = iota BackendEtcd + BackendPostgres ) // NewNetworkHarness creates a new network test harness. @@ -1641,36 +1642,77 @@ func (n *NetworkHarness) BackupDb(hn *HarnessNode) error { return errors.New("backup already created") } - // Backup files. - tempDir, err := ioutil.TempDir("", "past-state") + restart, err := n.SuspendNode(hn) if err != nil { - return fmt.Errorf("unable to create temp db folder: %v", err) + return err } - if err := copyAll(tempDir, hn.DBDir()); err != nil { - return fmt.Errorf("unable to copy database files: %v", err) + if hn.postgresDbName != "" { + // Backup database. + backupDbName := hn.postgresDbName + "_backup" + err := executePgQuery( + "CREATE DATABASE " + backupDbName + " WITH TEMPLATE " + + hn.postgresDbName, + ) + if err != nil { + return err + } + } else { + // Backup files. + tempDir, err := ioutil.TempDir("", "past-state") + if err != nil { + return fmt.Errorf("unable to create temp db folder: %v", + err) + } + + if err := copyAll(tempDir, hn.DBDir()); err != nil { + return fmt.Errorf("unable to copy database files: %v", + err) + } + + hn.backupDbDir = tempDir } - hn.backupDbDir = tempDir + err = restart() + if err != nil { + return err + } return nil } // RestoreDb restores a database backup. func (n *NetworkHarness) RestoreDb(hn *HarnessNode) error { - if hn.backupDbDir == "" { - return errors.New("no database backup created") - } + if hn.postgresDbName != "" { + // Restore database. + backupDbName := hn.postgresDbName + "_backup" + err := executePgQuery( + "DROP DATABASE " + hn.postgresDbName, + ) + if err != nil { + return err + } + err = executePgQuery( + "ALTER DATABASE " + backupDbName + " RENAME TO " + hn.postgresDbName, + ) + if err != nil { + return err + } + } else { + // Restore files. + if hn.backupDbDir == "" { + return errors.New("no database backup created") + } - // Restore files. - if err := copyAll(hn.DBDir(), hn.backupDbDir); err != nil { - return fmt.Errorf("unable to copy database files: %v", err) - } + if err := copyAll(hn.DBDir(), hn.backupDbDir); err != nil { + return fmt.Errorf("unable to copy database files: %v", err) + } - if err := os.RemoveAll(hn.backupDbDir); err != nil { - return fmt.Errorf("unable to remove backup dir: %v", err) + if err := os.RemoveAll(hn.backupDbDir); err != nil { + return fmt.Errorf("unable to remove backup dir: %v", err) + } + hn.backupDbDir = "" } - hn.backupDbDir = "" return nil } diff --git a/lntest/itest/lnd_misc_test.go b/lntest/itest/lnd_misc_test.go index 3d2c877e..0046f0ab 100644 --- a/lntest/itest/lnd_misc_test.go +++ b/lntest/itest/lnd_misc_test.go @@ -927,6 +927,10 @@ func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("unable to copy database files: %v", err) } + // Reconnect the peers after the restart that was needed for the db + // backup. + net.EnsureConnected(t.t, carol, node) + // Finally, send more payments from , using the remaining // payment hashes. err = completePaymentRequests( diff --git a/lntest/itest/lnd_revocation_test.go b/lntest/itest/lnd_revocation_test.go index d26356b8..4846850b 100644 --- a/lntest/itest/lnd_revocation_test.go +++ b/lntest/itest/lnd_revocation_test.go @@ -122,6 +122,10 @@ func testRevokedCloseRetribution(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("unable to copy database files: %v", err) } + // Reconnect the peers after the restart that was needed for the db + // backup. + net.EnsureConnected(t.t, carol, net.Bob) + // Finally, send payments from Carol to Bob, consuming Bob's remaining // payment hashes. err = completePaymentRequests( @@ -338,6 +342,10 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness t.Fatalf("unable to copy database files: %v", err) } + // Reconnect the peers after the restart that was needed for the db + // backup. + net.EnsureConnected(t.t, dave, carol) + // Finally, send payments from Dave to Carol, consuming Carol's // remaining payment hashes. err = completePaymentRequests( @@ -617,6 +625,10 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, t.Fatalf("unable to copy database files: %v", err) } + // Reconnect the peers after the restart that was needed for the db + // backup. + net.EnsureConnected(t.t, dave, carol) + // Finally, send payments from Dave to Carol, consuming Carol's // remaining payment hashes. err = completePaymentRequests( @@ -1036,6 +1048,10 @@ func testRevokedCloseRetributionAltruistWatchtowerCase( t.Fatalf("unable to copy database files: %v", err) } + // Reconnect the peers after the restart that was needed for the db + // backup. + net.EnsureConnected(t.t, dave, carol) + // Finally, send payments from Dave to Carol, consuming Carol's remaining // payment hashes. err = completePaymentRequests( diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index 01468ee6..0850b8c2 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -44,7 +44,8 @@ var ( ) // dbBackendFlag specifies the backend to use - dbBackendFlag = flag.String("dbbackend", "bbolt", "Database backend (bbolt, etcd)") + dbBackendFlag = flag.String("dbbackend", "bbolt", "Database backend "+ + "(bbolt, etcd, postgres)") ) // getTestCaseSplitTranche returns the sub slice of the test cases that should @@ -153,6 +154,9 @@ func TestLightningNetworkDaemon(t *testing.T) { case "etcd": dbBackend = lntest.BackendEtcd + case "postgres": + dbBackend = lntest.BackendPostgres + default: require.Fail(t, "unknown db backend") } diff --git a/lntest/node.go b/lntest/node.go index 856f39bb..b497948b 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -3,6 +3,7 @@ package lntest import ( "bytes" "context" + "crypto/rand" "encoding/hex" "encoding/json" "flag" @@ -26,6 +27,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/go-errors/errors" + "github.com/jackc/pgx/v4/pgxpool" "github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" @@ -63,6 +65,8 @@ const ( // NeutrinoBackendName is the name of the neutrino backend. NeutrinoBackendName = "neutrino" + + postgresDsn = "postgres://postgres:postgres@localhost:6432/%s?sslmode=disable" ) var ( @@ -94,6 +98,10 @@ var ( ) ) +func postgresDatabaseDsn(dbName string) string { + return fmt.Sprintf(postgresDsn, dbName) +} + // NextAvailablePort returns the first port that is available for listening by // a new node. It panics if no port is found and the maximum available TCP port // is reached. @@ -223,7 +231,8 @@ type NodeConfig struct { FeeURL string - DbBackend DatabaseBackend + DbBackend DatabaseBackend + PostgresDsn string } func (cfg NodeConfig) P2PAddr() string { @@ -311,7 +320,8 @@ func (cfg NodeConfig) genArgs() []string { args = append(args, "--accept-amp") } - if cfg.DbBackend == BackendEtcd { + switch cfg.DbBackend { + case BackendEtcd: args = append(args, "--db.backend=etcd") args = append(args, "--db.etcd.embedded") args = append( @@ -332,6 +342,10 @@ func (cfg NodeConfig) genArgs() []string { path.Join(cfg.LogDir, "etcd.log"), ), ) + + case BackendPostgres: + args = append(args, "--db.backend=postgres") + args = append(args, "--db.postgres.dsn="+cfg.PostgresDsn) } if cfg.FeeURL != "" { @@ -421,6 +435,10 @@ type HarnessNode struct { // backupDbDir is the path where a database backup is stored, if any. backupDbDir string + + // postgresDbName is the name of the postgres database where lnd data is + // stored in. + postgresDbName string } // Assert *HarnessNode implements the lnrpc.LightningClient interface. @@ -456,6 +474,17 @@ func newNode(cfg NodeConfig) (*HarnessNode, error) { // enabled. cfg.AcceptKeySend = true + // Create temporary database. + var dbName string + if cfg.DbBackend == BackendPostgres { + var err error + dbName, err = createTempPgDb() + if err != nil { + return nil, err + } + cfg.PostgresDsn = postgresDatabaseDsn(dbName) + } + numActiveNodesMtx.Lock() nodeNum := numActiveNodes numActiveNodes++ @@ -472,9 +501,43 @@ func newNode(cfg NodeConfig) (*HarnessNode, error) { closeChanWatchers: make(map[wire.OutPoint][]chan struct{}), policyUpdates: policyUpdateMap{}, + + postgresDbName: dbName, }, nil } +func createTempPgDb() (string, error) { + // Create random database name. + randBytes := make([]byte, 8) + _, err := rand.Read(randBytes) + if err != nil { + return "", err + } + dbName := "itest_" + hex.EncodeToString(randBytes) + + // Create database. + err = executePgQuery("CREATE DATABASE " + dbName) + if err != nil { + return "", err + } + + return dbName, nil +} + +func executePgQuery(query string) error { + pool, err := pgxpool.Connect( + context.Background(), + postgresDatabaseDsn("postgres"), + ) + if err != nil { + return fmt.Errorf("unable to connect to database: %v", err) + } + defer pool.Close() + + _, err = pool.Exec(context.Background(), query) + return err +} + // NewMiner creates a new miner using btcd backend. The baseLogDir specifies // the miner node's log dir. When tests are finished, during clean up, its log // files, including any compressed log files from logrotate, are copied to diff --git a/lntest/timeouts.go b/lntest/timeouts.go index 0070c49e..d4ca9555 100644 --- a/lntest/timeouts.go +++ b/lntest/timeouts.go @@ -1,4 +1,4 @@ -// +build !darwin,!kvdb_etcd +// +build !darwin,!kvdb_etcd,!kvdb_postgres package lntest diff --git a/lntest/timeouts_darwin.go b/lntest/timeouts_darwin.go index 4c4bdfe4..1382bc2f 100644 --- a/lntest/timeouts_darwin.go +++ b/lntest/timeouts_darwin.go @@ -1,4 +1,4 @@ -// +build darwin,!kvdb_etcd +// +build darwin,!kvdb_etcd,!kvdb_postgres package lntest diff --git a/lntest/timeouts_etcd.go b/lntest/timeouts_remote_db.go similarity index 91% rename from lntest/timeouts_etcd.go rename to lntest/timeouts_remote_db.go index 28a24830..c0c16cbc 100644 --- a/lntest/timeouts_etcd.go +++ b/lntest/timeouts_remote_db.go @@ -1,4 +1,4 @@ -// +build kvdb_etcd +// +build kvdb_etcd kvdb_postgres package lntest @@ -23,7 +23,7 @@ const ( // AsyncBenchmarkTimeout is the timeout used when running the async // payments benchmark. - AsyncBenchmarkTimeout = 2 * time.Minute + AsyncBenchmarkTimeout = 3 * time.Minute // NodeStartTimeout is the timeout value when waiting for a node to // become fully started.