network/e2e/hibernation_test.go

330 lines
9.4 KiB
Go

package e2e
import (
"context"
"fmt"
"os"
"path/filepath"
"testing"
"time"
"github.com/DeBrosOfficial/network/pkg/client"
"github.com/DeBrosOfficial/network/pkg/config"
"github.com/DeBrosOfficial/network/pkg/node"
"go.uber.org/zap"
)
// TestHibernationCycle tests that databases hibernate after idle timeout
func TestHibernationCycle(t *testing.T) {
if testing.Short() {
t.Skip("Skipping e2e test in short mode")
}
// Setup test environment
testDir := filepath.Join(os.TempDir(), fmt.Sprintf("debros_test_hibernate_%d", time.Now().Unix()))
defer os.RemoveAll(testDir)
logger, _ := zap.NewDevelopment()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
// Start 3 nodes with short hibernation timeout
nodes := make([]*node.Node, 3)
for i := 0; i < 3; i++ {
cfg := config.DefaultConfig()
cfg.Node.DataDir = filepath.Join(testDir, fmt.Sprintf("node%d", i+1))
cfg.P2P.ListenAddresses = []string{fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 14001+i)}
cfg.Database.ReplicationFactor = 3
cfg.Database.MaxDatabases = 10
cfg.Database.HibernationTimeout = 10 * time.Second // Short timeout for testing
cfg.Database.PortRangeHTTPStart = 15001 + (i * 100)
cfg.Database.PortRangeHTTPEnd = 15010 + (i * 100)
cfg.Database.PortRangeRaftStart = 17001 + (i * 100)
cfg.Database.PortRangeRaftEnd = 17010 + (i * 100)
if i > 0 {
bootstrapAddr := nodes[0].Host().Addrs()[0].String() + "/p2p/" + nodes[0].Host().ID().String()
cfg.P2P.BootstrapPeers = []string{bootstrapAddr}
}
n, err := node.NewNode(cfg, logger)
if err != nil {
t.Fatalf("Failed to create node %d: %v", i+1, err)
}
if err := n.Start(ctx); err != nil {
t.Fatalf("Failed to start node %d: %v", i+1, err)
}
defer n.Stop()
nodes[i] = n
}
time.Sleep(5 * time.Second)
// Create client
bootstrapAddr := nodes[0].Host().Addrs()[0].String() + "/p2p/" + nodes[0].Host().ID().String()
cli, err := client.NewClient(ctx, client.ClientConfig{
AppName: "testapp",
BootstrapPeers: []string{bootstrapAddr},
})
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
defer cli.Close()
// Create database and write data
db := cli.Database("testdb")
_, err = db.WriteOne("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
_, err = db.WriteOne("INSERT INTO users (name) VALUES ('Alice')")
if err != nil {
t.Fatalf("Failed to insert data: %v", err)
}
t.Log("Data written, waiting for hibernation...")
// Wait for hibernation (timeout + grace period)
time.Sleep(20 * time.Second)
t.Log("Hibernation period elapsed, database should be hibernating")
// Note: In a real test, we would check the metadata store to verify hibernation status
// For now, we just verify the data directory still exists
for i := 0; i < 3; i++ {
dbDir := filepath.Join(testDir, fmt.Sprintf("node%d/testapp_testdb", i+1))
if _, err := os.Stat(dbDir); os.IsNotExist(err) {
t.Errorf("Expected database directory to exist on node %d after hibernation", i+1)
}
}
t.Log("Hibernation cycle test passed")
}
// TestWakeUpCycle tests that hibernated databases wake up on access
func TestWakeUpCycle(t *testing.T) {
if testing.Short() {
t.Skip("Skipping e2e test in short mode")
}
// Setup test environment
testDir := filepath.Join(os.TempDir(), fmt.Sprintf("debros_test_wakeup_%d", time.Now().Unix()))
defer os.RemoveAll(testDir)
logger, _ := zap.NewDevelopment()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
// Start 3 nodes with short hibernation timeout
nodes := make([]*node.Node, 3)
for i := 0; i < 3; i++ {
cfg := config.DefaultConfig()
cfg.Node.DataDir = filepath.Join(testDir, fmt.Sprintf("node%d", i+1))
cfg.P2P.ListenAddresses = []string{fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 14001+i)}
cfg.Database.ReplicationFactor = 3
cfg.Database.MaxDatabases = 10
cfg.Database.HibernationTimeout = 10 * time.Second
cfg.Database.PortRangeHTTPStart = 15001 + (i * 100)
cfg.Database.PortRangeHTTPEnd = 15010 + (i * 100)
cfg.Database.PortRangeRaftStart = 17001 + (i * 100)
cfg.Database.PortRangeRaftEnd = 17010 + (i * 100)
if i > 0 {
bootstrapAddr := nodes[0].Host().Addrs()[0].String() + "/p2p/" + nodes[0].Host().ID().String()
cfg.P2P.BootstrapPeers = []string{bootstrapAddr}
}
n, err := node.NewNode(cfg, logger)
if err != nil {
t.Fatalf("Failed to create node %d: %v", i+1, err)
}
if err := n.Start(ctx); err != nil {
t.Fatalf("Failed to start node %d: %v", i+1, err)
}
defer n.Stop()
nodes[i] = n
}
time.Sleep(5 * time.Second)
// Create client
bootstrapAddr := nodes[0].Host().Addrs()[0].String() + "/p2p/" + nodes[0].Host().ID().String()
cli, err := client.NewClient(ctx, client.ClientConfig{
AppName: "testapp",
BootstrapPeers: []string{bootstrapAddr},
})
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
defer cli.Close()
// Create database and write data
db := cli.Database("testdb")
_, err = db.WriteOne("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
_, err = db.WriteOne("INSERT INTO users (name) VALUES ('Alice')")
if err != nil {
t.Fatalf("Failed to insert initial data: %v", err)
}
t.Log("Waiting for hibernation...")
time.Sleep(20 * time.Second)
t.Log("Attempting to wake up database by querying...")
// Query should trigger wake-up
startTime := time.Now()
rows, err := db.QueryOne("SELECT * FROM users")
wakeupDuration := time.Since(startTime)
if err != nil {
t.Fatalf("Failed to query after hibernation (wake-up failed): %v", err)
}
t.Logf("Wake-up took %v", wakeupDuration)
// Verify data persisted
if !rows.Next() {
t.Fatal("Expected at least one row after wake-up")
}
var id int
var name string
if err := rows.Scan(&id, &name); err != nil {
t.Fatalf("Failed to scan row: %v", err)
}
if name != "Alice" {
t.Errorf("Expected name 'Alice' after wake-up, got '%s'", name)
}
// Verify we can write new data
_, err = db.WriteOne("INSERT INTO users (name) VALUES ('Bob')")
if err != nil {
t.Fatalf("Failed to insert data after wake-up: %v", err)
}
// Verify both records exist
rows, err = db.QueryOne("SELECT COUNT(*) FROM users")
if err != nil {
t.Fatalf("Failed to count rows: %v", err)
}
if !rows.Next() {
t.Fatal("Expected count result")
}
var count int
if err := rows.Scan(&count); err != nil {
t.Fatalf("Failed to scan count: %v", err)
}
if count != 2 {
t.Errorf("Expected 2 rows after wake-up and insert, got %d", count)
}
t.Log("Wake-up cycle test passed")
}
// TestConcurrentHibernation tests multiple databases hibernating simultaneously
func TestConcurrentHibernation(t *testing.T) {
if testing.Short() {
t.Skip("Skipping e2e test in short mode")
}
// Setup test environment
testDir := filepath.Join(os.TempDir(), fmt.Sprintf("debros_test_concurrent_hibernate_%d", time.Now().Unix()))
defer os.RemoveAll(testDir)
logger, _ := zap.NewDevelopment()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
// Start 3 nodes
nodes := make([]*node.Node, 3)
for i := 0; i < 3; i++ {
cfg := config.DefaultConfig()
cfg.Node.DataDir = filepath.Join(testDir, fmt.Sprintf("node%d", i+1))
cfg.P2P.ListenAddresses = []string{fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", 14001+i)}
cfg.Database.ReplicationFactor = 3
cfg.Database.MaxDatabases = 20
cfg.Database.HibernationTimeout = 10 * time.Second
cfg.Database.PortRangeHTTPStart = 15001 + (i * 200)
cfg.Database.PortRangeHTTPEnd = 15050 + (i * 200)
cfg.Database.PortRangeRaftStart = 17001 + (i * 200)
cfg.Database.PortRangeRaftEnd = 17050 + (i * 200)
if i > 0 {
bootstrapAddr := nodes[0].Host().Addrs()[0].String() + "/p2p/" + nodes[0].Host().ID().String()
cfg.P2P.BootstrapPeers = []string{bootstrapAddr}
}
n, err := node.NewNode(cfg, logger)
if err != nil {
t.Fatalf("Failed to create node %d: %v", i+1, err)
}
if err := n.Start(ctx); err != nil {
t.Fatalf("Failed to start node %d: %v", i+1, err)
}
defer n.Stop()
nodes[i] = n
}
time.Sleep(5 * time.Second)
// Create client
bootstrapAddr := nodes[0].Host().Addrs()[0].String() + "/p2p/" + nodes[0].Host().ID().String()
cli, err := client.NewClient(ctx, client.ClientConfig{
AppName: "testapp",
BootstrapPeers: []string{bootstrapAddr},
})
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
defer cli.Close()
// Create multiple databases
dbNames := []string{"db1", "db2", "db3"}
for _, dbName := range dbNames {
db := cli.Database(dbName)
_, err = db.WriteOne(fmt.Sprintf("CREATE TABLE %s_data (id INTEGER PRIMARY KEY, value TEXT)", dbName))
if err != nil {
t.Fatalf("Failed to create table in %s: %v", dbName, err)
}
_, err = db.WriteOne(fmt.Sprintf("INSERT INTO %s_data (value) VALUES ('%s_value')", dbName, dbName))
if err != nil {
t.Fatalf("Failed to insert data in %s: %v", dbName, err)
}
}
t.Log("All databases created, waiting for concurrent hibernation...")
time.Sleep(20 * time.Second)
t.Log("Hibernation period elapsed")
// Verify all data directories still exist
for _, dbName := range dbNames {
for i := 0; i < 3; i++ {
dbDir := filepath.Join(testDir, fmt.Sprintf("node%d/testapp_%s", i+1, dbName))
if _, err := os.Stat(dbDir); os.IsNotExist(err) {
t.Errorf("Expected database directory for %s to exist on node %d", dbName, i+1)
}
}
}
t.Log("Concurrent hibernation test passed")
}