-
Notifications
You must be signed in to change notification settings - Fork 197
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Simplified the failover mechanism from last commit. Instead of keep using server from last succesful request; always cycle the server list on each request. Changelog: None Signed-off-by: Alf-Rune Siqveland <[email protected]>
- Loading branch information
Showing
8 changed files
with
349 additions
and
199 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,3 +24,6 @@ _testmain.go | |
*.prof | ||
|
||
mender | ||
|
||
# Go test coverage output | ||
*coverage*.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -71,6 +71,15 @@ type ApiRequester interface { | |
Do(req *http.Request) (*http.Response, error) | ||
} | ||
|
||
// MenderServer is a placeholder for a full server definition used when | ||
// multiple servers are given. The fields corresponds to the definitions | ||
// given in menderConfig. | ||
type MenderServer struct { | ||
ServerURL string | ||
// TODO: Move all possible server specific configurations in | ||
// menderConfig over to this struct. (e.g. TenantToken?) | ||
} | ||
|
||
// APIError is an error type returned after receiving an error message from the | ||
// server. It wraps a regular error with the request_id - and if | ||
// the server returns an error message, this is also returned. | ||
|
@@ -118,18 +127,18 @@ type ApiClient struct { | |
} | ||
|
||
// function type for reauthorization closure (see func [email protected]) | ||
type ClientReauthorizeFunc func() (AuthToken, error) | ||
type ClientReauthorizeFunc func(string) (AuthToken, error) | ||
|
||
// function type for setting server (in case of multiple fallover servers) | ||
type ServerManagementFunc func() string | ||
type ServerManagementFunc func() *MenderServer | ||
|
||
// Return a new ApiRequest | ||
func (a *ApiClient) Request(code AuthToken, _nextServerIterator ServerManagementFunc, req ClientReauthorizeFunc) *ApiRequest { | ||
func (a *ApiClient) Request(code AuthToken, nextServerIterator ServerManagementFunc, reauth ClientReauthorizeFunc) *ApiRequest { | ||
return &ApiRequest{ | ||
api: a, | ||
auth: code, | ||
nextServerIterator: _nextServerIterator, | ||
revoke: req, | ||
nextServerIterator: nextServerIterator, | ||
revoke: reauth, | ||
} | ||
} | ||
|
||
|
@@ -148,13 +157,13 @@ type ApiRequest struct { | |
|
||
// tryDo is a wrapper around http.Do that also tries to reauthorize | ||
// on a 401 response (Unauthorized). | ||
func (ar *ApiRequest) tryDo(req *http.Request) (*http.Response, error) { | ||
func (ar *ApiRequest) tryDo(req *http.Request, serverURL string) (*http.Response, error) { | ||
r, err := ar.api.Do(req) | ||
if r != nil && r.StatusCode == http.StatusUnauthorized { | ||
if err == nil && r.StatusCode == http.StatusUnauthorized { | ||
// invalid JWT; most likely the token is expired: | ||
// Try to refresh it and reattempt sending the request | ||
log.Info("Device unauthorized; attempting reauthorization") | ||
if jwt, e := ar.revoke(); e == nil { | ||
if jwt, e := ar.revoke(serverURL); e == nil { | ||
// retry API request with new JWT token | ||
ar.auth = jwt | ||
// check if request had a body | ||
|
@@ -175,29 +184,48 @@ func (ar *ApiRequest) tryDo(req *http.Request) (*http.Response, error) { | |
|
||
// Do is a wrapper for http.Do function for ApiRequests. This function in | ||
// addition to calling http.Do handles client-server authorization header / | ||
// reauthorization, as well as attempting failover servers (if given) if | ||
// reauthorization, as well as attempting failover servers (if given) whenever | ||
// the server "refuse" to serve the request. | ||
func (ar *ApiRequest) Do(req *http.Request) (*http.Response, error) { | ||
if ar.nextServerIterator == nil { | ||
return nil, errors.New("Empty server list!") | ||
} | ||
if req.Header.Get("Authorization") == "" { | ||
// Add JWT to header | ||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", ar.auth)) | ||
} | ||
r, err := ar.tryDo(req) | ||
// if: (failoverservers given) && (error / bad statuscode) | ||
// then: cycle servers and retry request | ||
if (ar.nextServerIterator != nil) && | ||
(r != nil && r.StatusCode >= 400 && r.StatusCode < 600) || (err != nil) { | ||
// incrementally try listed servers | ||
log.Warnf("Server %q does not serve API-Request: %q. %s", | ||
req.URL.Host, req.URL.Path, "Attempting failover servers.") | ||
// do {try next server} while (server refuse to serve) | ||
for serverURL := ar.nextServerIterator(); serverURL != ""; serverURL = ar.nextServerIterator() { | ||
// set new host (server) in request | ||
req.URL.Host = serverURL | ||
req.Host = serverURL | ||
r, err = ar.tryDo(req) | ||
if r != nil && (r.StatusCode < 400 || r.StatusCode >= 600) { | ||
log.Infof("Automatically fell over to server: %q", serverURL) | ||
var r *http.Response | ||
var host string | ||
var err error | ||
|
||
server := ar.nextServerIterator() | ||
for { | ||
// Split host from URL | ||
tmp := strings.Split(server.ServerURL, "://") | ||
if len(tmp) == 1 { | ||
host = tmp[0] | ||
} else { | ||
// (len >= 2) should usually be 2 | ||
host = tmp[1] | ||
} | ||
|
||
req.URL.Host = host | ||
req.Host = host | ||
r, err = ar.tryDo(req, server.ServerURL) | ||
if err == nil && r.StatusCode < 400 { | ||
break | ||
} | ||
prewHost := server.ServerURL | ||
if server = ar.nextServerIterator(); server == nil { | ||
break | ||
} | ||
log.Warnf("Server %q failed to serve request %q. Attempting %q", | ||
prewHost, req.URL.Path, server.ServerURL) | ||
} | ||
if server != nil { | ||
// reset server iterator | ||
for { | ||
if ar.nextServerIterator() == nil { | ||
break | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.