diff --git a/go.mod b/go.mod
index bbe55d228..dcdc23fce 100644
--- a/go.mod
+++ b/go.mod
@@ -12,6 +12,7 @@ require (
github.com/go-openapi/strfmt v0.19.5
github.com/go-openapi/swag v0.19.8
github.com/go-openapi/validate v0.19.7
+ github.com/gorilla/websocket v1.4.2
github.com/jessevdk/go-flags v1.4.0
github.com/json-iterator/go v1.1.9
github.com/minio/cli v1.22.0
diff --git a/go.sum b/go.sum
index efa3d33dc..cfa9060b2 100644
--- a/go.sum
+++ b/go.sum
@@ -42,6 +42,7 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/aws/aws-sdk-go v1.20.21/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2 h1:M+TYzBcNIRyzPRg66ndEqUMd7oWDmhvdQmaPC6EZNwM=
github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2/go.mod h1:RDu/qcrnpEdJC/p8tx34+YBFqqX71lB7dOX9QE+ZC4M=
@@ -254,6 +255,8 @@ github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk=
github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdRqNr0QAUJTIkQAUtFjg=
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
@@ -351,6 +354,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kurin/blazer v0.5.4-0.20200327014341-8f90a40f8af7 h1:smZXPopqRVVywwzou4WYWvUbJvSAzIDFizfWElpmAqY=
github.com/kurin/blazer v0.5.4-0.20200327014341-8f90a40f8af7/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
diff --git a/pkg/ws/websocket.go b/pkg/ws/websocket.go
new file mode 100644
index 000000000..fbb4b9c6a
--- /dev/null
+++ b/pkg/ws/websocket.go
@@ -0,0 +1,56 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2020 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+// Package ws contains websocket utils for mcs project
+package ws
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/go-openapi/errors"
+ "github.com/minio/mcs/pkg/auth"
+)
+
+// Authenticate validates websocket header and returns mcs jwt claims
+//
+// Authorization Header needs to be like "Authorization Bearer "
+func Authenticate(r *http.Request) (*auth.DecryptedClaims, error) {
+ // Get Auth token
+ var reqToken string
+
+ // Token might come either as a Cookie or as a Header
+ // if not set in cookie, check if it is set on Header.
+ tokenCookie, err := r.Cookie("token")
+ if err != nil {
+ headerToken := r.Header.Get("Authorization")
+ // reqToken should come as "Bearer "
+ splitHeaderToken := strings.Split(headerToken, "Bearer")
+ if len(splitHeaderToken) <= 1 {
+ return nil, errors.New(http.StatusBadRequest, "Authentication not valid")
+ }
+ reqToken = strings.TrimSpace(splitHeaderToken[1])
+ } else {
+ reqToken = strings.TrimSpace(tokenCookie.Value)
+ }
+
+ // Perform authentication before upgrading to a Websocket Connection
+ claims, err := auth.JWTAuthenticate(reqToken)
+ if err != nil {
+ return nil, errors.New(http.StatusUnauthorized, err.Error())
+ }
+ return claims, nil
+}
diff --git a/restapi/admin_trace.go b/restapi/admin_trace.go
new file mode 100644
index 000000000..8ab2f83ba
--- /dev/null
+++ b/restapi/admin_trace.go
@@ -0,0 +1,196 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2020 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package restapi
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net"
+ "net/http"
+ "strings"
+ "sync"
+
+ "github.com/gorilla/websocket"
+ "github.com/minio/minio/pkg/madmin"
+)
+
+// shortTraceMsg Short trace record
+type shortTraceMsg struct {
+ Host string `json:"host"`
+ Time string `json:"time"`
+ Client string `json:"client"`
+ CallStats callStats `json:"callStats"`
+ FuncName string `json:"api"`
+ Path string `json:"path"`
+ Query string `json:"query"`
+ StatusCode int `json:"statusCode"`
+ StatusMsg string `json:"statusMsg"`
+}
+
+type callStats struct {
+ Rx int `json:"rx"`
+ Tx int `json:"tx"`
+ Duration string `json:"duration"`
+ Ttfb string `json:"timeToFirstByte"`
+}
+
+// trace serves madmin.ServiceTraceInfo
+// on a Websocket connection.
+func (wsc *wsClient) trace() {
+ defer func() {
+ log.Println("trace stopped")
+ // close connection after return
+ wsc.conn.close()
+ }()
+ log.Println("trace started")
+
+ err := startTraceInfo(wsc.conn, wsc.madmin)
+ // Send Connection Close Message indicating the Status Code
+ // see https://tools.ietf.org/html/rfc6455#page-45
+ if err != nil {
+ // If connection exceeded read deadline send Close
+ // Message Policy Violation code since we don't want
+ // to let the receiver figure out the read deadline.
+ // This is a generic code designed if there is a
+ // need to hide specific details about the policy.
+ if nErr, ok := err.(net.Error); ok && nErr.Timeout() {
+ wsc.conn.writeMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.ClosePolicyViolation, ""))
+ return
+ }
+ // else, internal server error
+ wsc.conn.writeMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, err.Error()))
+ return
+ }
+ // normal closure
+ wsc.conn.writeMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+}
+
+// startTraceInfo starts trace of the servers
+// by first setting a websocket reader that will
+// check for a heartbeat.
+//
+// A WaitGroup is used to handle goroutines and to ensure
+// all finish in the proper order. If any, sendTraceInfo()
+// or wsReadCheck() returns, trace should end.
+func startTraceInfo(conn WSConn, client MinioAdmin) (mError error) {
+ // a WaitGroup waits for a collection of goroutines to finish
+ wg := sync.WaitGroup{}
+ // a cancel context is needed to end all goroutines used
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ // Set number of goroutines to wait. wg.Wait()
+ // waitsuntil counter is zero (all are done)
+ wg.Add(3)
+ // start go routine for reading websocket heartbeat
+ readErr := wsReadCheck(ctx, &wg, conn)
+ // send Stream of Trace Info to the ws c.connection
+ traceCh := sendTraceInfo(ctx, &wg, conn, client)
+ // If wsReadCheck returns it means that it is not possible to check
+ // ws heartbeat anymore so we stop from doing trace, cancel context
+ // for all goroutines.
+ go func(wg *sync.WaitGroup) {
+ defer wg.Done()
+ if err := <-readErr; err != nil {
+ log.Println("error on wsReadCheck:", err)
+ mError = err
+ }
+ // cancel context for all goroutines.
+ cancel()
+ }(&wg)
+
+ // wait for traceCh to finish
+ if err := <-traceCh; err != nil {
+ mError = err
+ }
+
+ // if traceCh closes for any reason,
+ // cancel context for all goroutines
+ cancel()
+ // wait all goroutines to finish
+ wg.Wait()
+ return mError
+}
+
+// sendTraceInfo sends stream of Trace Info to the ws connection
+func sendTraceInfo(ctx context.Context, wg *sync.WaitGroup, conn WSConn, client MinioAdmin) <-chan error {
+ // decrements the WaitGroup counter
+ // by one when the function returns
+ defer wg.Done()
+ ch := make(chan error)
+ go func(ch chan<- error) {
+ defer close(ch)
+
+ // trace all traffic
+ allTraffic := true
+ // Trace failed requests only
+ errOnly := false
+ // Start listening on all trace activity.
+ traceCh := client.serviceTrace(ctx, allTraffic, errOnly)
+
+ for traceInfo := range traceCh {
+ if traceInfo.Err != nil {
+ log.Println("error on serviceTrace:", traceInfo.Err)
+ ch <- traceInfo.Err
+ return
+ }
+ // Serialize message to be sent
+ traceInfoBytes, err := json.Marshal(shortTrace(&traceInfo))
+ if err != nil {
+ fmt.Println("error on json.Marshal:", err)
+ ch <- err
+ return
+ }
+ // Send Message through websocket connection
+ err = conn.writeMessage(websocket.TextMessage, traceInfoBytes)
+ if err != nil {
+ log.Println("error writeMessage:", err)
+ ch <- err
+ return
+ }
+ }
+ // TODO: verbose
+ }(ch)
+
+ return ch
+}
+
+// shortTrace creates a shorter Trace Info message.
+// Same implementation as github/minio/mc/cmd/admin-trace.go
+func shortTrace(info *madmin.ServiceTraceInfo) shortTraceMsg {
+ t := info.Trace
+ s := shortTraceMsg{}
+
+ s.Time = t.ReqInfo.Time.String()
+ s.Path = t.ReqInfo.Path
+ s.Query = t.ReqInfo.RawQuery
+ s.FuncName = t.FuncName
+ s.StatusCode = t.RespInfo.StatusCode
+ s.StatusMsg = http.StatusText(t.RespInfo.StatusCode)
+ s.CallStats.Duration = t.CallStats.Latency.String()
+ s.CallStats.Rx = t.CallStats.InputBytes
+ s.CallStats.Tx = t.CallStats.OutputBytes
+
+ if host, ok := t.ReqInfo.Headers["Host"]; ok {
+ s.Host = strings.Join(host, "")
+ }
+ cSlice := strings.Split(t.ReqInfo.Client, ":")
+ s.Client = cSlice[0]
+ return s
+}
diff --git a/restapi/admin_trace_test.go b/restapi/admin_trace_test.go
new file mode 100644
index 000000000..b68ec8f95
--- /dev/null
+++ b/restapi/admin_trace_test.go
@@ -0,0 +1,169 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2020 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package restapi
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "testing"
+
+ "github.com/gorilla/websocket"
+ "github.com/minio/minio/pkg/madmin"
+ trace "github.com/minio/minio/pkg/trace"
+ "github.com/stretchr/testify/assert"
+)
+
+// assigning mock at runtime instead of compile time
+var minioServiceTraceMock func(ctx context.Context, allTrace, errTrace bool) <-chan madmin.ServiceTraceInfo
+
+// mock function of listPolicies()
+func (ac adminClientMock) serviceTrace(ctx context.Context, allTrace, errTrace bool) <-chan madmin.ServiceTraceInfo {
+ return minioServiceTraceMock(ctx, allTrace, errTrace)
+}
+
+func TestAdminTrace(t *testing.T) {
+ assert := assert.New(t)
+ adminClient := adminClientMock{}
+ mockWSConn := mockConn{}
+ wsClientMock := wsClientMock{madmin: adminClient}
+ function := "startTraceInfo()"
+
+ testReceiver := make(chan shortTraceMsg, 5)
+ textToReceive := "test"
+ testStreamSize := 5
+
+ // Test-1: Serve Trace with no errors until trace finishes sending
+ // define mock function behavior for minio server Trace
+ minioServiceTraceMock = func(ctx context.Context, allTrace, errTrace bool) <-chan madmin.ServiceTraceInfo {
+ ch := make(chan madmin.ServiceTraceInfo)
+ // Only success, start a routine to start reading line by line.
+ go func(ch chan<- madmin.ServiceTraceInfo) {
+ defer close(ch)
+ lines := make([]int, testStreamSize)
+ // mocking sending 5 lines of info
+ for range lines {
+ info := trace.Info{
+ FuncName: textToReceive,
+ }
+ ch <- madmin.ServiceTraceInfo{Trace: info}
+ }
+ }(ch)
+ return ch
+ }
+ // mock function of conn.ReadMessage(), no error on read
+ connReadMessageMock = func() (messageType int, p []byte, err error) {
+ return 0, []byte{}, nil
+ }
+ writesCount := 1
+ // mock connection WriteMessage() no error
+ connWriteMessageMock = func(messageType int, data []byte) error {
+ // emulate that receiver gets the message written
+ var t shortTraceMsg
+ _ = json.Unmarshal(data, &t)
+ if writesCount == testStreamSize {
+ // for testing we need to close the receiver channel
+ close(testReceiver)
+ return nil
+ }
+ testReceiver <- t
+ writesCount++
+ return nil
+ }
+ if err := startTraceInfo(mockWSConn, wsClientMock.madmin); err != nil {
+ t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
+ }
+ // check that the TestReceiver got the same number of data from trace.
+ for i := range testReceiver {
+ assert.Equal(textToReceive, i.FuncName)
+ }
+
+ // Test-2: if error happens while writing, return error
+ connWriteMessageMock = func(messageType int, data []byte) error {
+ return fmt.Errorf("error on write")
+ }
+ if err := startTraceInfo(mockWSConn, wsClientMock.madmin); assert.Error(err) {
+ assert.Equal("error on write", err.Error())
+ }
+
+ // Test-3: error happens while reading, unexpected Close Error should return error.
+ connWriteMessageMock = func(messageType int, data []byte) error {
+ return nil
+ }
+ // mock function of conn.ReadMessage(), returns unexpected Close Error CloseAbnormalClosure
+ connReadMessageMock = func() (messageType int, p []byte, err error) {
+ return 0, []byte{}, &websocket.CloseError{Code: websocket.CloseAbnormalClosure, Text: ""}
+ }
+ if err := startTraceInfo(mockWSConn, wsClientMock.madmin); assert.Error(err) {
+ assert.Equal("websocket: close 1006 (abnormal closure)", err.Error())
+ }
+
+ // Test-4: error happens while reading, expected Close Error NormalClosure
+ // expected Close Error should not return an error, just end trace.
+ connReadMessageMock = func() (messageType int, p []byte, err error) {
+ return 0, []byte{}, &websocket.CloseError{Code: websocket.CloseNormalClosure, Text: ""}
+ }
+ if err := startTraceInfo(mockWSConn, wsClientMock.madmin); err != nil {
+ t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
+ }
+
+ // Test-5: error happens while reading, expected Close Error CloseGoingAway
+ // expected Close Error should not return an error, just return.
+ connReadMessageMock = func() (messageType int, p []byte, err error) {
+ return 0, []byte{}, &websocket.CloseError{Code: websocket.CloseGoingAway, Text: ""}
+ }
+ if err := startTraceInfo(mockWSConn, wsClientMock.madmin); err != nil {
+ t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
+ }
+
+ // Test-6: error happens while reading, non Close Error Type should be returned as
+ // error
+ connReadMessageMock = func() (messageType int, p []byte, err error) {
+ return 0, []byte{}, fmt.Errorf("error on read")
+ }
+ if err := startTraceInfo(mockWSConn, wsClientMock.madmin); assert.Error(err) {
+ assert.Equal("error on read", err.Error())
+ }
+
+ // Test-7: error happens on serviceTrace Minio, trace should stop
+ // and error shall be returned.
+ minioServiceTraceMock = func(ctx context.Context, allTrace, errTrace bool) <-chan madmin.ServiceTraceInfo {
+ ch := make(chan madmin.ServiceTraceInfo)
+ // Only success, start a routine to start reading line by line.
+ go func(ch chan<- madmin.ServiceTraceInfo) {
+ defer close(ch)
+ lines := make([]int, 2)
+ // mocking sending 5 lines of info
+ for range lines {
+ info := trace.Info{
+ NodeName: "test",
+ }
+ ch <- madmin.ServiceTraceInfo{Trace: info}
+ }
+ ch <- madmin.ServiceTraceInfo{Err: fmt.Errorf("error on trace")}
+ }(ch)
+ return ch
+ }
+ // mock function of conn.ReadMessage(), no error on read, should stay unless
+ // context is done.
+ connReadMessageMock = func() (messageType int, p []byte, err error) {
+ return 0, []byte{}, nil
+ }
+ if err := startTraceInfo(mockWSConn, wsClientMock.madmin); assert.Error(err) {
+ assert.Equal("error on trace", err.Error())
+ }
+}
diff --git a/restapi/client-admin.go b/restapi/client-admin.go
index af8fffd78..54ed2ed2a 100644
--- a/restapi/client-admin.go
+++ b/restapi/client-admin.go
@@ -55,7 +55,7 @@ func NewAdminClient(url, accessKey, secretKey string) (*madmin.AdminClient, *pro
// it also enables an internal trace transport.
var s3AdminNew = mcCmd.NewAdminFactory()
-// Define MinioAdmin interface with all functions to be implemented
+// MinioAdmin interface with all functions to be implemented
// by mock when testing, it should include all MinioAdmin respective api calls
// that are used within this project.
type MinioAdmin interface {
@@ -80,6 +80,7 @@ type MinioAdmin interface {
serverInfo(ctx context.Context) (madmin.InfoMessage, error)
startProfiling(ctx context.Context, profiler madmin.ProfilerType) ([]madmin.StartProfilingResult, error)
stopProfiling(ctx context.Context) (io.ReadCloser, error)
+ serviceTrace(ctx context.Context, allTrace, errTrace bool) <-chan madmin.ServiceTraceInfo
// Service Accounts
addServiceAccount(ctx context.Context, policy *iampolicy.Policy) (mauth.Credentials, error)
}
@@ -197,6 +198,11 @@ func (ac adminClient) stopProfiling(ctx context.Context) (io.ReadCloser, error)
return ac.client.DownloadProfilingData(ctx)
}
+// implements madmin.ServiceTrace()
+func (ac adminClient) serviceTrace(ctx context.Context, allTrace, errTrace bool) <-chan madmin.ServiceTraceInfo {
+ return ac.client.ServiceTrace(ctx, allTrace, errTrace)
+}
+
// implements madmin.AddServiceAccount()
func (ac adminClient) addServiceAccount(ctx context.Context, policy *iampolicy.Policy) (mauth.Credentials, error) {
return ac.client.AddServiceAccount(ctx, policy)
@@ -216,3 +222,14 @@ func newMAdminClient(jwt string) (*madmin.AdminClient, error) {
}
return adminClient, nil
}
+
+func newSuperMAdminClient() (*madmin.AdminClient, error) {
+ endpoint := getMinIOServer()
+ accessKeyID := getAccessKey()
+ secretAccessKey := getSecretKey()
+ adminClient, pErr := NewAdminClient(endpoint, accessKeyID, secretAccessKey)
+ if pErr != nil {
+ return nil, pErr.Cause
+ }
+ return adminClient, nil
+}
diff --git a/restapi/client.go b/restapi/client.go
index 437c5cb80..4843aee59 100644
--- a/restapi/client.go
+++ b/restapi/client.go
@@ -34,7 +34,7 @@ func init() {
minio.MaxRetry = 1
}
-// Define MinioClient interface with all functions to be implemented
+// MinioClient interface with all functions to be implemented
// by mock when testing, it should include all MinioClient respective api calls
// that are used within this project.
type MinioClient interface {
@@ -84,7 +84,7 @@ func (c minioClient) getBucketPolicy(bucketName string) (string, error) {
return c.client.GetBucketPolicy(bucketName)
}
-// Define MCS3Client interface with all functions to be implemented
+// MCS3Client interface with all functions to be implemented
// by mock when testing, it should include all mc/S3Client respective api calls
// that are used within this project.
type MCS3Client interface {
diff --git a/restapi/configure_mcs.go b/restapi/configure_mcs.go
index 7276dd11c..01da647a0 100644
--- a/restapi/configure_mcs.go
+++ b/restapi/configure_mcs.go
@@ -161,9 +161,12 @@ func setupGlobalMiddleware(handler http.Handler) http.Handler {
// FileServerMiddleware serves files from the static folder
func FileServerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if strings.HasPrefix(r.URL.Path, "/api") {
+ switch {
+ case strings.HasPrefix(r.URL.Path, "/ws"):
+ serveWS(w, r)
+ case strings.HasPrefix(r.URL.Path, "/api"):
next.ServeHTTP(w, r)
- } else {
+ default:
http.FileServer(&assetfs.AssetFS{
Asset: portalUI.Asset,
AssetDir: portalUI.AssetDir,
diff --git a/restapi/doc.go b/restapi/doc.go
index fc76bd793..a302e908c 100644
--- a/restapi/doc.go
+++ b/restapi/doc.go
@@ -20,6 +20,7 @@
//
// Schemes:
// http
+// ws
// Host: localhost
// BasePath: /api/v1
// Version: 0.1.0
diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go
index f7e9e1434..446d8526b 100644
--- a/restapi/embedded_spec.go
+++ b/restapi/embedded_spec.go
@@ -42,7 +42,8 @@ func init() {
"application/json"
],
"schemes": [
- "http"
+ "http",
+ "ws"
],
"swagger": "2.0",
"info": {
@@ -2032,7 +2033,8 @@ func init() {
"application/json"
],
"schemes": [
- "http"
+ "http",
+ "ws"
],
"swagger": "2.0",
"info": {
diff --git a/restapi/operations/bulk_update_users_groups.go b/restapi/operations/bulk_update_users_groups.go
deleted file mode 100644
index e45730f9d..000000000
--- a/restapi/operations/bulk_update_users_groups.go
+++ /dev/null
@@ -1,90 +0,0 @@
-// Code generated by go-swagger; DO NOT EDIT.
-
-// This file is part of MinIO Console Server
-// Copyright (c) 2020 MinIO, Inc.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-//
-
-package operations
-
-// This file was generated by the swagger tool.
-// Editing this file might prove futile when you re-run the generate command
-
-import (
- "net/http"
-
- "github.com/go-openapi/runtime/middleware"
-
- "github.com/minio/mcs/models"
-)
-
-// BulkUpdateUsersGroupsHandlerFunc turns a function with the right signature into a bulk update users groups handler
-type BulkUpdateUsersGroupsHandlerFunc func(BulkUpdateUsersGroupsParams, *models.Principal) middleware.Responder
-
-// Handle executing the request and returning a response
-func (fn BulkUpdateUsersGroupsHandlerFunc) Handle(params BulkUpdateUsersGroupsParams, principal *models.Principal) middleware.Responder {
- return fn(params, principal)
-}
-
-// BulkUpdateUsersGroupsHandler interface for that can handle valid bulk update users groups params
-type BulkUpdateUsersGroupsHandler interface {
- Handle(BulkUpdateUsersGroupsParams, *models.Principal) middleware.Responder
-}
-
-// NewBulkUpdateUsersGroups creates a new http.Handler for the bulk update users groups operation
-func NewBulkUpdateUsersGroups(ctx *middleware.Context, handler BulkUpdateUsersGroupsHandler) *BulkUpdateUsersGroups {
- return &BulkUpdateUsersGroups{Context: ctx, Handler: handler}
-}
-
-/*BulkUpdateUsersGroups swagger:route PUT /user/groups bulkUpdateUsersGroups
-
-Bulk functionality to Add Users to Groups
-
-*/
-type BulkUpdateUsersGroups struct {
- Context *middleware.Context
- Handler BulkUpdateUsersGroupsHandler
-}
-
-func (o *BulkUpdateUsersGroups) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
- route, rCtx, _ := o.Context.RouteInfo(r)
- if rCtx != nil {
- r = rCtx
- }
- var Params = NewBulkUpdateUsersGroupsParams()
-
- uprinc, aCtx, err := o.Context.Authorize(r, route)
- if err != nil {
- o.Context.Respond(rw, r, route.Produces, route, err)
- return
- }
- if aCtx != nil {
- r = aCtx
- }
- var principal *models.Principal
- if uprinc != nil {
- principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
- }
-
- if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
- o.Context.Respond(rw, r, route.Produces, route, err)
- return
- }
-
- res := o.Handler.Handle(Params, principal) // actually handle the request
-
- o.Context.Respond(rw, r, route.Produces, route, res)
-
-}
diff --git a/restapi/operations/bulk_update_users_groups_parameters.go b/restapi/operations/bulk_update_users_groups_parameters.go
deleted file mode 100644
index 28150db15..000000000
--- a/restapi/operations/bulk_update_users_groups_parameters.go
+++ /dev/null
@@ -1,94 +0,0 @@
-// Code generated by go-swagger; DO NOT EDIT.
-
-// This file is part of MinIO Console Server
-// Copyright (c) 2020 MinIO, Inc.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-//
-
-package operations
-
-// This file was generated by the swagger tool.
-// Editing this file might prove futile when you re-run the swagger generate command
-
-import (
- "io"
- "net/http"
-
- "github.com/go-openapi/errors"
- "github.com/go-openapi/runtime"
- "github.com/go-openapi/runtime/middleware"
-
- "github.com/minio/mcs/models"
-)
-
-// NewBulkUpdateUsersGroupsParams creates a new BulkUpdateUsersGroupsParams object
-// no default values defined in spec.
-func NewBulkUpdateUsersGroupsParams() BulkUpdateUsersGroupsParams {
-
- return BulkUpdateUsersGroupsParams{}
-}
-
-// BulkUpdateUsersGroupsParams contains all the bound params for the bulk update users groups operation
-// typically these are obtained from a http.Request
-//
-// swagger:parameters BulkUpdateUsersGroups
-type BulkUpdateUsersGroupsParams struct {
-
- // HTTP Request Object
- HTTPRequest *http.Request `json:"-"`
-
- /*
- Required: true
- In: body
- */
- Body *models.BulkUserGroups
-}
-
-// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
-// for simple values it will use straight method calls.
-//
-// To ensure default values, the struct must have been initialized with NewBulkUpdateUsersGroupsParams() beforehand.
-func (o *BulkUpdateUsersGroupsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
- var res []error
-
- o.HTTPRequest = r
-
- if runtime.HasBody(r) {
- defer r.Body.Close()
- var body models.BulkUserGroups
- if err := route.Consumer.Consume(r.Body, &body); err != nil {
- if err == io.EOF {
- res = append(res, errors.Required("body", "body"))
- } else {
- res = append(res, errors.NewParseError("body", "body", "", err))
- }
- } else {
- // validate body object
- if err := body.Validate(route.Formats); err != nil {
- res = append(res, err)
- }
-
- if len(res) == 0 {
- o.Body = &body
- }
- }
- } else {
- res = append(res, errors.Required("body", "body"))
- }
- if len(res) > 0 {
- return errors.CompositeValidationError(res...)
- }
- return nil
-}
diff --git a/restapi/operations/bulk_update_users_groups_responses.go b/restapi/operations/bulk_update_users_groups_responses.go
deleted file mode 100644
index a0b0344c4..000000000
--- a/restapi/operations/bulk_update_users_groups_responses.go
+++ /dev/null
@@ -1,113 +0,0 @@
-// Code generated by go-swagger; DO NOT EDIT.
-
-// This file is part of MinIO Console Server
-// Copyright (c) 2020 MinIO, Inc.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-//
-
-package operations
-
-// This file was generated by the swagger tool.
-// Editing this file might prove futile when you re-run the swagger generate command
-
-import (
- "net/http"
-
- "github.com/go-openapi/runtime"
-
- "github.com/minio/mcs/models"
-)
-
-// BulkUpdateUsersGroupsOKCode is the HTTP code returned for type BulkUpdateUsersGroupsOK
-const BulkUpdateUsersGroupsOKCode int = 200
-
-/*BulkUpdateUsersGroupsOK A successful response.
-
-swagger:response bulkUpdateUsersGroupsOK
-*/
-type BulkUpdateUsersGroupsOK struct {
-}
-
-// NewBulkUpdateUsersGroupsOK creates BulkUpdateUsersGroupsOK with default headers values
-func NewBulkUpdateUsersGroupsOK() *BulkUpdateUsersGroupsOK {
-
- return &BulkUpdateUsersGroupsOK{}
-}
-
-// WriteResponse to the client
-func (o *BulkUpdateUsersGroupsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
-
- rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses
-
- rw.WriteHeader(200)
-}
-
-/*BulkUpdateUsersGroupsDefault Generic error response.
-
-swagger:response bulkUpdateUsersGroupsDefault
-*/
-type BulkUpdateUsersGroupsDefault struct {
- _statusCode int
-
- /*
- In: Body
- */
- Payload *models.Error `json:"body,omitempty"`
-}
-
-// NewBulkUpdateUsersGroupsDefault creates BulkUpdateUsersGroupsDefault with default headers values
-func NewBulkUpdateUsersGroupsDefault(code int) *BulkUpdateUsersGroupsDefault {
- if code <= 0 {
- code = 500
- }
-
- return &BulkUpdateUsersGroupsDefault{
- _statusCode: code,
- }
-}
-
-// WithStatusCode adds the status to the bulk update users groups default response
-func (o *BulkUpdateUsersGroupsDefault) WithStatusCode(code int) *BulkUpdateUsersGroupsDefault {
- o._statusCode = code
- return o
-}
-
-// SetStatusCode sets the status to the bulk update users groups default response
-func (o *BulkUpdateUsersGroupsDefault) SetStatusCode(code int) {
- o._statusCode = code
-}
-
-// WithPayload adds the payload to the bulk update users groups default response
-func (o *BulkUpdateUsersGroupsDefault) WithPayload(payload *models.Error) *BulkUpdateUsersGroupsDefault {
- o.Payload = payload
- return o
-}
-
-// SetPayload sets the payload to the bulk update users groups default response
-func (o *BulkUpdateUsersGroupsDefault) SetPayload(payload *models.Error) {
- o.Payload = payload
-}
-
-// WriteResponse to the client
-func (o *BulkUpdateUsersGroupsDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
-
- rw.WriteHeader(o._statusCode)
- if o.Payload != nil {
- payload := o.Payload
- if err := producer.Produce(rw, payload); err != nil {
- panic(err) // let the recovery middleware deal with this
- }
- }
-}
diff --git a/restapi/operations/bulk_update_users_groups_urlbuilder.go b/restapi/operations/bulk_update_users_groups_urlbuilder.go
deleted file mode 100644
index cadee05aa..000000000
--- a/restapi/operations/bulk_update_users_groups_urlbuilder.go
+++ /dev/null
@@ -1,104 +0,0 @@
-// Code generated by go-swagger; DO NOT EDIT.
-
-// This file is part of MinIO Console Server
-// Copyright (c) 2020 MinIO, Inc.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-//
-
-package operations
-
-// This file was generated by the swagger tool.
-// Editing this file might prove futile when you re-run the generate command
-
-import (
- "errors"
- "net/url"
- golangswaggerpaths "path"
-)
-
-// BulkUpdateUsersGroupsURL generates an URL for the bulk update users groups operation
-type BulkUpdateUsersGroupsURL struct {
- _basePath string
-}
-
-// WithBasePath sets the base path for this url builder, only required when it's different from the
-// base path specified in the swagger spec.
-// When the value of the base path is an empty string
-func (o *BulkUpdateUsersGroupsURL) WithBasePath(bp string) *BulkUpdateUsersGroupsURL {
- o.SetBasePath(bp)
- return o
-}
-
-// SetBasePath sets the base path for this url builder, only required when it's different from the
-// base path specified in the swagger spec.
-// When the value of the base path is an empty string
-func (o *BulkUpdateUsersGroupsURL) SetBasePath(bp string) {
- o._basePath = bp
-}
-
-// Build a url path and query string
-func (o *BulkUpdateUsersGroupsURL) Build() (*url.URL, error) {
- var _result url.URL
-
- var _path = "/user/groups"
-
- _basePath := o._basePath
- if _basePath == "" {
- _basePath = "/api/v1"
- }
- _result.Path = golangswaggerpaths.Join(_basePath, _path)
-
- return &_result, nil
-}
-
-// Must is a helper function to panic when the url builder returns an error
-func (o *BulkUpdateUsersGroupsURL) Must(u *url.URL, err error) *url.URL {
- if err != nil {
- panic(err)
- }
- if u == nil {
- panic("url can't be nil")
- }
- return u
-}
-
-// String returns the string representation of the path with query string
-func (o *BulkUpdateUsersGroupsURL) String() string {
- return o.Must(o.Build()).String()
-}
-
-// BuildFull builds a full url with scheme, host, path and query string
-func (o *BulkUpdateUsersGroupsURL) BuildFull(scheme, host string) (*url.URL, error) {
- if scheme == "" {
- return nil, errors.New("scheme is required for a full url on BulkUpdateUsersGroupsURL")
- }
- if host == "" {
- return nil, errors.New("host is required for a full url on BulkUpdateUsersGroupsURL")
- }
-
- base, err := o.Build()
- if err != nil {
- return nil, err
- }
-
- base.Scheme = scheme
- base.Host = host
- return base, nil
-}
-
-// StringFull returns the string representation of a complete url
-func (o *BulkUpdateUsersGroupsURL) StringFull(scheme, host string) string {
- return o.Must(o.BuildFull(scheme, host)).String()
-}
diff --git a/restapi/ws_handle.go b/restapi/ws_handle.go
new file mode 100644
index 000000000..30c0bd04c
--- /dev/null
+++ b/restapi/ws_handle.go
@@ -0,0 +1,207 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2020 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package restapi
+
+import (
+ "context"
+ "log"
+ "net/http"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/go-openapi/errors"
+ "github.com/gorilla/websocket"
+ "github.com/minio/mcs/pkg/ws"
+)
+
+var upgrader = websocket.Upgrader{
+ ReadBufferSize: 0,
+ WriteBufferSize: 1024,
+}
+
+const (
+ // websocket base path
+ wsBasePath = "/ws"
+
+ // Time allowed to read the next pong message from the peer.
+ pingWait = 15 * time.Second
+
+ // Maximum message size allowed from peer. 0 = unlimited
+ maxMessageSize = 512
+)
+
+// MCSWebsocket interface of a Websocket Client
+type MCSWebsocket interface {
+ // start trace info from servers
+ trace()
+}
+
+type wsClient struct {
+ // websocket connection.
+ conn wsConn
+ // MinIO admin Client
+ madmin MinioAdmin
+}
+
+// WSConn interface with all functions to be implemented
+// by mock when testing, it should include all websocket.Conn
+// respective api calls that are used within this project.
+type WSConn interface {
+ writeMessage(messageType int, data []byte) error
+ close() error
+ setReadLimit(limit int64)
+ setReadDeadline(t time.Time) error
+ setPongHandler(h func(appData string) error)
+ readMessage() (messageType int, p []byte, err error)
+}
+
+// Interface implementation
+//
+// Define the structure of a websocket Connection
+type wsConn struct {
+ conn *websocket.Conn
+}
+
+func (c wsConn) writeMessage(messageType int, data []byte) error {
+ return c.conn.WriteMessage(messageType, data)
+}
+
+func (c wsConn) close() error {
+ return c.conn.Close()
+}
+
+func (c wsConn) setReadLimit(limit int64) {
+ c.conn.SetReadLimit(limit)
+}
+
+func (c wsConn) setReadDeadline(t time.Time) error {
+ return c.conn.SetReadDeadline(t)
+}
+
+func (c wsConn) setPongHandler(h func(appData string) error) {
+ c.conn.SetPongHandler(h)
+}
+func (c wsConn) readMessage() (messageType int, p []byte, err error) {
+ return c.conn.ReadMessage()
+}
+
+// serveWS validates the incoming request and
+// upgrades the request to a Websocket protocol.
+// Websocket communication will be done depending
+// on the path.
+// Request should come like ws://:/ws/
+//
+// TODO: Enable CORS
+func serveWS(w http.ResponseWriter, req *http.Request) {
+ // authenticate WS connection
+ // TODO: use this claims to create the adminClient
+ _, err := ws.Authenticate(req)
+ if err != nil {
+ log.Print("error on ws authentication: ", err)
+ errors.ServeError(w, req, err)
+ return
+ }
+
+ // upgrades the HTTP server connection to the WebSocket protocol.
+ conn, err := upgrader.Upgrade(w, req, nil)
+ if err != nil {
+ log.Print("error on upgrade: ", err)
+ errors.ServeError(w, req, err)
+ return
+ }
+
+ // TODO: CHANGE ! to use newMAdminClient once Assume Role is
+ // allowed to do Trace use jwt on ws.
+
+ // Using newSuperMAdminClient in the meantime for sake of functionality
+ // Only start Websocket Interaction after user has been
+ // authenticated.
+ mAdmin, err := newSuperMAdminClient()
+ if err != nil {
+ log.Println("error creating Madmin Client:", err)
+ errors.ServeError(w, req, err)
+ return
+ }
+ // create a minioClient interface implementation
+ // defining the client to be used
+ adminClient := adminClient{client: mAdmin}
+ // create a websocket connection interface implementation
+ // defining the connection to be used
+ wsConnection := wsConn{conn: conn}
+
+ // create websocket client and handle request
+ wsClient := &wsClient{conn: wsConnection, madmin: adminClient}
+ switch strings.TrimPrefix(req.URL.Path, wsBasePath) {
+ case "/trace":
+ go wsClient.trace()
+ default:
+ // path not found
+ conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+ conn.Close()
+ }
+}
+
+// wsReadCheck ensures that the client is sending a message
+// every `pingWait` seconds. If deadline exceeded or an error
+// happened this will return, meaning it won't be able to ensure
+// client heartbeat.
+func wsReadCheck(ctx context.Context, wg *sync.WaitGroup, conn WSConn) chan error {
+ // decrements the WaitGroup counter by one when the function returns
+ defer wg.Done()
+ ch := make(chan error)
+ go func(ch chan error) {
+ defer close(ch)
+
+ // set initial Limits and deadlines for the Reader
+ conn.setReadLimit(maxMessageSize)
+ conn.setReadDeadline(time.Now().Add(pingWait))
+ conn.setPongHandler(func(string) error { conn.setReadDeadline(time.Now().Add(pingWait)); return nil })
+
+ for {
+ select {
+ case <-ctx.Done():
+ log.Println("context done inside wsReadCheck")
+ return
+ default:
+ _, _, err := conn.readMessage()
+ if err != nil {
+ // if error of type websocket.CloseError and is Unexpected
+ if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseNormalClosure) {
+ log.Println("error unexpected CloseError on ReadMessage:", err)
+ ch <- err
+ return
+ }
+ // Not all errors are of type websocket.CloseError.
+ // If not of type websocket.CloseError return error
+ if _, ok := err.(*websocket.CloseError); !ok {
+ log.Println("error on ReadMessage:", err)
+ ch <- err
+ return
+ }
+ // else is an expected Close Error
+ log.Println("closed conn.ReadMessage:", err)
+ return
+ }
+ // Reset Read Deadline after each Read
+ conn.setReadDeadline(time.Now().Add(pingWait))
+ conn.setPongHandler(func(string) error { conn.setReadDeadline(time.Now().Add(pingWait)); return nil })
+ }
+ }
+ }(ch)
+ return ch
+}
diff --git a/restapi/ws_handle_test.go b/restapi/ws_handle_test.go
new file mode 100644
index 000000000..edf39b914
--- /dev/null
+++ b/restapi/ws_handle_test.go
@@ -0,0 +1,68 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2020 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package restapi
+
+import (
+ "time"
+)
+
+// Common mocks for WSConn interface
+// assigning mock at runtime instead of compile time
+var connWriteMessageMock func(messageType int, data []byte) error
+var connReadMessageMock func() (messageType int, p []byte, err error)
+
+// Uncomment and implement if needed
+// var connCloseMock func() error
+// var connSetReadLimitMock func(limit int64)
+// var connSetReadDeadlineMock func(t time.Time) error
+// var connSetPongHandlerMock func(h func(appData string) error)
+
+// The Conn type represents a WebSocket connection.
+type mockConn struct{}
+
+func (c mockConn) writeMessage(messageType int, data []byte) error {
+ return connWriteMessageMock(messageType, data)
+}
+func (c mockConn) readMessage() (messageType int, p []byte, err error) {
+ return connReadMessageMock()
+}
+
+func (c mockConn) close() error {
+ return nil
+}
+func (c mockConn) setReadLimit(limit int64) {
+}
+func (c mockConn) setReadDeadline(t time.Time) error {
+ return nil
+}
+func (c mockConn) setPongHandler(h func(appData string) error) {
+}
+
+// Common mocks for MCSWebsocket interface
+// assigning mock at runtime instead of compile time
+var wsTraceMock func()
+
+// Define a mock struct of wsClient interface implementation
+type wsClientMock struct {
+ // MinIO admin Client
+ madmin MinioAdmin
+}
+
+// mock function of wsc.trace()
+func (wsc wsClientMock) trace() {
+ wsTraceMock()
+}
diff --git a/swagger.yml b/swagger.yml
index d9cae8806..de24e3a2b 100644
--- a/swagger.yml
+++ b/swagger.yml
@@ -8,6 +8,7 @@ produces:
- application/json
schemes:
- http
+ - ws
basePath: /api/v1
# We are going to be taking `Authorization: Bearer TOKEN` header for our authentication
securityDefinitions: