|
@@ -81,8 +81,9 @@ func TestBoltResiliency(t *testing.T) {
|
|
|
|
|
|
|
|
noticeCandidateServers := make(chan struct{}, 1)
|
|
noticeCandidateServers := make(chan struct{}, 1)
|
|
|
noticeExiting := make(chan struct{}, 1)
|
|
noticeExiting := make(chan struct{}, 1)
|
|
|
- noticeResetDatastore := make(chan struct{}, 1)
|
|
|
|
|
|
|
+ noticeOpenResetDatastore := make(chan struct{}, 1)
|
|
|
noticeDatastoreFailed := make(chan struct{}, 1)
|
|
noticeDatastoreFailed := make(chan struct{}, 1)
|
|
|
|
|
+ noticeFailedResetDatastore := make(chan struct{}, 1)
|
|
|
|
|
|
|
|
err = SetNoticeWriter(NewNoticeReceiver(
|
|
err = SetNoticeWriter(NewNoticeReceiver(
|
|
|
func(notice []byte) {
|
|
func(notice []byte) {
|
|
@@ -113,9 +114,11 @@ func TestBoltResiliency(t *testing.T) {
|
|
|
message := payload["message"].(string)
|
|
message := payload["message"].(string)
|
|
|
var channel chan struct{}
|
|
var channel chan struct{}
|
|
|
if strings.Contains(message, "tryDatastoreOpenDB: reset") {
|
|
if strings.Contains(message, "tryDatastoreOpenDB: reset") {
|
|
|
- channel = noticeResetDatastore
|
|
|
|
|
|
|
+ channel = noticeOpenResetDatastore
|
|
|
} else if strings.Contains(message, "datastore has failed") {
|
|
} else if strings.Contains(message, "datastore has failed") {
|
|
|
channel = noticeDatastoreFailed
|
|
channel = noticeDatastoreFailed
|
|
|
|
|
+ } else if strings.Contains(message, "reset failed datastore") {
|
|
|
|
|
+ channel = noticeFailedResetDatastore
|
|
|
}
|
|
}
|
|
|
if channel != nil {
|
|
if channel != nil {
|
|
|
select {
|
|
select {
|
|
@@ -147,14 +150,15 @@ func TestBoltResiliency(t *testing.T) {
|
|
|
drainNoticeChannels := func() {
|
|
drainNoticeChannels := func() {
|
|
|
drainNoticeChannel(noticeCandidateServers)
|
|
drainNoticeChannel(noticeCandidateServers)
|
|
|
drainNoticeChannel(noticeExiting)
|
|
drainNoticeChannel(noticeExiting)
|
|
|
- drainNoticeChannel(noticeResetDatastore)
|
|
|
|
|
|
|
+ drainNoticeChannel(noticeOpenResetDatastore)
|
|
|
drainNoticeChannel(noticeDatastoreFailed)
|
|
drainNoticeChannel(noticeDatastoreFailed)
|
|
|
|
|
+ drainNoticeChannel(noticeFailedResetDatastore)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Paving sufficient server entries, then truncating the datastore file to
|
|
// Paving sufficient server entries, then truncating the datastore file to
|
|
|
// remove some server entry data, then iterating over all server entries (to
|
|
// remove some server entry data, then iterating over all server entries (to
|
|
|
// produce the CandidateServers output) triggers datastore corruption
|
|
// produce the CandidateServers output) triggers datastore corruption
|
|
|
- // detection and, at start up, reset/recovery.
|
|
|
|
|
|
|
+ // detection and reset/recovery.
|
|
|
|
|
|
|
|
paveServerEntries := func() {
|
|
paveServerEntries := func() {
|
|
|
for i := 0; i < serverEntryCount; i++ {
|
|
for i := 0; i < serverEntryCount; i++ {
|
|
@@ -199,6 +203,27 @@ func TestBoltResiliency(t *testing.T) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ corruptDataStore := func() {
|
|
|
|
|
+ filename := filepath.Join(testDataDirName, "ca.psiphon.PsiphonTunnel.tunnel-core", "datastore", "psiphon.boltdb")
|
|
|
|
|
+ file, err := os.OpenFile(filename, os.O_RDWR, 0666)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("OpenFile failed: %s", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ defer file.Close()
|
|
|
|
|
+ fileInfo, err := file.Stat()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("Stat failed: %s", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ _, err = file.WriteAt(prng.Bytes(int(fileInfo.Size())), 0)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("WriteAt failed: %s", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ err = file.Sync()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ t.Fatalf("Sync failed: %s", err)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
truncateDataStore := func() {
|
|
truncateDataStore := func() {
|
|
|
filename := filepath.Join(testDataDirName, "ca.psiphon.PsiphonTunnel.tunnel-core", "datastore", "psiphon.boltdb")
|
|
filename := filepath.Join(testDataDirName, "ca.psiphon.PsiphonTunnel.tunnel-core", "datastore", "psiphon.boltdb")
|
|
|
file, err := os.OpenFile(filename, os.O_RDWR, 0666)
|
|
file, err := os.OpenFile(filename, os.O_RDWR, 0666)
|
|
@@ -239,19 +264,19 @@ func TestBoltResiliency(t *testing.T) {
|
|
|
|
|
|
|
|
drainNoticeChannels()
|
|
drainNoticeChannels()
|
|
|
|
|
|
|
|
- // Truncate datastore file before running controller; expect a datastore
|
|
|
|
|
|
|
+ // Corrupt datastore file before running controller; expect a datastore
|
|
|
// "reset" notice on OpenDataStore.
|
|
// "reset" notice on OpenDataStore.
|
|
|
|
|
|
|
|
t.Logf("test: recover from datastore corrupted before opening")
|
|
t.Logf("test: recover from datastore corrupted before opening")
|
|
|
|
|
|
|
|
- truncateDataStore()
|
|
|
|
|
|
|
+ corruptDataStore()
|
|
|
|
|
|
|
|
err = OpenDataStore(clientConfig)
|
|
err = OpenDataStore(clientConfig)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
t.Fatalf("OpenDataStore failed: %s", err)
|
|
t.Fatalf("OpenDataStore failed: %s", err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- <-noticeResetDatastore
|
|
|
|
|
|
|
+ <-noticeOpenResetDatastore
|
|
|
|
|
|
|
|
if !canTruncateOpenDataStore {
|
|
if !canTruncateOpenDataStore {
|
|
|
CloseDataStore()
|
|
CloseDataStore()
|
|
@@ -262,7 +287,7 @@ func TestBoltResiliency(t *testing.T) {
|
|
|
|
|
|
|
|
// Truncate datastore while running the controller. First, complete one
|
|
// Truncate datastore while running the controller. First, complete one
|
|
|
// successful data scan (CandidateServers). The next scan should trigger a
|
|
// successful data scan (CandidateServers). The next scan should trigger a
|
|
|
- // datastore "failed" notice.
|
|
|
|
|
|
|
+ // datastore "failed" notice and datastore reset.
|
|
|
|
|
|
|
|
t.Logf("test: detect corrupt datastore while running")
|
|
t.Logf("test: detect corrupt datastore while running")
|
|
|
|
|
|
|
@@ -274,6 +299,8 @@ func TestBoltResiliency(t *testing.T) {
|
|
|
|
|
|
|
|
<-noticeDatastoreFailed
|
|
<-noticeDatastoreFailed
|
|
|
|
|
|
|
|
|
|
+ <-noticeFailedResetDatastore
|
|
|
|
|
+
|
|
|
<-noticeExiting
|
|
<-noticeExiting
|
|
|
|
|
|
|
|
stopController()
|
|
stopController()
|
|
@@ -282,17 +309,15 @@ func TestBoltResiliency(t *testing.T) {
|
|
|
|
|
|
|
|
drainNoticeChannels()
|
|
drainNoticeChannels()
|
|
|
|
|
|
|
|
- // Restart successfully after previous failure shutdown.
|
|
|
|
|
|
|
+ // Restart successfully after previous failure reset and shutdown.
|
|
|
|
|
|
|
|
- t.Logf("test: after restart, recover from datastore corrupted while running")
|
|
|
|
|
|
|
+ t.Logf("test: after restart, recover from reset datastore")
|
|
|
|
|
|
|
|
err = OpenDataStore(clientConfig)
|
|
err = OpenDataStore(clientConfig)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
t.Fatalf("OpenDataStore failed: %s", err)
|
|
t.Fatalf("OpenDataStore failed: %s", err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- <-noticeResetDatastore
|
|
|
|
|
-
|
|
|
|
|
paveServerEntries()
|
|
paveServerEntries()
|
|
|
|
|
|
|
|
stopController = startController()
|
|
stopController = startController()
|