37baabe134
* EE-384: add endpoint to set auto backup (#224) * EE-383: add endpoint to fetch backup settings (#231) * add get backup settings handler * add api docs desc * EE-382: restore from s3 (#233) * EE-381: add GET backup status handler (#234) * EE-385: Add S3 backup execute handler (#237) * add s3 backup execute handler * refactories inside `./api/backup/backup_scheduler.go` and `./api/backup/backup_scheduler.go` * fix tests * EE-375: added backup to S3 form * EE-376: added restore from S3 form * EE-377: Update Home screen to display last backup run status * update backup service with back end endpoints. * restart admin monitor during s3 restores * use go 1.13 * go 1.13 compatibility * EE-375: added cron-validator lib * EE-375: using enum to compare form types * EE-375: validate cron rule field * try fix windows build * EE-375 EE-376 backup and restore forms validation changes * fix(autobackup): update autobackup settings validation rules (#260) * fix(autobackup): automate backup to s3 fe update (#261) * EE-292: fixed typo in property. * EE-292: updated auto backup front end validation. * EE-292: updated lib to validate cron rule in front end * fix dependencies * bumped libcompose version Co-authored-by: Hui <arris_li@hotmail.com> Co-authored-by: Felix Han <felix.han@portainer.io> Co-authored-by: fhanportainer <79428273+fhanportainer@users.noreply.github.com>
218 lines
5.4 KiB
Go
218 lines
5.4 KiB
Go
package offlinegate
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
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)
|
|
|
|
assert.Equal(t, http.StatusRequestTimeout, response.Result().StatusCode, "Request support to timeout waiting for the gate")
|
|
}
|