From 206799ff1c42e2c42c8390b5d5f556677f2ffeed Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Thu, 14 Nov 2019 18:48:01 +0100 Subject: [PATCH] httpd: add an API to get data provider status --- dataprovider/dataprovider.go | 5 +++++ httpd/api_utils.go | 18 ++++++++++++++++++ httpd/httpd.go | 3 ++- httpd/httpd_test.go | 18 +++++++++++++++++- httpd/internal_test.go | 4 ++++ httpd/router.go | 10 ++++++++++ httpd/schema/openapi.yaml | 29 ++++++++++++++++++++++++++++- scripts/README.md | 18 ++++++++++++++++++ scripts/sftpgo_api_cli.py | 9 +++++++++ 9 files changed, 111 insertions(+), 3 deletions(-) diff --git a/dataprovider/dataprovider.go b/dataprovider/dataprovider.go index e7d400ca..6b078f7f 100644 --- a/dataprovider/dataprovider.go +++ b/dataprovider/dataprovider.go @@ -315,6 +315,11 @@ func GetUserByID(p Provider, ID int64) (User, error) { return p.getUserByID(ID) } +// GetProviderStatus returns an error if the provider is not available +func GetProviderStatus(p Provider) error { + return p.checkAvailability() +} + // Close releases all provider resources. // This method is used in test cases. // Closing an uninitialized provider is not supported diff --git a/httpd/api_utils.go b/httpd/api_utils.go index 7e6ee330..e504bc15 100644 --- a/httpd/api_utils.go +++ b/httpd/api_utils.go @@ -287,6 +287,24 @@ func GetVersion(expectedStatusCode int) (utils.VersionInfo, []byte, error) { return version, body, err } +// GetProviderStatus returns provider status +func GetProviderStatus(expectedStatusCode int) (map[string]interface{}, []byte, error) { + var response map[string]interface{} + var body []byte + resp, err := getHTTPClient().Get(buildURLRelativeToBase(providerStatusPath)) + if err != nil { + return response, body, err + } + defer resp.Body.Close() + err = checkResponse(resp.StatusCode, expectedStatusCode) + if err == nil && (expectedStatusCode == http.StatusOK || expectedStatusCode == http.StatusInternalServerError) { + err = render.DecodeJSON(resp.Body, &response) + } else { + body, _ = getResponseBody(resp) + } + return response, body, err +} + func checkResponse(actual int, expected int) error { if expected != actual { return fmt.Errorf("wrong status code: got %v want %v", actual, expected) diff --git a/httpd/httpd.go b/httpd/httpd.go index 142edb28..856c1d39 100644 --- a/httpd/httpd.go +++ b/httpd/httpd.go @@ -23,6 +23,7 @@ const ( quotaScanPath = "/api/v1/quota_scan" userPath = "/api/v1/user" versionPath = "/api/v1/version" + providerStatusPath = "/api/v1/providerstatus" metricsPath = "/metrics" webBasePath = "/web" webUsersPath = "/web/users" @@ -77,7 +78,7 @@ func (c Conf) Initialize(configDir string) error { Handler: router, ReadTimeout: 300 * time.Second, WriteTimeout: 300 * time.Second, - MaxHeaderBytes: 1 << 20, // 1MB + MaxHeaderBytes: 1 << 16, // 64KB } return httpServer.ListenAndServe() } diff --git a/httpd/httpd_test.go b/httpd/httpd_test.go index 03d6228b..b20c87ff 100644 --- a/httpd/httpd_test.go +++ b/httpd/httpd_test.go @@ -39,6 +39,7 @@ const ( activeConnectionsPath = "/api/v1/connection" quotaScanPath = "/api/v1/quota_scan" versionPath = "/api/v1/version" + providerStatusPath = "/api/v1/providerstatus" metricsPath = "/metrics" webBasePath = "/web" webUsersPath = "/web/users" @@ -429,7 +430,7 @@ func TestStartQuotaScan(t *testing.T) { func TestGetVersion(t *testing.T) { _, _, err := httpd.GetVersion(http.StatusOK) if err != nil { - t.Errorf("unable to get sftp version: %v", err) + t.Errorf("unable to get version: %v", err) } _, _, err = httpd.GetVersion(http.StatusInternalServerError) if err == nil { @@ -437,6 +438,17 @@ func TestGetVersion(t *testing.T) { } } +func TestGetProviderStatus(t *testing.T) { + _, _, err := httpd.GetProviderStatus(http.StatusOK) + if err != nil { + t.Errorf("unable to get provider status: %v", err) + } + _, _, err = httpd.GetProviderStatus(http.StatusBadRequest) + if err == nil { + t.Errorf("get provider status request must succeed, we requested to check a wrong status code") + } +} + func TestGetConnections(t *testing.T) { _, _, err := httpd.GetConnections(http.StatusOK) if err != nil { @@ -513,6 +525,10 @@ func TestProviderErrors(t *testing.T) { if err != nil { t.Errorf("delete user with provider closed must fail: %v", err) } + _, _, err = httpd.GetProviderStatus(http.StatusInternalServerError) + if err != nil { + t.Errorf("get provider status with provider closed must fail: %v", err) + } config.LoadConfig(configDir, "") providerConf := config.GetProviderConf() err = dataprovider.Initialize(providerConf, configDir) diff --git a/httpd/internal_test.go b/httpd/internal_test.go index 2302f023..5939af85 100644 --- a/httpd/internal_test.go +++ b/httpd/internal_test.go @@ -225,6 +225,10 @@ func TestApiCallToNotListeningServer(t *testing.T) { if err == nil { t.Errorf("request to an inactive URL must fail") } + _, _, err = GetProviderStatus(http.StatusOK) + if err == nil { + t.Errorf("request to an inactive URL must fail") + } SetBaseURL(oldBaseURL) } diff --git a/httpd/router.go b/httpd/router.go index 4980abbc..f81893a7 100644 --- a/httpd/router.go +++ b/httpd/router.go @@ -3,6 +3,7 @@ package httpd import ( "net/http" + "github.com/drakkan/sftpgo/dataprovider" "github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/sftpd" "github.com/drakkan/sftpgo/utils" @@ -46,6 +47,15 @@ func initializeRouter(staticFilesPath string) { render.JSON(w, r, utils.GetAppVersion()) }) + router.Get(providerStatusPath, func(w http.ResponseWriter, r *http.Request) { + err := dataprovider.GetProviderStatus(dataProvider) + if err != nil { + sendAPIResponse(w, r, err, "", http.StatusInternalServerError) + } else { + sendAPIResponse(w, r, err, "Alive", http.StatusOK) + } + }) + router.Get(activeConnectionsPath, func(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, sftpd.GetConnectionsStats()) }) diff --git a/httpd/schema/openapi.yaml b/httpd/schema/openapi.yaml index 01fdcc39..c7dfb1f6 100644 --- a/httpd/schema/openapi.yaml +++ b/httpd/schema/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.0.1 info: title: SFTPGo description: 'SFTPGo REST API' - version: 1.1.0 + version: 1.2.0 servers: - url: /api/v1 @@ -22,6 +22,33 @@ paths: type: array items: $ref : '#/components/schemas/VersionInfo' + /providerstatus: + get: + tags: + - providerstatus + summary: Get data provider status + operationId: get_provider_status + responses: + 200: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 200 + message: "Alive" + error: "" + 500: + description: Provider Error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + example: + status: 500 + message: "" + error: "Error description if any" /connection: get: tags: diff --git a/scripts/README.md b/scripts/README.md index 833842b7..649ae7ad 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -277,6 +277,24 @@ Output: } ``` +### Get provider status + +Command: + +``` +python sftpgo_api_cli.py get-provider-status +``` + +Output: + +```json +{ + "error": "", + "message": "Alive", + "status": 200 +} +``` + ### Colors highlight for Windows command prompt If your Windows command prompt does not recognize ANSI/VT100 escape sequences you can download [ANSICON](https://github.com/adoxa/ansicon "ANSICON") extract proper files depending on your Windows OS, and install them using `ansicon -i`. diff --git a/scripts/sftpgo_api_cli.py b/scripts/sftpgo_api_cli.py index e67fc286..e0c5b6c6 100755 --- a/scripts/sftpgo_api_cli.py +++ b/scripts/sftpgo_api_cli.py @@ -25,6 +25,7 @@ class SFTPGoApiRequests: self.quotaScanPath = urlparse.urljoin(baseUrl, '/api/v1/quota_scan') self.activeConnectionsPath = urlparse.urljoin(baseUrl, '/api/v1/connection') self.versionPath = urlparse.urljoin(baseUrl, '/api/v1/version') + self.providerStatusPath = urlparse.urljoin(baseUrl, '/api/v1/providerstatus') self.debug = debug if authType == 'basic': self.auth = requests.auth.HTTPBasicAuth(authUser, authPassword) @@ -125,6 +126,10 @@ class SFTPGoApiRequests: r = requests.get(self.versionPath, auth=self.auth, verify=self.verify) self.printResponse(r) + def getProviderStatus(self): + r = requests.get(self.providerStatusPath, auth=self.auth, verify=self.verify) + self.printResponse(r) + def validDate(s): if not s: @@ -222,6 +227,8 @@ if __name__ == '__main__': parserGetVersion = subparsers.add_parser('get-version', help='Get version details') + parserGetProviderStatus = subparsers.add_parser('get-provider-status', help='Get data provider status') + args = parser.parse_args() api = SFTPGoApiRequests(args.debug, args.base_url, args.auth_type, args.auth_user, args.auth_password, args.secure, @@ -251,4 +258,6 @@ if __name__ == '__main__': api.startQuotaScan(args.username) elif args.command == 'get-version': api.getVersion() + elif args.command == 'get-provider-status': + api.getProviderStatus()