Переглянути джерело

httpd: add an API to get data provider status

Nicola Murino 5 роки тому
батько
коміт
206799ff1c

+ 5 - 0
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

+ 18 - 0
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)

+ 2 - 1
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()
 }

+ 17 - 1
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)

+ 4 - 0
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)
 }
 

+ 10 - 0
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())
 	})

+ 28 - 1
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:

+ 18 - 0
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`.

+ 9 - 0
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()