e15b908983
* EE-319: backup endpoint (#193) * feat(backup): * add an orbiter to block writes while backup * add backup handler * add an ability to tar.gz a dir * add aes encryption support * EE-320: restore endpoint (#196) * feat(backup): * add restore handler * re-init system state after restore * feat(backup): Update server to respect readonly lock (#199) * feat(backup): EE-322 Add backup and restore screen (#198) Co-authored-by: Simon Meng <simon.meng@portainer.io> * name archive as portainer-backup_yyyy-mm-dd_hh-mm-ss * backup custom templates and edge jobs * restart http and proxy servers after restore to re-init internal state * feat(backup): EE-322 hide password field if password protect toggle is off * feat(backup): EE-322 add tooltip for password field of restore backup * feat(backup): EE-322 wait for backend restart after restoring * Shutdown background go-routines * changed restore err message when cannot extract * fix: symlinks are ignored from backups * replace single admin check with a restartable monitor (#238) * clean log Co-authored-by: Maxime Bajeux <max.bajeux@gmail.com> Co-authored-by: cong meng <mcpacino@gmail.com> Co-authored-by: Simon Meng <simon.meng@portainer.io>
219 lines
5.3 KiB
Go
219 lines
5.3 KiB
Go
package offlinegate
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func Test_canLockAndUnlock(t *testing.T) {
|
|
o := NewOfflineGate()
|
|
|
|
unlock := o.Lock()
|
|
unlock()
|
|
}
|
|
|
|
func Test_hasToBeUnlockedToLockAgain(t *testing.T) {
|
|
// scenario:
|
|
// 1. first routine starts and locks the gate
|
|
// 2. first routine starts a second and wait for the second to start
|
|
// 3. second start but waits for the gate to be released
|
|
// 4. first continues and unlocks the gate, when done
|
|
// 5. second be able to continue
|
|
// 6. second lock the gate, does the job and unlocks it
|
|
|
|
o := NewOfflineGate()
|
|
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(2)
|
|
|
|
result := make([]string, 0, 2)
|
|
go func() {
|
|
unlock := o.Lock()
|
|
defer unlock()
|
|
waitForSecondToStart := sync.WaitGroup{}
|
|
waitForSecondToStart.Add(1)
|
|
go func() {
|
|
waitForSecondToStart.Done()
|
|
unlock := o.Lock()
|
|
defer unlock()
|
|
result = append(result, "second")
|
|
wg.Done()
|
|
}()
|
|
waitForSecondToStart.Wait()
|
|
result = append(result, "first")
|
|
wg.Done()
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
if len(result) != 2 || result[0] != "first" || result[1] != "second" {
|
|
t.Error("Second call have disresregarded a raised lock")
|
|
}
|
|
|
|
}
|
|
|
|
func Test_waitChannelWillBeEmpty_ifGateIsUnlocked(t *testing.T) {
|
|
o := NewOfflineGate()
|
|
|
|
signalingCh := o.Watch()
|
|
if signalingCh != nil {
|
|
t.Error("Signaling channel should be empty")
|
|
}
|
|
}
|
|
|
|
func Test_startWaitingForSignal_beforeGateGetsUnlocked(t *testing.T) {
|
|
// scenario:
|
|
// 1. main routing locks the gate and waits for a consumer to start up
|
|
// 2. consumer starts up, notifies main and begins waiting for the gate to be unlocked
|
|
// 3. main unlocks the gate
|
|
// 4. consumer be able to continue
|
|
|
|
o := NewOfflineGate()
|
|
unlock := o.Lock()
|
|
|
|
signalingCh := o.Watch()
|
|
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(1)
|
|
readerIsReady := sync.WaitGroup{}
|
|
readerIsReady.Add(1)
|
|
|
|
go func(t *testing.T) {
|
|
readerIsReady.Done()
|
|
|
|
// either wait for a signal or timeout
|
|
select {
|
|
case <-signalingCh:
|
|
case <-time.After(10 * time.Second):
|
|
t.Error("Failed to wait for a signal, exit by timeout")
|
|
}
|
|
wg.Done()
|
|
}(t)
|
|
|
|
readerIsReady.Wait()
|
|
unlock()
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
func Test_startWaitingForSignal_afterGateGetsUnlocked(t *testing.T) {
|
|
// scenario:
|
|
// 1. main routing locks, gets waiting channel and unlocks
|
|
// 2. consumer starts up and begins waiting for the gate to be unlocked
|
|
// 3. consumer gets signal immediately and continues
|
|
|
|
o := NewOfflineGate()
|
|
unlock := o.Lock()
|
|
signalingCh := o.Watch()
|
|
unlock()
|
|
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(1)
|
|
|
|
go func(t *testing.T) {
|
|
// either wait for a signal or timeout
|
|
select {
|
|
case <-signalingCh:
|
|
case <-time.After(10 * time.Second):
|
|
t.Error("Failed to wait for a signal, exit by timeout")
|
|
}
|
|
wg.Done()
|
|
}(t)
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
func Test_waitingMiddleware_executesImmediately_whenNotLocked(t *testing.T) {
|
|
// scenario:
|
|
// 1. create an gate
|
|
// 2. kick off a waiting middleware that will release immediately as gate wasn't locked
|
|
// 3. middleware shouldn't timeout
|
|
|
|
o := NewOfflineGate()
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
response := httptest.NewRecorder()
|
|
|
|
timeout := 2 * time.Second
|
|
start := time.Now()
|
|
o.WaitingMiddleware(timeout, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
elapsed := time.Since(start)
|
|
if elapsed >= timeout {
|
|
t.Error("WaitingMiddleware had likely timeout, when it shouldn't")
|
|
}
|
|
w.Write([]byte("success"))
|
|
})).ServeHTTP(response, request)
|
|
|
|
body, _ := io.ReadAll(response.Body)
|
|
if string(body) != "success" {
|
|
t.Error("Didn't receive expected result from the hanlder")
|
|
}
|
|
}
|
|
|
|
func Test_waitingMiddleware_waitsForTheLockToBeReleased(t *testing.T) {
|
|
// scenario:
|
|
// 1. create an gate and lock it
|
|
// 2. kick off a routing that will unlock the gate after 1 second
|
|
// 3. kick off a waiting middleware that will wait for lock to be eventually released
|
|
// 4. middleware shouldn't timeout
|
|
|
|
o := NewOfflineGate()
|
|
unlock := o.Lock()
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
response := httptest.NewRecorder()
|
|
|
|
go func() {
|
|
time.Sleep(1 * time.Second)
|
|
unlock()
|
|
}()
|
|
|
|
timeout := 10 * time.Second
|
|
start := time.Now()
|
|
o.WaitingMiddleware(timeout, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
elapsed := time.Since(start)
|
|
if elapsed >= timeout {
|
|
t.Error("WaitingMiddleware had likely timeout, when it shouldn't")
|
|
}
|
|
w.Write([]byte("success"))
|
|
})).ServeHTTP(response, request)
|
|
|
|
body, _ := io.ReadAll(response.Body)
|
|
if string(body) != "success" {
|
|
t.Error("Didn't receive expected result from the hanlder")
|
|
}
|
|
}
|
|
|
|
func Test_waitingMiddleware_mayTimeout_whenLockedForTooLong(t *testing.T) {
|
|
/*
|
|
scenario:
|
|
1. create an gate and lock it
|
|
2. kick off a waiting middleware that will wait for lock to be eventually released
|
|
3. because we never unlocked the gate, middleware suppose to timeout
|
|
*/
|
|
o := NewOfflineGate()
|
|
o.Lock()
|
|
|
|
request := httptest.NewRequest(http.MethodPost, "/", nil)
|
|
response := httptest.NewRecorder()
|
|
|
|
timeout := 1 * time.Second
|
|
start := time.Now()
|
|
o.WaitingMiddleware(timeout, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
elapsed := time.Since(start)
|
|
if elapsed < timeout {
|
|
t.Error("WaitingMiddleware suppose to timeout, but it didnt")
|
|
}
|
|
w.Write([]byte("success"))
|
|
})).ServeHTTP(response, request)
|
|
|
|
body, _ := io.ReadAll(response.Body)
|
|
if string(body) != "success" {
|
|
t.Error("Didn't receive expected result from the hanlder")
|
|
}
|
|
}
|