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 e7d400ca2b15410ae7c8837d8d7f6dec1ee19792..6b078f7f4c89df9b50f8d3fb9f68ca7fe526e499 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 7e6ee33078c20d0c2f8d41cb88f34a83b239bb6a..e504bc15b04d64dfe0c3aada5cf042197af083ec 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 142edb28beecde54b64c02a7b23f913fca515c9b..856c1d39cab0010e8a46a405961f8d72fa3ea1ee 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 03d6228bbbed847b7fd5d8471351f3ed80966f5a..b20c87ff7e8ac0037d2775b97d11de4c697b8c10 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 2302f023b76ef4846f34481138b77ce9194c2044..5939af857df8b1c5cd5b53712ade6c337ac2aa51 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 4980abbcf0ed414e35ca3a249ee4f32feac549d3..f81893a7408159863d5d02737cba4eb6b9b3d32e 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 01fdcc39a1f6898aaf655eb19dbd9155a7767ff8..c7dfb1f6a512e5831305ce21a4ceb61794a294ee 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 833842b7e9b1ea1fb888911a7cf47e8331a46323..649ae7ad66af15c19d6a4fa75723be9de0b710f6 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 e67fc286a9ece15da866d07c0f92ca6cb38bc511..e0c5b6c640d3be63f7bddd0f594fe97ea61b86ab 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()