mirror of
https://github.com/pgsty/minio.git
synced 2026-11-04 14:52:02 +03:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6dd26b8231 | |||
| e098852a80 | |||
| 1e82c4a7c4 | |||
| ce960565b1 | |||
| 755e675d5c | |||
| b6c00405ec | |||
| 8f62935448 | |||
| 396d78352d | |||
| 8a405cab2f | |||
| 6d778a883f |
+2
-1
@@ -22,4 +22,5 @@ parts/
|
||||
prime/
|
||||
stage/
|
||||
.sia_temp/
|
||||
config.json
|
||||
config.json
|
||||
healthcheck
|
||||
|
||||
+5
-4
@@ -11,7 +11,8 @@ RUN \
|
||||
apk add --no-cache git && \
|
||||
go get -v -d github.com/minio/minio && \
|
||||
cd /go/src/github.com/minio/minio && \
|
||||
go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)"
|
||||
go install -v -ldflags "$(go run buildscripts/gen-ldflags.go)" && \
|
||||
go build -ldflags "-s -w" -o /usr/bin/healthcheck dockerscripts/healthcheck.go
|
||||
|
||||
FROM alpine:3.7
|
||||
|
||||
@@ -22,7 +23,8 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||
EXPOSE 9000
|
||||
|
||||
COPY --from=0 /go/bin/minio /usr/bin/minio
|
||||
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck.sh /usr/bin/
|
||||
COPY --from=0 /usr/bin/healthcheck /usr/bin/healthcheck
|
||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||
|
||||
RUN \
|
||||
apk add --no-cache ca-certificates 'curl>7.61.0' && \
|
||||
@@ -32,7 +34,6 @@ ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||
|
||||
VOLUME ["/data"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s \
|
||||
CMD /usr/bin/healthcheck.sh
|
||||
HEALTHCHECK --interval=1m CMD healthcheck
|
||||
|
||||
CMD ["minio"]
|
||||
|
||||
+3
-4
@@ -2,7 +2,7 @@ FROM alpine:3.7
|
||||
|
||||
LABEL maintainer="Minio Inc <dev@minio.io>"
|
||||
|
||||
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck.sh /usr/bin/
|
||||
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck /usr/bin/
|
||||
COPY minio /usr/bin/
|
||||
|
||||
ENV MINIO_UPDATE off
|
||||
@@ -14,7 +14,7 @@ RUN \
|
||||
echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \
|
||||
chmod +x /usr/bin/minio && \
|
||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/bin/healthcheck.sh
|
||||
chmod +x /usr/bin/healthcheck
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
@@ -22,7 +22,6 @@ ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||
|
||||
VOLUME ["/data"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s \
|
||||
CMD /usr/bin/healthcheck.sh
|
||||
HEALTHCHECK --interval=1m CMD healthcheck
|
||||
|
||||
CMD ["minio"]
|
||||
|
||||
+17
-4
@@ -1,8 +1,22 @@
|
||||
FROM golang:1.11.4-alpine3.7
|
||||
|
||||
ENV GOPATH /go
|
||||
ENV CGO_ENABLED 0
|
||||
|
||||
WORKDIR /go/src/github.com/minio/
|
||||
|
||||
RUN \
|
||||
apk add --no-cache git && \
|
||||
go get -v -d github.com/minio/minio && \
|
||||
cd /go/src/github.com/minio/minio/dockerscripts && \
|
||||
go build -ldflags "-s -w" -o /usr/bin/healthcheck healthcheck.go
|
||||
|
||||
FROM alpine:3.7
|
||||
|
||||
LABEL maintainer="Minio Inc <dev@minio.io>"
|
||||
|
||||
COPY dockerscripts/docker-entrypoint.sh dockerscripts/healthcheck.sh /usr/bin/
|
||||
COPY --from=0 /usr/bin/healthcheck /usr/bin/healthcheck
|
||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||
|
||||
ENV MINIO_UPDATE off
|
||||
ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||
@@ -14,7 +28,7 @@ RUN \
|
||||
curl https://dl.minio.io/server/minio/release/linux-amd64/minio > /usr/bin/minio && \
|
||||
chmod +x /usr/bin/minio && \
|
||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/bin/healthcheck.sh
|
||||
chmod +x /usr/bin/healthcheck
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
@@ -22,7 +36,6 @@ ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||
|
||||
VOLUME ["/data"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s \
|
||||
CMD /usr/bin/healthcheck.sh
|
||||
HEALTHCHECK --interval=1m CMD healthcheck
|
||||
|
||||
CMD ["minio"]
|
||||
|
||||
@@ -66,6 +66,7 @@ coverage: build
|
||||
build: checks
|
||||
@echo "Building minio binary to './minio'"
|
||||
@GOFLAGS="" CGO_ENABLED=0 go build -tags kqueue --ldflags $(BUILD_LDFLAGS) -o $(PWD)/minio
|
||||
@GOFLAGS="" CGO_ENABLED=0 go build --ldflags="-s -w" -o $(PWD)/dockerscripts/healthcheck $(PWD)/dockerscripts/healthcheck.go
|
||||
|
||||
docker: build
|
||||
@docker build -t $(TAG) . -f Dockerfile.dev
|
||||
|
||||
+55
-61
@@ -16,7 +16,6 @@ package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -24,7 +23,6 @@ import (
|
||||
"time"
|
||||
|
||||
vault "github.com/hashicorp/vault/api"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -64,6 +62,7 @@ type VaultConfig struct {
|
||||
type vaultService struct {
|
||||
config *VaultConfig
|
||||
client *vault.Client
|
||||
secret *vault.Secret
|
||||
leaseDuration time.Duration
|
||||
}
|
||||
|
||||
@@ -123,86 +122,81 @@ func NewVault(config VaultConfig) (KMS, error) {
|
||||
client.SetNamespace(config.Namespace)
|
||||
}
|
||||
v := &vaultService{client: client, config: &config}
|
||||
|
||||
if err := v.authenticate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.renewToken()
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// renewSecret tries to renew the given secret. It blocks
|
||||
// until it receives either the new secret or encounters an error.
|
||||
func (v *vaultService) renewSecret(secret *vault.Secret) (*vault.Secret, error) {
|
||||
renewer, err := v.client.NewRenewer(&vault.RenewerInput{
|
||||
Secret: secret,
|
||||
})
|
||||
if err != nil {
|
||||
logger.CriticalIf(context.Background(), fmt.Errorf("crypto: failed to create hashicorp vault renewer: %s", err))
|
||||
}
|
||||
go renewer.Renew()
|
||||
defer renewer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-renewer.DoneCh():
|
||||
// renewToken starts a new go-routine which renews
|
||||
// the vault authentication token periodically and re-authenticates
|
||||
// if the token renewal fails
|
||||
func (v *vaultService) renewToken() {
|
||||
retryDelay := v.leaseDuration / 2
|
||||
go func() {
|
||||
for {
|
||||
if v.secret == nil {
|
||||
if err := v.authenticate(); err != nil {
|
||||
time.Sleep(retryDelay)
|
||||
continue
|
||||
}
|
||||
}
|
||||
s, err := v.client.Auth().Token().RenewSelf(int(v.leaseDuration))
|
||||
if err != nil || s == nil {
|
||||
v.secret = nil
|
||||
time.Sleep(retryDelay)
|
||||
continue
|
||||
}
|
||||
if ok, err := s.TokenIsRenewable(); !ok || err != nil {
|
||||
v.secret = nil
|
||||
continue
|
||||
}
|
||||
ttl, err := s.TokenTTL()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
v.secret = nil
|
||||
continue
|
||||
}
|
||||
case renew := <-renewer.RenewCh():
|
||||
if renew.Secret == nil || renew.Secret.Auth == nil {
|
||||
return nil, ErrKMSAuthLogin
|
||||
}
|
||||
return renew.Secret, nil
|
||||
v.secret = s
|
||||
retryDelay = ttl / 2
|
||||
time.Sleep(retryDelay)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// login tries to authenticate the minio server to
|
||||
// the Vault KMS using the approle ID and secret.
|
||||
func (v *vaultService) login() (*vault.Secret, error) {
|
||||
// authenticate logs the app to vault, and starts the auto renewer
|
||||
// before secret expires
|
||||
func (v *vaultService) authenticate() (err error) {
|
||||
payload := map[string]interface{}{
|
||||
"role_id": v.config.Auth.AppRole.ID,
|
||||
"secret_id": v.config.Auth.AppRole.Secret,
|
||||
}
|
||||
secret, err := v.client.Logical().Write("auth/approle/login", payload)
|
||||
var tokenID string
|
||||
var ttl time.Duration
|
||||
var secret *vault.Secret
|
||||
secret, err = v.client.Logical().Write("auth/approle/login", payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
if secret == nil || secret.Auth == nil {
|
||||
return nil, ErrKMSAuthLogin
|
||||
if secret == nil {
|
||||
err = ErrKMSAuthLogin
|
||||
return
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// authenticate tries to authenticate the minio server
|
||||
// to the Vault KMS and starts a background job to renew
|
||||
// the login.
|
||||
func (v *vaultService) authenticate() error {
|
||||
secret, err := v.login()
|
||||
tokenID, err = secret.TokenID()
|
||||
if err != nil {
|
||||
return err
|
||||
err = ErrKMSAuthLogin
|
||||
return
|
||||
}
|
||||
v.client.SetToken(secret.Auth.ClientToken)
|
||||
v.leaseDuration = time.Duration(secret.Auth.LeaseDuration)
|
||||
|
||||
// Start background job trying to renew the token
|
||||
// or (if this fails) try to login again with app-ID and app-Secret.
|
||||
go func(secret *vault.Secret) {
|
||||
for {
|
||||
newSecret, err := v.renewSecret(secret) // try to renew the secret (blocking)
|
||||
if err != nil {
|
||||
// Try to login again with app-ID and app-Secret
|
||||
if newSecret, err = v.login(); err != nil { // failed -> try again
|
||||
time.Sleep(1 * time.Minute) // retry delay
|
||||
continue
|
||||
}
|
||||
}
|
||||
secret = newSecret // Now newSecret contains a valid, non-nil *vault.Secret
|
||||
v.client.SetToken(secret.Auth.ClientToken)
|
||||
v.leaseDuration = time.Duration(secret.Auth.LeaseDuration)
|
||||
}
|
||||
}(secret)
|
||||
return nil
|
||||
ttl, err = secret.TokenTTL()
|
||||
if err != nil {
|
||||
err = ErrKMSAuthLogin
|
||||
return
|
||||
}
|
||||
v.client.SetToken(tokenID)
|
||||
v.secret = secret
|
||||
v.leaseDuration = ttl
|
||||
return
|
||||
}
|
||||
|
||||
// GenerateKey returns a new plaintext key, generated by the KMS,
|
||||
|
||||
+21
-6
@@ -550,14 +550,14 @@ func (h httpStatsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
globalHTTPStats.updateStats(r, ww, durationSecs)
|
||||
}
|
||||
|
||||
// pathValidityHandler validates all the incoming paths for
|
||||
// any bad components and rejects them.
|
||||
type pathValidityHandler struct {
|
||||
// requestValidityHandler validates all the incoming paths for
|
||||
// any malicious requests.
|
||||
type requestValidityHandler struct {
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
func setPathValidityHandler(h http.Handler) http.Handler {
|
||||
return pathValidityHandler{handler: h}
|
||||
func setRequestValidityHandler(h http.Handler) http.Handler {
|
||||
return requestValidityHandler{handler: h}
|
||||
}
|
||||
|
||||
// Bad path components to be rejected by the path validity handler.
|
||||
@@ -581,7 +581,18 @@ func hasBadPathComponent(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (h pathValidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Check if client is sending a malicious request.
|
||||
func hasMultipleAuth(r *http.Request) bool {
|
||||
authTypeCount := 0
|
||||
for _, hasValidAuth := range []func(*http.Request) bool{isRequestSignatureV2, isRequestPresignedSignatureV2, isRequestSignatureV4, isRequestPresignedSignatureV4, isRequestJWT, isRequestPostPolicySignatureV4} {
|
||||
if hasValidAuth(r) {
|
||||
authTypeCount++
|
||||
}
|
||||
}
|
||||
return authTypeCount > 1
|
||||
}
|
||||
|
||||
func (h requestValidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Check for bad components in URL path.
|
||||
if hasBadPathComponent(r.URL.Path) {
|
||||
writeErrorResponse(context.Background(), w, errorCodes.ToAPIErr(ErrInvalidResourceName), r.URL, guessIsBrowserReq(r))
|
||||
@@ -596,6 +607,10 @@ func (h pathValidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if hasMultipleAuth(r) {
|
||||
writeErrorResponse(context.Background(), w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
h.handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
|
||||
+6
-36
@@ -19,58 +19,28 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
func getRequestResource(r *http.Request) string {
|
||||
if r == nil {
|
||||
// http.Request is nil when non-HTTP data (like TLS record) is read/written.
|
||||
return ""
|
||||
}
|
||||
|
||||
if globalDomainName != "" {
|
||||
host := r.Header.Get("Host")
|
||||
if strings.HasSuffix(host, "."+globalDomainName) {
|
||||
return "/" + strings.TrimSuffix(host, "."+globalDomainName) + r.URL.Path
|
||||
}
|
||||
}
|
||||
|
||||
return r.URL.Path
|
||||
}
|
||||
|
||||
// ConnStats - Network statistics
|
||||
// Count total input/output transferred bytes during
|
||||
// the server's life.
|
||||
type ConnStats struct {
|
||||
totalInputBytes atomic.Uint64
|
||||
totalOutputBytes atomic.Uint64
|
||||
totalRPCInputBytes atomic.Uint64
|
||||
totalRPCOutputBytes atomic.Uint64
|
||||
totalInputBytes atomic.Uint64
|
||||
totalOutputBytes atomic.Uint64
|
||||
}
|
||||
|
||||
// Increase total input bytes
|
||||
func (s *ConnStats) incInputBytes(r *http.Request, n int) {
|
||||
resource := getRequestResource(r)
|
||||
if resource == minioReservedBucketPath || strings.HasPrefix(resource, minioReservedBucketPath+"/") {
|
||||
s.totalRPCInputBytes.Add(uint64(n))
|
||||
} else {
|
||||
s.totalInputBytes.Add(uint64(n))
|
||||
}
|
||||
func (s *ConnStats) incInputBytes(n int) {
|
||||
s.totalInputBytes.Add(uint64(n))
|
||||
}
|
||||
|
||||
// Increase total output bytes
|
||||
func (s *ConnStats) incOutputBytes(r *http.Request, n int) {
|
||||
resource := getRequestResource(r)
|
||||
if resource == minioReservedBucketPath || strings.HasPrefix(resource, minioReservedBucketPath+"/") {
|
||||
s.totalRPCOutputBytes.Add(uint64(n))
|
||||
} else {
|
||||
s.totalOutputBytes.Add(uint64(n))
|
||||
}
|
||||
func (s *ConnStats) incOutputBytes(n int) {
|
||||
s.totalOutputBytes.Add(uint64(n))
|
||||
}
|
||||
|
||||
// Return total input bytes
|
||||
|
||||
+14
-23
@@ -19,28 +19,17 @@ package http
|
||||
import (
|
||||
"bufio"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BufConn - is a generic stream-oriented network connection supporting buffered reader and read/write timeout.
|
||||
type BufConn struct {
|
||||
QuirkConn
|
||||
bufReader *bufio.Reader // buffered reader wraps reader in net.Conn.
|
||||
readTimeout time.Duration // sets the read timeout in the connection.
|
||||
writeTimeout time.Duration // sets the write timeout in the connection.
|
||||
request *http.Request // HTTP request information.
|
||||
updateBytesReadFunc func(*http.Request, int) // function to be called to update bytes read.
|
||||
updateBytesWrittenFunc func(*http.Request, int) // function to be called to update bytes written.
|
||||
}
|
||||
|
||||
func (c *BufConn) setRequest(request *http.Request) {
|
||||
c.request = request
|
||||
}
|
||||
|
||||
func (c *BufConn) setUpdateFuncs(updateBytesReadFunc, updateBytesWrittenFunc func(*http.Request, int)) {
|
||||
c.updateBytesReadFunc = updateBytesReadFunc
|
||||
c.updateBytesWrittenFunc = updateBytesWrittenFunc
|
||||
bufReader *bufio.Reader // buffered reader wraps reader in net.Conn.
|
||||
readTimeout time.Duration // sets the read timeout in the connection.
|
||||
writeTimeout time.Duration // sets the write timeout in the connection.
|
||||
updateBytesReadFunc func(int) // function to be called to update bytes read.
|
||||
updateBytesWrittenFunc func(int) // function to be called to update bytes written.
|
||||
}
|
||||
|
||||
// Sets read timeout
|
||||
@@ -81,7 +70,7 @@ func (c *BufConn) Read(b []byte) (n int, err error) {
|
||||
c.setReadTimeout()
|
||||
n, err = c.bufReader.Read(b)
|
||||
if err == nil && c.updateBytesReadFunc != nil {
|
||||
c.updateBytesReadFunc(c.request, n)
|
||||
c.updateBytesReadFunc(n)
|
||||
}
|
||||
|
||||
return n, err
|
||||
@@ -92,18 +81,20 @@ func (c *BufConn) Write(b []byte) (n int, err error) {
|
||||
c.setWriteTimeout()
|
||||
n, err = c.Conn.Write(b)
|
||||
if err == nil && c.updateBytesWrittenFunc != nil {
|
||||
c.updateBytesWrittenFunc(c.request, n)
|
||||
c.updateBytesWrittenFunc(n)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// newBufConn - creates a new connection object wrapping net.Conn.
|
||||
func newBufConn(c net.Conn, readTimeout, writeTimeout time.Duration, maxHeaderBytes int) *BufConn {
|
||||
func newBufConn(c net.Conn, readTimeout, writeTimeout time.Duration, maxHeaderBytes int, updateBytesReadFunc, updateBytesWrittenFunc func(int)) *BufConn {
|
||||
return &BufConn{
|
||||
QuirkConn: QuirkConn{Conn: c},
|
||||
bufReader: bufio.NewReaderSize(c, maxHeaderBytes),
|
||||
readTimeout: readTimeout,
|
||||
writeTimeout: writeTimeout,
|
||||
QuirkConn: QuirkConn{Conn: c},
|
||||
bufReader: bufio.NewReaderSize(c, maxHeaderBytes),
|
||||
readTimeout: readTimeout,
|
||||
writeTimeout: writeTimeout,
|
||||
updateBytesReadFunc: updateBytesReadFunc,
|
||||
updateBytesWrittenFunc: updateBytesWrittenFunc,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestBuffConnReadTimeout(t *testing.T) {
|
||||
t.Errorf("failed to accept new connection. %v", terr)
|
||||
return
|
||||
}
|
||||
bufconn := newBufConn(tcpConn, 1*time.Second, 1*time.Second, 4096)
|
||||
bufconn := newBufConn(tcpConn, 1*time.Second, 1*time.Second, 4096, nil, nil)
|
||||
defer bufconn.Close()
|
||||
|
||||
// Read a line
|
||||
|
||||
+7
-138
@@ -23,9 +23,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -33,9 +31,7 @@ import (
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
)
|
||||
|
||||
var sslRequiredErrMsg = []byte("HTTP/1.0 403 Forbidden\r\n\r\nSSL required")
|
||||
|
||||
var badRequestMsg = []byte("HTTP/1.0 400 Bad Request\r\n\r\n")
|
||||
var sslRequiredErrMsg = []byte("HTTP/1.1 403 Forbidden\r\n\r\nSSL required")
|
||||
|
||||
// HTTP methods.
|
||||
var methods = []string{
|
||||
@@ -51,30 +47,6 @@ var methods = []string{
|
||||
"PRI", // HTTP 2 method
|
||||
}
|
||||
|
||||
// maximum length of above methods + one space.
|
||||
var methodMaxLen = getMethodMaxLen() + 1
|
||||
|
||||
func getMethodMaxLen() int {
|
||||
maxLen := 0
|
||||
for _, method := range methods {
|
||||
if len(method) > maxLen {
|
||||
maxLen = len(method)
|
||||
}
|
||||
}
|
||||
|
||||
return maxLen
|
||||
}
|
||||
|
||||
func isHTTPMethod(s string) bool {
|
||||
for _, method := range methods {
|
||||
if s == method {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func getPlainText(bufConn *BufConn) (bool, error) {
|
||||
defer bufConn.setReadTimeout()
|
||||
|
||||
@@ -95,66 +67,6 @@ func getPlainText(bufConn *BufConn) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func getMethodResourceHost(bufConn *BufConn, maxHeaderBytes int) (method string, resource string, host string, err error) {
|
||||
defer bufConn.setReadTimeout()
|
||||
|
||||
var data []byte
|
||||
for dataLen := 1; dataLen < maxHeaderBytes; dataLen++ {
|
||||
if bufConn.canSetReadDeadline() {
|
||||
// Set deadline such that we close the connection quickly
|
||||
// of no data was received from the Peek()
|
||||
bufConn.SetReadDeadline(time.Now().UTC().Add(time.Second * 3))
|
||||
}
|
||||
|
||||
data, err = bufConn.bufReader.Peek(dataLen)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
tokens := strings.Split(string(data), "\n")
|
||||
if len(tokens) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
if method == "" && resource == "" {
|
||||
if i := strings.IndexByte(tokens[0], ' '); i == -1 {
|
||||
return "", "", "", fmt.Errorf("malformed HTTP request from '%s'", bufConn.LocalAddr())
|
||||
}
|
||||
httpTokens := strings.SplitN(tokens[0], " ", 3)
|
||||
if len(httpTokens) < 3 {
|
||||
return "", "", "", fmt.Errorf("malformed HTTP request from '%s'", bufConn.LocalAddr())
|
||||
}
|
||||
if !isHTTPMethod(httpTokens[0]) {
|
||||
return "", "", "", fmt.Errorf("malformed HTTP request, invalid HTTP method '%s' from '%s'",
|
||||
httpTokens[0], bufConn.LocalAddr())
|
||||
}
|
||||
|
||||
method = httpTokens[0]
|
||||
resource = httpTokens[1]
|
||||
}
|
||||
|
||||
for _, token := range tokens[1:] {
|
||||
if token == "" || !strings.HasSuffix(token, "\r") {
|
||||
continue
|
||||
}
|
||||
|
||||
// HTTP headers are case insensitive, so we should simply convert
|
||||
// each tokens to their lower case form to match 'host' header.
|
||||
token = strings.ToLower(token)
|
||||
if strings.HasPrefix(token, "host:") {
|
||||
host = strings.TrimPrefix(strings.TrimSuffix(token, "\r"), "host:")
|
||||
return method, resource, host, nil
|
||||
}
|
||||
}
|
||||
|
||||
if tokens[len(tokens)-1] == "\r" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return "", "", "", fmt.Errorf("malformed HTTP request from %s", bufConn.LocalAddr())
|
||||
}
|
||||
|
||||
type acceptResult struct {
|
||||
conn net.Conn
|
||||
err error
|
||||
@@ -171,8 +83,8 @@ type httpListener struct {
|
||||
readTimeout time.Duration
|
||||
writeTimeout time.Duration
|
||||
maxHeaderBytes int
|
||||
updateBytesReadFunc func(*http.Request, int) // function to be called to update bytes read in BufConn.
|
||||
updateBytesWrittenFunc func(*http.Request, int) // function to be called to update bytes written in BufConn.
|
||||
updateBytesReadFunc func(int) // function to be called to update bytes read in BufConn.
|
||||
updateBytesWrittenFunc func(int) // function to be called to update bytes written in BufConn.
|
||||
}
|
||||
|
||||
// isRoutineNetErr returns true if error is due to a network timeout,
|
||||
@@ -222,7 +134,8 @@ func (listener *httpListener) start() {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
tcpConn.SetKeepAlivePeriod(listener.tcpKeepAliveTimeout)
|
||||
|
||||
bufconn := newBufConn(tcpConn, listener.readTimeout, listener.writeTimeout, listener.maxHeaderBytes)
|
||||
bufconn := newBufConn(tcpConn, listener.readTimeout, listener.writeTimeout, listener.maxHeaderBytes,
|
||||
listener.updateBytesReadFunc, listener.updateBytesWrittenFunc)
|
||||
if listener.tlsConfig != nil {
|
||||
ok, err := getPlainText(bufconn)
|
||||
if err != nil {
|
||||
@@ -248,52 +161,8 @@ func (listener *httpListener) start() {
|
||||
bufconn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// As the listener is configured with TLS, try to do TLS handshake, drop the connection if it fails.
|
||||
tlsConn := tls.Server(bufconn, listener.tlsConfig)
|
||||
|
||||
if err := tlsConn.Handshake(); err != nil {
|
||||
reqInfo := (&logger.ReqInfo{}).AppendTags("remoteAddr", bufconn.RemoteAddr().String())
|
||||
reqInfo.AppendTags("localAddr", bufconn.LocalAddr().String())
|
||||
ctx := logger.SetReqInfo(context.Background(), reqInfo)
|
||||
logger.LogIf(ctx, err)
|
||||
bufconn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
bufconn = newBufConn(tlsConn, listener.readTimeout, listener.writeTimeout, listener.maxHeaderBytes)
|
||||
}
|
||||
|
||||
method, resource, host, err := getMethodResourceHost(bufconn, listener.maxHeaderBytes)
|
||||
if err != nil {
|
||||
// Peek could fail legitimately when clients abruptly close
|
||||
// connection. E.g. Chrome browser opens connections speculatively to
|
||||
// speed up loading of a web page. Peek may also fail due to network
|
||||
// saturation on a transport with read timeout set. All other kind of
|
||||
// errors should be logged for further investigation. Thanks @brendanashworth.
|
||||
if !isRoutineNetErr(err) {
|
||||
reqInfo := (&logger.ReqInfo{}).AppendTags("remoteAddr", bufconn.RemoteAddr().String())
|
||||
reqInfo.AppendTags("localAddr", bufconn.LocalAddr().String())
|
||||
ctx := logger.SetReqInfo(context.Background(), reqInfo)
|
||||
logger.LogIf(ctx, err)
|
||||
bufconn.Write(badRequestMsg)
|
||||
}
|
||||
bufconn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
header := make(http.Header)
|
||||
if host != "" {
|
||||
header.Add("Host", host)
|
||||
}
|
||||
bufconn.setRequest(&http.Request{
|
||||
Method: method,
|
||||
URL: &url.URL{Path: resource},
|
||||
Host: bufconn.LocalAddr().String(),
|
||||
Header: header,
|
||||
})
|
||||
bufconn.setUpdateFuncs(listener.updateBytesReadFunc, listener.updateBytesWrittenFunc)
|
||||
|
||||
send(acceptResult{bufconn, nil}, doneCh)
|
||||
}
|
||||
|
||||
@@ -380,8 +249,8 @@ func newHTTPListener(serverAddrs []string,
|
||||
readTimeout time.Duration,
|
||||
writeTimeout time.Duration,
|
||||
maxHeaderBytes int,
|
||||
updateBytesReadFunc func(*http.Request, int),
|
||||
updateBytesWrittenFunc func(*http.Request, int)) (listener *httpListener, err error) {
|
||||
updateBytesReadFunc func(int),
|
||||
updateBytesWrittenFunc func(int)) (listener *httpListener, err error) {
|
||||
|
||||
var tcpListeners []*net.TCPListener
|
||||
|
||||
|
||||
+6
-387
@@ -17,17 +17,14 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -156,43 +153,6 @@ func getNonLoopBackIP(t *testing.T) string {
|
||||
return nonLoopBackIP
|
||||
}
|
||||
|
||||
// Test getMethodMaxLen()
|
||||
func TestGetMethodMaxLen(t *testing.T) {
|
||||
l := getMethodMaxLen()
|
||||
if l != (methodMaxLen - 1) {
|
||||
t.Fatalf("expected: %v, got: %v", (methodMaxLen - 1), l)
|
||||
}
|
||||
}
|
||||
|
||||
// Test isHTTPMethod()
|
||||
func TestIsHTTPMethod(t *testing.T) {
|
||||
testCases := []struct {
|
||||
method string
|
||||
expectedResult bool
|
||||
}{
|
||||
{"", false},
|
||||
{"get", false},
|
||||
{"put", false},
|
||||
{"UPLOAD", false},
|
||||
{"OPTIONS", true},
|
||||
{"GET", true},
|
||||
{"HEAD", true},
|
||||
{"POST", true},
|
||||
{"PUT", true},
|
||||
{"DELETE", true},
|
||||
{"TRACE", true},
|
||||
{"CONNECT", true},
|
||||
{"PRI", true},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
result := isHTTPMethod(testCase.method)
|
||||
if result != testCase.expectedResult {
|
||||
t.Fatalf("expected: %v, got: %v", testCase.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewHTTPListener(t *testing.T) {
|
||||
tlsConfig := getTLSConfig(t)
|
||||
|
||||
@@ -202,8 +162,8 @@ func TestNewHTTPListener(t *testing.T) {
|
||||
tcpKeepAliveTimeout time.Duration
|
||||
readTimeout time.Duration
|
||||
writeTimeout time.Duration
|
||||
updateBytesReadFunc func(*http.Request, int)
|
||||
updateBytesWrittenFunc func(*http.Request, int)
|
||||
updateBytesReadFunc func(int)
|
||||
updateBytesWrittenFunc func(int)
|
||||
expectedErr bool
|
||||
}{
|
||||
{[]string{"93.184.216.34:65432"}, nil, time.Duration(0), time.Duration(0), time.Duration(0), nil, nil, true},
|
||||
@@ -266,8 +226,7 @@ func TestHTTPListenerStartClose(t *testing.T) {
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
DefaultMaxHeaderBytes,
|
||||
nil,
|
||||
nil,
|
||||
nil, nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
@@ -314,8 +273,7 @@ func TestHTTPListenerAddr(t *testing.T) {
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
DefaultMaxHeaderBytes,
|
||||
nil,
|
||||
nil,
|
||||
nil, nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
@@ -359,8 +317,7 @@ func TestHTTPListenerAddrs(t *testing.T) {
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
DefaultMaxHeaderBytes,
|
||||
nil,
|
||||
nil,
|
||||
nil, nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
@@ -380,149 +337,6 @@ func TestHTTPListenerAddrs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerAccept(t *testing.T) {
|
||||
tlsConfig := getTLSConfig(t)
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
tlsConfig *tls.Config
|
||||
request string
|
||||
reply string
|
||||
expectedRequestLine string
|
||||
}{
|
||||
{[]string{"localhost:0"}, nil, "GET / HTTP/1.0\r\nHost: example.org\r\n\r\n", "200 OK\r\n", "GET / HTTP/1.0\r\n"},
|
||||
{[]string{nonLoopBackIP + ":0"}, nil, "POST / HTTP/1.0\r\nHost: example.org\r\n\r\n", "200 OK\r\n", "POST / HTTP/1.0\r\n"},
|
||||
{[]string{nonLoopBackIP + ":0"}, nil, "HEAD / HTTP/1.0\r\nhost: example.org\r\n\r\n", "200 OK\r\n", "HEAD / HTTP/1.0\r\n"},
|
||||
{[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}, nil, "CONNECT / HTTP/1.0\r\nHost: www.example.org\r\n\r\n", "200 OK\r\n", "CONNECT / HTTP/1.0\r\n"},
|
||||
{[]string{"localhost:0"}, tlsConfig, "GET / HTTP/1.0\r\nHost: example.org\r\n\r\n", "200 OK\r\n", "GET / HTTP/1.0\r\n"},
|
||||
{[]string{nonLoopBackIP + ":0"}, tlsConfig, "POST / HTTP/1.0\r\nHost: example.org\r\n\r\n", "200 OK\r\n", "POST / HTTP/1.0\r\n"},
|
||||
{[]string{nonLoopBackIP + ":0"}, tlsConfig, "HEAD / HTTP/1.0\r\nhost: example.org\r\n\r\n", "200 OK\r\n", "HEAD / HTTP/1.0\r\n"},
|
||||
{[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}, tlsConfig, "CONNECT / HTTP/1.0\r\nHost: www.example.org\r\n\r\n", "200 OK\r\n", "CONNECT / HTTP/1.0\r\n"},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
testCase.tlsConfig,
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
DefaultMaxHeaderBytes,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
for _, serverAddr := range listener.Addrs() {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
|
||||
if testCase.tlsConfig == nil {
|
||||
conn, err = net.Dial("tcp", serverAddr.String())
|
||||
} else {
|
||||
conn, err = tls.Dial("tcp", serverAddr.String(), &tls.Config{InsecureSkipVerify: true})
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if _, err = io.WriteString(conn, testCase.request); err != nil {
|
||||
t.Fatalf("Test %d: request send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
serverConn, err := listener.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: accept: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
requestLine, err := bufio.NewReader(serverConn).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: request read: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if requestLine != testCase.expectedRequestLine {
|
||||
t.Fatalf("Test %d: request: expected = %v, got = %v", i+1, testCase.expectedRequestLine, requestLine)
|
||||
}
|
||||
|
||||
if _, err = io.WriteString(serverConn, testCase.reply); err != nil {
|
||||
t.Fatalf("Test %d: reply send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
reply, err := bufio.NewReader(conn).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: reply read: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if testCase.reply != reply {
|
||||
t.Fatalf("Test %d: reply: expected = %v, got = %v", i+1, testCase.reply, reply)
|
||||
}
|
||||
|
||||
serverConn.Close()
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerAcceptPeekError(t *testing.T) {
|
||||
tlsConfig := getTLSConfig(t)
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
tlsConfig *tls.Config
|
||||
request string
|
||||
}{
|
||||
{[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}, nil, "CONN"},
|
||||
{[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}, tlsConfig, "CONN"},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
testCase.tlsConfig,
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
DefaultMaxHeaderBytes,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
serverConn, aerr := listener.Accept()
|
||||
if aerr == nil {
|
||||
fail(t, "Test %d: accept: expected = <error>, got = <nil>", i+1)
|
||||
}
|
||||
if serverConn != nil {
|
||||
fail(t, "Test %d: accept: server expected = <nil>, got = %v", i+1, serverConn)
|
||||
}
|
||||
}()
|
||||
|
||||
for _, serverAddr := range listener.Addrs() {
|
||||
conn, err := net.Dial("tcp", serverAddr.String())
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if _, err = io.WriteString(conn, testCase.request); err != nil {
|
||||
t.Fatalf("Test %d: request send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerAcceptTLSError(t *testing.T) {
|
||||
tlsConfig := getTLSConfig(t)
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
@@ -543,8 +357,7 @@ func TestHTTPListenerAcceptTLSError(t *testing.T) {
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
DefaultMaxHeaderBytes,
|
||||
nil,
|
||||
nil,
|
||||
nil, nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
@@ -587,200 +400,6 @@ func TestHTTPListenerAcceptTLSError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerAcceptError(t *testing.T) {
|
||||
tlsConfig := getTLSConfig(t)
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
tlsConfig *tls.Config
|
||||
secureClient bool
|
||||
request string
|
||||
}{
|
||||
{[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}, nil, false, "CONNECTION"},
|
||||
{[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}, tlsConfig, false, "CONNECTION"},
|
||||
{[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}, tlsConfig, true, "CONNECTION"},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
testCase.tlsConfig,
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
DefaultMaxHeaderBytes,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
for _, serverAddr := range listener.Addrs() {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
|
||||
if testCase.secureClient {
|
||||
conn, err = tls.Dial("tcp", serverAddr.String(), &tls.Config{InsecureSkipVerify: true})
|
||||
} else {
|
||||
conn, err = net.Dial("tcp", serverAddr.String())
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if _, err = io.WriteString(conn, testCase.request); err != nil {
|
||||
t.Fatalf("Test %d: request send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
serverConn, aerr := listener.Accept()
|
||||
if aerr == nil {
|
||||
fail(t, "Test %d: accept: expected = <error>, got = <nil>", i+1)
|
||||
}
|
||||
if serverConn != nil {
|
||||
fail(t, "Test %d: accept: server expected = <nil>, got = %v", i+1, serverConn)
|
||||
}
|
||||
}()
|
||||
|
||||
if !testCase.secureClient && testCase.tlsConfig != nil {
|
||||
buf := make([]byte, len(sslRequiredErrMsg))
|
||||
var n int
|
||||
n, err = io.ReadFull(conn, buf)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: reply read: expected = <nil> got = %v", i+1, err)
|
||||
} else if n != len(buf) {
|
||||
t.Fatalf("Test %d: reply length: expected = %v got = %v", i+1, len(buf), n)
|
||||
} else if !bytes.Equal(buf, sslRequiredErrMsg) {
|
||||
t.Fatalf("Test %d: reply: expected = %v got = %v", i+1, string(sslRequiredErrMsg), string(buf))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err = bufio.NewReader(conn).ReadString('\n'); err != io.EOF {
|
||||
t.Errorf("Test %d: reply read: expected = io.EOF, got = %s", i+1, err)
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPListenerAcceptParallel(t *testing.T) {
|
||||
tlsConfig := getTLSConfig(t)
|
||||
nonLoopBackIP := getNonLoopBackIP(t)
|
||||
|
||||
testCases := []struct {
|
||||
serverAddrs []string
|
||||
tlsConfig *tls.Config
|
||||
reply string
|
||||
}{
|
||||
{[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}, nil, "200 OK\n"},
|
||||
{[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}, tlsConfig, "200 OK\n"},
|
||||
}
|
||||
|
||||
connect := func(i int, serverAddr string, secure bool, delay bool, request, reply string) {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
|
||||
if secure {
|
||||
conn, err = tls.Dial("tcp", serverAddr, &tls.Config{InsecureSkipVerify: true})
|
||||
} else {
|
||||
conn, err = net.Dial("tcp", serverAddr)
|
||||
}
|
||||
if err != nil {
|
||||
fail(t, "Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if delay {
|
||||
if _, err = io.WriteString(conn, request[:3]); err != nil {
|
||||
fail(t, "Test %d: request send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
if _, err = io.WriteString(conn, request[3:]); err != nil {
|
||||
fail(t, "Test %d: request send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
} else {
|
||||
if _, err = io.WriteString(conn, request); err != nil {
|
||||
fail(t, "Test %d: request send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
}
|
||||
|
||||
received, err := bufio.NewReader(conn).ReadString('\n')
|
||||
if err != nil {
|
||||
fail(t, "Test %d: reply read: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
if received != reply {
|
||||
fail(t, "Test %d: reply: expected = %v, got = %v", i+1, reply, received)
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
handleConnection := func(i int, wg *sync.WaitGroup, serverConn net.Conn, request, reply string) {
|
||||
defer wg.Done()
|
||||
|
||||
received, err := bufio.NewReader(serverConn).ReadString('\n')
|
||||
if err != nil {
|
||||
fail(t, "Test %d: request read: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
if received != request {
|
||||
fail(t, "Test %d: request: expected = %v, got = %v", i+1, request, received)
|
||||
}
|
||||
|
||||
if _, err := io.WriteString(serverConn, reply); err != nil {
|
||||
fail(t, "Test %d: reply send: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
serverConn.Close()
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
listener, err := newHTTPListener(
|
||||
testCase.serverAddrs,
|
||||
testCase.tlsConfig,
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
time.Duration(0),
|
||||
DefaultMaxHeaderBytes,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
|
||||
for _, serverAddr := range listener.Addrs() {
|
||||
go connect(i, serverAddr.String(), testCase.tlsConfig != nil, true, "GET /1 HTTP/1.0\r\nHost: example.org\r\nr\n", testCase.reply)
|
||||
go connect(i, serverAddr.String(), testCase.tlsConfig != nil, false, "GET /2 HTTP/1.0\r\nHost: example.org\r\n\r\n", testCase.reply)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
serverConn, err := listener.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: accept: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
wg.Add(1)
|
||||
go handleConnection(i, &wg, serverConn, "GET /2 HTTP/1.0\r\n", testCase.reply)
|
||||
|
||||
serverConn, err = listener.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: accept: expected = <nil>, got = %v", i+1, err)
|
||||
}
|
||||
wg.Add(1)
|
||||
go handleConnection(i, &wg, serverConn, "GET /1 HTTP/1.0\r\n", testCase.reply)
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
type myTimeoutErr struct {
|
||||
timeout bool
|
||||
}
|
||||
|
||||
+15
-14
@@ -51,15 +51,15 @@ const (
|
||||
// Server - extended http.Server supports multiple addresses to serve and enhanced connection handling.
|
||||
type Server struct {
|
||||
http.Server
|
||||
Addrs []string // addresses on which the server listens for new connection.
|
||||
ShutdownTimeout time.Duration // timeout used for graceful server shutdown.
|
||||
TCPKeepAliveTimeout time.Duration // timeout used for underneath TCP connection.
|
||||
UpdateBytesReadFunc func(*http.Request, int) // function to be called to update bytes read in bufConn.
|
||||
UpdateBytesWrittenFunc func(*http.Request, int) // function to be called to update bytes written in bufConn.
|
||||
listenerMutex *sync.Mutex // to guard 'listener' field.
|
||||
listener *httpListener // HTTP listener for all 'Addrs' field.
|
||||
inShutdown uint32 // indicates whether the server is in shutdown or not
|
||||
requestCount int32 // counter holds no. of request in progress.
|
||||
Addrs []string // addresses on which the server listens for new connection.
|
||||
ShutdownTimeout time.Duration // timeout used for graceful server shutdown.
|
||||
TCPKeepAliveTimeout time.Duration // timeout used for underneath TCP connection.
|
||||
UpdateBytesReadFunc func(int) // function to be called to update bytes read in bufConn.
|
||||
UpdateBytesWrittenFunc func(int) // function to be called to update bytes written in bufConn.
|
||||
listenerMutex *sync.Mutex // to guard 'listener' field.
|
||||
listener *httpListener // HTTP listener for all 'Addrs' field.
|
||||
inShutdown uint32 // indicates whether the server is in shutdown or not
|
||||
requestCount int32 // counter holds no. of request in progress.
|
||||
}
|
||||
|
||||
// GetRequestCount - returns number of request in progress.
|
||||
@@ -80,8 +80,6 @@ func (srv *Server) Start() (err error) {
|
||||
|
||||
addrs := set.CreateStringSet(srv.Addrs...).ToSlice() // copy and remove duplicates
|
||||
tcpKeepAliveTimeout := srv.TCPKeepAliveTimeout
|
||||
updateBytesReadFunc := srv.UpdateBytesReadFunc
|
||||
updateBytesWrittenFunc := srv.UpdateBytesWrittenFunc
|
||||
|
||||
// Create new HTTP listener.
|
||||
var listener *httpListener
|
||||
@@ -92,8 +90,8 @@ func (srv *Server) Start() (err error) {
|
||||
readTimeout,
|
||||
writeTimeout,
|
||||
srv.MaxHeaderBytes,
|
||||
updateBytesReadFunc,
|
||||
updateBytesWrittenFunc,
|
||||
srv.UpdateBytesReadFunc,
|
||||
srv.UpdateBytesWrittenFunc,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -121,6 +119,9 @@ func (srv *Server) Start() (err error) {
|
||||
srv.listenerMutex.Unlock()
|
||||
|
||||
// Start servicing with listener.
|
||||
if tlsConfig != nil {
|
||||
return srv.Server.Serve(tls.NewListener(listener, tlsConfig))
|
||||
}
|
||||
return srv.Server.Serve(listener)
|
||||
}
|
||||
|
||||
@@ -192,7 +193,7 @@ func NewServer(addrs []string, handler http.Handler, getCert certs.GetCertificat
|
||||
CipherSuites: defaultCipherSuites,
|
||||
CurvePreferences: secureCurves,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
NextProtos: []string{"http/1.1"},
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
}
|
||||
tlsConfig.GetCertificate = getCert
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ func newStorageAPI(endpoint Endpoint) (storage StorageAPI, err error) {
|
||||
return newPosix(endpoint.Path)
|
||||
}
|
||||
|
||||
return newStorageRESTClient(endpoint), nil
|
||||
return newStorageRESTClient(endpoint)
|
||||
}
|
||||
|
||||
// Cleanup a directory recursively.
|
||||
|
||||
@@ -239,8 +239,8 @@ func testPutObjectPartDiskNotFound(obj ObjectLayer, instanceType string, disks [
|
||||
if err == nil {
|
||||
t.Fatalf("Test %s: expected to fail but passed instead", instanceType)
|
||||
}
|
||||
// as majority of xl.json are not available, we expect InsufficientReadQuorum while trying to fetch the object quorum
|
||||
expectedErr := InsufficientReadQuorum{}
|
||||
// as majority of xl.json are not available, we expect uploadID to be not available.
|
||||
expectedErr := InvalidUploadID{UploadID: testCase.uploadID}
|
||||
if err.Error() != expectedErr.Error() {
|
||||
t.Fatalf("Test %s: expected error %s, got %s instead.", instanceType, expectedErr, err)
|
||||
}
|
||||
|
||||
@@ -1312,6 +1312,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if crypto.IsEncrypted(objInfo.UserDefined) {
|
||||
objInfo.Size, _ = objInfo.DecryptedSize()
|
||||
switch {
|
||||
case crypto.S3.IsEncrypted(objInfo.UserDefined):
|
||||
w.Header().Set(crypto.SSEHeader, crypto.SSEAlgorithmAES256)
|
||||
@@ -2347,6 +2348,11 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||
host, port = "", ""
|
||||
}
|
||||
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if crypto.IsEncrypted(objInfo.UserDefined) {
|
||||
objInfo.Size, _ = objInfo.DecryptedSize()
|
||||
}
|
||||
}
|
||||
// Notify object created event.
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.ObjectCreatedCompleteMultipartUpload,
|
||||
|
||||
+13
-4
@@ -73,6 +73,7 @@ type posix struct {
|
||||
diskMount bool // indicates if the path is an actual mount.
|
||||
driveSync bool // indicates if the backend is synchronous.
|
||||
|
||||
diskFileInfo os.FileInfo
|
||||
// Disk usage metrics
|
||||
stopUsageCh chan struct{}
|
||||
}
|
||||
@@ -177,7 +178,10 @@ func newPosix(path string) (*posix, error) {
|
||||
if path, err = getValidPath(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := &posix{
|
||||
connected: true,
|
||||
diskPath: path,
|
||||
@@ -188,8 +192,9 @@ func newPosix(path string) (*posix, error) {
|
||||
return &b
|
||||
},
|
||||
},
|
||||
stopUsageCh: make(chan struct{}),
|
||||
diskMount: mountinfo.IsLikelyMountPoint(path),
|
||||
stopUsageCh: make(chan struct{}),
|
||||
diskFileInfo: fi,
|
||||
diskMount: mountinfo.IsLikelyMountPoint(path),
|
||||
}
|
||||
|
||||
var pf BoolFlag
|
||||
@@ -351,7 +356,7 @@ func (s *posix) checkDiskFound() (err error) {
|
||||
if !s.IsOnline() {
|
||||
return errDiskNotFound
|
||||
}
|
||||
_, err = os.Stat(s.diskPath)
|
||||
fi, err := os.Stat(s.diskPath)
|
||||
if err != nil {
|
||||
switch {
|
||||
case os.IsNotExist(err):
|
||||
@@ -364,6 +369,10 @@ func (s *posix) checkDiskFound() (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !os.SameFile(s.diskFileInfo, fi) {
|
||||
s.connected = false
|
||||
return errDiskNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+12
-6
@@ -28,6 +28,7 @@ import (
|
||||
"time"
|
||||
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
// DefaultRESTTimeout - default RPC timeout is one minute.
|
||||
@@ -95,7 +96,7 @@ func newCustomDialContext(timeout time.Duration) func(ctx context.Context, netwo
|
||||
}
|
||||
|
||||
// NewClient - returns new REST client.
|
||||
func NewClient(url *url.URL, tlsConfig *tls.Config, timeout time.Duration, newAuthToken func() string) *Client {
|
||||
func NewClient(url *url.URL, tlsConfig *tls.Config, timeout time.Duration, newAuthToken func() string) (*Client, error) {
|
||||
// Transport is exactly same as Go default in https://golang.org/pkg/net/http/#RoundTripper
|
||||
// except custom DialContext and TLSClientConfig.
|
||||
tr := &http.Transport{
|
||||
@@ -103,17 +104,22 @@ func NewClient(url *url.URL, tlsConfig *tls.Config, timeout time.Duration, newAu
|
||||
DialContext: newCustomDialContext(timeout),
|
||||
MaxIdleConnsPerHost: 4096,
|
||||
MaxIdleConns: 4096,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
IdleConnTimeout: 120 * time.Second,
|
||||
TLSHandshakeTimeout: 30 * time.Second,
|
||||
ExpectContinueTimeout: 10 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
DisableCompression: true,
|
||||
}
|
||||
|
||||
if tlsConfig != nil {
|
||||
// If TLS is enabled configure http2
|
||||
if err := http2.ConfigureTransport(tr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &Client{
|
||||
httpClient: &http.Client{Transport: tr},
|
||||
httpIdleConnsCloser: tr.CloseIdleConnections,
|
||||
url: url,
|
||||
newAuthToken: newAuthToken,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
+2
-2
@@ -55,8 +55,8 @@ var globalHandlers = []HandlerFunc{
|
||||
setBucketForwardingHandler,
|
||||
// Ratelimit the incoming requests using a token bucket algorithm
|
||||
setRateLimitHandler,
|
||||
// Validate all the incoming paths.
|
||||
setPathValidityHandler,
|
||||
// Validate all the incoming requests.
|
||||
setRequestValidityHandler,
|
||||
// Network statistics
|
||||
setHTTPStatsHandler,
|
||||
// Limits all requests size to a maximum fixed limit
|
||||
|
||||
+6
-1
@@ -268,9 +268,14 @@ func NewRPCClient(args RPCClientArgs) (*RPCClient, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rpcClient, err := xrpc.NewClient(args.ServiceURL, args.TLSConfig, defaultRPCTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &RPCClient{
|
||||
args: args,
|
||||
authToken: args.NewAuthTokenFunc(),
|
||||
rpcClient: xrpc.NewClient(args.ServiceURL, args.TLSConfig, defaultRPCTimeout),
|
||||
rpcClient: rpcClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
+32
-21
@@ -29,6 +29,7 @@ import (
|
||||
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
xnet "github.com/minio/minio/pkg/net"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
// DefaultRPCTimeout - default RPC timeout is one minute.
|
||||
@@ -36,8 +37,9 @@ const DefaultRPCTimeout = 1 * time.Minute
|
||||
|
||||
// Client - http based RPC client.
|
||||
type Client struct {
|
||||
httpClient *http.Client
|
||||
serviceURL *xnet.URL
|
||||
httpClient *http.Client
|
||||
httpIdleConnsCloser func()
|
||||
serviceURL *xnet.URL
|
||||
}
|
||||
|
||||
// Call - calls service method on RPC server.
|
||||
@@ -87,8 +89,11 @@ func (client *Client) Call(serviceMethod string, args, reply interface{}) error
|
||||
return gobDecode(callResponse.ReplyBytes, reply)
|
||||
}
|
||||
|
||||
// Close - does nothing and presents for interface compatibility.
|
||||
// Close closes all idle connections of the underlying http client
|
||||
func (client *Client) Close() error {
|
||||
if client.httpIdleConnsCloser != nil {
|
||||
client.httpIdleConnsCloser()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -110,23 +115,29 @@ func newCustomDialContext(timeout time.Duration) func(ctx context.Context, netwo
|
||||
}
|
||||
|
||||
// NewClient - returns new RPC client.
|
||||
func NewClient(serviceURL *xnet.URL, tlsConfig *tls.Config, timeout time.Duration) *Client {
|
||||
return &Client{
|
||||
httpClient: &http.Client{
|
||||
// Transport is exactly same as Go default in https://golang.org/pkg/net/http/#RoundTripper
|
||||
// except custom DialContext and TLSClientConfig.
|
||||
Transport: &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: newCustomDialContext(timeout),
|
||||
MaxIdleConnsPerHost: 4096,
|
||||
MaxIdleConns: 4096,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
DisableCompression: true,
|
||||
},
|
||||
},
|
||||
serviceURL: serviceURL,
|
||||
func NewClient(serviceURL *xnet.URL, tlsConfig *tls.Config, timeout time.Duration) (*Client, error) {
|
||||
// Transport is exactly same as Go default in https://golang.org/pkg/net/http/#RoundTripper
|
||||
// except custom DialContext and TLSClientConfig.
|
||||
tr := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: newCustomDialContext(timeout),
|
||||
MaxIdleConnsPerHost: 4096,
|
||||
MaxIdleConns: 4096,
|
||||
IdleConnTimeout: 120 * time.Second,
|
||||
TLSHandshakeTimeout: 30 * time.Second,
|
||||
ExpectContinueTimeout: 10 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
DisableCompression: true,
|
||||
}
|
||||
if tlsConfig != nil {
|
||||
// If TLS is enabled configure http2
|
||||
if err := http2.ConfigureTransport(tr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &Client{
|
||||
httpClient: &http.Client{Transport: tr},
|
||||
httpIdleConnsCloser: tr.CloseIdleConnections,
|
||||
serviceURL: serviceURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -39,7 +39,10 @@ func TestClientCall(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
rpcClient := NewClient(url, nil, DefaultRPCTimeout)
|
||||
rpcClient, err := NewClient(url, nil, DefaultRPCTimeout)
|
||||
if err != nil {
|
||||
t.Fatalf("NewClient initialization error %v", err)
|
||||
}
|
||||
|
||||
var reply int
|
||||
var boolReply bool
|
||||
|
||||
@@ -34,7 +34,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/cmd/rest"
|
||||
xnet "github.com/minio/minio/pkg/net"
|
||||
)
|
||||
@@ -381,9 +380,11 @@ func (client *storageRESTClient) Close() error {
|
||||
}
|
||||
|
||||
// Returns a storage rest client.
|
||||
func newStorageRESTClient(endpoint Endpoint) *storageRESTClient {
|
||||
func newStorageRESTClient(endpoint Endpoint) (*storageRESTClient, error) {
|
||||
host, err := xnet.ParseHost(endpoint.Host)
|
||||
logger.FatalIf(err, "Unable to parse storage Host")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scheme := "http"
|
||||
if globalIsSSL {
|
||||
@@ -404,8 +405,11 @@ func newStorageRESTClient(endpoint Endpoint) *storageRESTClient {
|
||||
}
|
||||
}
|
||||
|
||||
restClient := rest.NewClient(serverURL, tlsConfig, storageRESTTimeout, newAuthToken)
|
||||
restClient, err := rest.NewClient(serverURL, tlsConfig, storageRESTTimeout, newAuthToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := &storageRESTClient{endpoint: endpoint, restClient: restClient, connected: true}
|
||||
client.connected = client.getInstanceID() == nil
|
||||
return client
|
||||
return client, nil
|
||||
}
|
||||
|
||||
@@ -509,7 +509,10 @@ func newStorageRESTHTTPServerClient(t *testing.T) (*httptest.Server, *storageRES
|
||||
}
|
||||
|
||||
registerStorageRESTHandlers(router, EndpointList{endpoint})
|
||||
restClient := newStorageRESTClient(endpoint)
|
||||
restClient, err := newStorageRESTClient(endpoint)
|
||||
if err != nil {
|
||||
t.Fatalf("newStorageRESTClient failed for %v, with error %s", endpoint, err)
|
||||
}
|
||||
|
||||
prevGlobalServerConfig := globalServerConfig
|
||||
globalServerConfig = newServerConfig()
|
||||
|
||||
+17
-13
@@ -22,12 +22,11 @@ import (
|
||||
)
|
||||
|
||||
// writeSTSErrorRespone writes error headers
|
||||
func writeSTSErrorResponse(w http.ResponseWriter, errorCode STSErrorCode) {
|
||||
stsError := getSTSError(errorCode)
|
||||
func writeSTSErrorResponse(w http.ResponseWriter, err STSError) {
|
||||
// Generate error response.
|
||||
stsErrorResponse := getSTSErrorResponse(stsError)
|
||||
stsErrorResponse := getSTSErrorResponse(err, w.Header().Get(responseRequestIDKey))
|
||||
encodedErrorResponse := encodeResponse(stsErrorResponse)
|
||||
writeResponse(w, stsError.HTTPStatusCode, encodedErrorResponse, mimeXML)
|
||||
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML)
|
||||
}
|
||||
|
||||
// STSError structure
|
||||
@@ -64,9 +63,19 @@ const (
|
||||
ErrSTSInternalError
|
||||
)
|
||||
|
||||
type stsErrorCodeMap map[STSErrorCode]STSError
|
||||
|
||||
func (e stsErrorCodeMap) ToSTSErr(errCode STSErrorCode) STSError {
|
||||
apiErr, ok := e[errCode]
|
||||
if !ok {
|
||||
return e[ErrSTSInternalError]
|
||||
}
|
||||
return apiErr
|
||||
}
|
||||
|
||||
// error code to STSError structure, these fields carry respective
|
||||
// descriptions for all the error responses.
|
||||
var stsErrCodeResponse = map[STSErrorCode]STSError{
|
||||
var stsErrCodes = stsErrorCodeMap{
|
||||
ErrSTSMissingParameter: {
|
||||
Code: "MissingParameter",
|
||||
Description: "A required parameter for the specified action is not supplied.",
|
||||
@@ -109,17 +118,12 @@ var stsErrCodeResponse = map[STSErrorCode]STSError{
|
||||
},
|
||||
}
|
||||
|
||||
// getSTSError provides STS Error for input STS error code.
|
||||
func getSTSError(code STSErrorCode) STSError {
|
||||
return stsErrCodeResponse[code]
|
||||
}
|
||||
|
||||
// getErrorResponse gets in standard error and resource value and
|
||||
// getSTSErrorResponse gets in standard error and
|
||||
// provides a encodable populated response values
|
||||
func getSTSErrorResponse(err STSError) STSErrorResponse {
|
||||
func getSTSErrorResponse(err STSError, requestID string) STSErrorResponse {
|
||||
errRsp := STSErrorResponse{}
|
||||
errRsp.Error.Code = err.Code
|
||||
errRsp.Error.Message = err.Description
|
||||
errRsp.RequestID = "3L137"
|
||||
errRsp.RequestID = requestID
|
||||
return errRsp
|
||||
}
|
||||
|
||||
+11
-11
@@ -70,13 +70,13 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
|
||||
// Parse the incoming form data.
|
||||
if err := r.ParseForm(); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
||||
return
|
||||
}
|
||||
|
||||
if r.Form.Get("Version") != stsAPIVersion {
|
||||
logger.LogIf(ctx, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion))
|
||||
writeSTSErrorResponse(w, ErrSTSMissingParameter)
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSMissingParameter))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
|
||||
case clientGrants, webIdentity:
|
||||
default:
|
||||
logger.LogIf(ctx, fmt.Errorf("Unsupported action %s", action))
|
||||
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -93,14 +93,14 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
|
||||
defer logger.AuditLog(w, r, action, nil)
|
||||
|
||||
if globalIAMValidators == nil {
|
||||
writeSTSErrorResponse(w, ErrSTSNotInitialized)
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSNotInitialized))
|
||||
return
|
||||
}
|
||||
|
||||
v, err := globalIAMValidators.Get("jwt")
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -115,17 +115,17 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
|
||||
case validator.ErrTokenExpired:
|
||||
switch action {
|
||||
case clientGrants:
|
||||
writeSTSErrorResponse(w, ErrSTSClientGrantsExpiredToken)
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSClientGrantsExpiredToken))
|
||||
case webIdentity:
|
||||
writeSTSErrorResponse(w, ErrSTSWebIdentityExpiredToken)
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSWebIdentityExpiredToken))
|
||||
}
|
||||
return
|
||||
case validator.ErrInvalidDuration:
|
||||
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
||||
return
|
||||
}
|
||||
logger.LogIf(ctx, err)
|
||||
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInvalidParameterValue))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
|
||||
cred, err := auth.GetNewCredentialsWithMetadata(m, secret)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
writeSTSErrorResponse(w, ErrSTSInternalError)
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInternalError))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
|
||||
// Set the newly generated credentials.
|
||||
if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, policyName); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
writeSTSErrorResponse(w, ErrSTSInternalError)
|
||||
writeSTSErrorResponse(w, stsErrCodes.ToSTSErr(ErrSTSInternalError))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+2
-78
@@ -1323,82 +1323,6 @@ func (s *xlSets) ListBucketsHeal(ctx context.Context) ([]BucketInfo, error) {
|
||||
return listBuckets, nil
|
||||
}
|
||||
|
||||
// Returns function "listDir" of the type listDirFunc.
|
||||
// isLeaf - is used by listDir function to check if an entry is a leaf or non-leaf entry.
|
||||
// disks - used for doing disk.ListDir(). Sets passes set of disks.
|
||||
func listDirSetsHealFactory(isLeaf isLeafFunc, sets ...[]StorageAPI) listDirFunc {
|
||||
listDirInternal := func(bucket, prefixDir, prefixEntry string, disks []StorageAPI) (mergedEntries []string) {
|
||||
for _, disk := range disks {
|
||||
if disk == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var entries []string
|
||||
var newEntries []string
|
||||
var err error
|
||||
entries, err = disk.ListDir(bucket, prefixDir, -1)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter entries that have the prefix prefixEntry.
|
||||
entries = filterMatchingPrefix(entries, prefixEntry)
|
||||
|
||||
// isLeaf() check has to happen here so that
|
||||
// trailing "/" for objects can be removed.
|
||||
for i, entry := range entries {
|
||||
if isLeaf(bucket, pathJoin(prefixDir, entry)) {
|
||||
entries[i] = strings.TrimSuffix(entry, slashSeparator)
|
||||
}
|
||||
}
|
||||
|
||||
// Find elements in entries which are not in mergedEntries
|
||||
for _, entry := range entries {
|
||||
idx := sort.SearchStrings(mergedEntries, entry)
|
||||
// if entry is already present in mergedEntries don't add.
|
||||
if idx < len(mergedEntries) && mergedEntries[idx] == entry {
|
||||
continue
|
||||
}
|
||||
newEntries = append(newEntries, entry)
|
||||
}
|
||||
|
||||
if len(newEntries) > 0 {
|
||||
// Merge the entries and sort it.
|
||||
mergedEntries = append(mergedEntries, newEntries...)
|
||||
sort.Strings(mergedEntries)
|
||||
}
|
||||
}
|
||||
return mergedEntries
|
||||
|
||||
}
|
||||
|
||||
// listDir - lists all the entries at a given prefix and given entry in the prefix.
|
||||
listDir := func(bucket, prefixDir, prefixEntry string) (mergedEntries []string, delayIsLeaf bool) {
|
||||
for _, disks := range sets {
|
||||
entries := listDirInternal(bucket, prefixDir, prefixEntry, disks)
|
||||
|
||||
var newEntries []string
|
||||
// Find elements in entries which are not in mergedEntries
|
||||
for _, entry := range entries {
|
||||
idx := sort.SearchStrings(mergedEntries, entry)
|
||||
// if entry is already present in mergedEntries don't add.
|
||||
if idx < len(mergedEntries) && mergedEntries[idx] == entry {
|
||||
continue
|
||||
}
|
||||
newEntries = append(newEntries, entry)
|
||||
}
|
||||
|
||||
if len(newEntries) > 0 {
|
||||
// Merge the entries and sort it.
|
||||
mergedEntries = append(mergedEntries, newEntries...)
|
||||
sort.Strings(mergedEntries)
|
||||
}
|
||||
}
|
||||
return mergedEntries, false
|
||||
}
|
||||
return listDir
|
||||
}
|
||||
|
||||
// listObjectsHeal - wrapper function implemented over file tree walk.
|
||||
func (s *xlSets) listObjectsHeal(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (loi ListObjectsInfo, e error) {
|
||||
// Default is recursive, if delimiter is set then list non recursive.
|
||||
@@ -1434,8 +1358,8 @@ func (s *xlSets) listObjectsHeal(ctx context.Context, bucket, prefix, marker, de
|
||||
setDisks = append(setDisks, set.getLoadBalancedDisks())
|
||||
}
|
||||
|
||||
listDir := listDirSetsHealFactory(isLeaf, setDisks...)
|
||||
walkResultCh = startTreeWalk(ctx, bucket, prefix, marker, recursive, listDir, nil, isLeafDir, endWalkCh)
|
||||
listDir := listDirSetsFactory(ctx, isLeaf, isLeafDir, setDisks...)
|
||||
walkResultCh = startTreeWalk(ctx, bucket, prefix, marker, recursive, listDir, isLeaf, isLeafDir, endWalkCh)
|
||||
}
|
||||
|
||||
var objInfos []ObjectInfo
|
||||
|
||||
+33
-36
@@ -20,8 +20,6 @@ import (
|
||||
"context"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
)
|
||||
|
||||
// getLoadBalancedDisks - fetches load balanced (sufficiently randomized) disk slice.
|
||||
@@ -52,31 +50,44 @@ func (xl xlObjects) parentDirIsObject(ctx context.Context, bucket, parent string
|
||||
return isParentDirObject(parent)
|
||||
}
|
||||
|
||||
var xlTreeWalkIgnoredErrs = append(baseIgnoredErrs, errDiskAccessDenied, errVolumeNotFound, errFileNotFound)
|
||||
|
||||
// isObject - returns `true` if the prefix is an object i.e if
|
||||
// `xl.json` exists at the leaf, false otherwise.
|
||||
func (xl xlObjects) isObject(bucket, prefix string) (ok bool) {
|
||||
for _, disk := range xl.getLoadBalancedDisks() {
|
||||
var errs = make([]error, len(xl.getDisks()))
|
||||
var wg sync.WaitGroup
|
||||
for index, disk := range xl.getDisks() {
|
||||
if disk == nil {
|
||||
continue
|
||||
}
|
||||
// Check if 'prefix' is an object on this 'disk', else continue the check the next disk
|
||||
_, err := disk.StatFile(bucket, path.Join(prefix, xlMetaJSONFile))
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
// Ignore for file not found, disk not found or faulty disk.
|
||||
if IsErrIgnored(err, xlTreeWalkIgnoredErrs...) {
|
||||
continue
|
||||
}
|
||||
} // Exhausted all disks - return false.
|
||||
return false
|
||||
wg.Add(1)
|
||||
go func(index int, disk StorageAPI) {
|
||||
defer wg.Done()
|
||||
// Check if 'prefix' is an object on this 'disk', else continue the check the next disk
|
||||
fi, err := disk.StatFile(bucket, path.Join(prefix, xlMetaJSONFile))
|
||||
if err != nil {
|
||||
errs[index] = err
|
||||
return
|
||||
}
|
||||
if fi.Size == 0 {
|
||||
errs[index] = errCorruptedFormat
|
||||
return
|
||||
}
|
||||
}(index, disk)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
// NOTE: Observe we are not trying to read `xl.json` and figure out the actual
|
||||
// quorum intentionally, but rely on the default case scenario. Actual quorum
|
||||
// verification will happen by top layer by using getObjectInfo() and will be
|
||||
// ignored if necessary.
|
||||
readQuorum := len(xl.getDisks()) / 2
|
||||
|
||||
return reduceReadQuorumErrs(context.Background(), errs, objectOpIgnoredErrs, readQuorum) == nil
|
||||
}
|
||||
|
||||
// isObjectDir returns if the specified path represents an empty directory.
|
||||
func (xl xlObjects) isObjectDir(bucket, prefix string) (ok bool) {
|
||||
var objectDirs = make([]bool, len(xl.getDisks()))
|
||||
var errs = make([]error, len(xl.getDisks()))
|
||||
var wg sync.WaitGroup
|
||||
for index, disk := range xl.getDisks() {
|
||||
@@ -92,7 +103,10 @@ func (xl xlObjects) isObjectDir(bucket, prefix string) (ok bool) {
|
||||
errs[index] = err
|
||||
return
|
||||
}
|
||||
objectDirs[index] = len(entries) == 0
|
||||
if len(entries) > 0 {
|
||||
errs[index] = errVolumeNotEmpty
|
||||
return
|
||||
}
|
||||
}(index, disk)
|
||||
}
|
||||
|
||||
@@ -100,22 +114,5 @@ func (xl xlObjects) isObjectDir(bucket, prefix string) (ok bool) {
|
||||
|
||||
readQuorum := len(xl.getDisks()) / 2
|
||||
|
||||
err := reduceReadQuorumErrs(context.Background(), errs, xlTreeWalkIgnoredErrs, readQuorum)
|
||||
if err != nil {
|
||||
reqInfo := &logger.ReqInfo{BucketName: bucket}
|
||||
reqInfo.AppendTags("prefix", prefix)
|
||||
ctx := logger.SetReqInfo(context.Background(), reqInfo)
|
||||
logger.LogIf(ctx, err)
|
||||
return false
|
||||
}
|
||||
|
||||
for _, ok := range objectDirs {
|
||||
if !ok {
|
||||
// We perhaps found some entries in a prefix.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if and only if the all directories are empty.
|
||||
return true
|
||||
return reduceReadQuorumErrs(context.Background(), errs, objectOpIgnoredErrs, readQuorum) == nil
|
||||
}
|
||||
|
||||
Executable
+170
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Minio Cloud Storage, (C) 2019 Minio, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
)
|
||||
|
||||
const (
|
||||
initGraceTime = 300
|
||||
healthPath = "/minio/health/live"
|
||||
timeout = time.Duration(30 * time.Second)
|
||||
minioProcess = "minio"
|
||||
)
|
||||
|
||||
// returns container boot time by finding
|
||||
// modtime of /proc/1 directory
|
||||
func getStartTime() time.Time {
|
||||
di, err := os.Stat("/proc/1")
|
||||
if err != nil {
|
||||
// Cant stat proc dir successfully, exit with error
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
return di.ModTime()
|
||||
}
|
||||
|
||||
// Returns the ip:port of the Minio process
|
||||
// running in the container
|
||||
func findEndpoint() string {
|
||||
cmd := exec.Command("netstat", "-ntlp")
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
// error getting stdout pipe
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
if err = cmd.Start(); err != nil {
|
||||
// error starting the command
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
// split netstat output in rows
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
// loop over the rows to find Minio process
|
||||
for scanner.Scan() {
|
||||
if strings.Contains(scanner.Text(), minioProcess) {
|
||||
line := scanner.Text()
|
||||
newLine := strings.Replace(line, ":::", "127.0.0.1:", 1)
|
||||
fields := strings.Fields(newLine)
|
||||
// index 3 in the row has the Local address
|
||||
// find the last index of ":" - address will
|
||||
// have port number after this index
|
||||
i := strings.LastIndex(fields[3], ":")
|
||||
// split address and port
|
||||
addr := fields[3][:i]
|
||||
port := fields[3][i+1:]
|
||||
// add surrounding [] for ip6 address
|
||||
if strings.Count(addr, ":") > 0 {
|
||||
addr = strings.Join([]string{"[", addr, "]"}, "")
|
||||
}
|
||||
// return joint address and port
|
||||
return strings.Join([]string{addr, port}, ":")
|
||||
}
|
||||
}
|
||||
if err = cmd.Wait(); err != nil {
|
||||
// command failed to run
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
// minio process not found, exit with error
|
||||
os.Exit(1)
|
||||
return ""
|
||||
}
|
||||
|
||||
func main() {
|
||||
startTime := getStartTime()
|
||||
|
||||
// In distributed environment like Swarm, traffic is routed
|
||||
// to a container only when it reports a `healthy` status. So, we exit
|
||||
// with 0 to ensure healthy status till distributed Minio starts (120s).
|
||||
|
||||
// Refer: https://github.com/moby/moby/pull/28938#issuecomment-301753272
|
||||
|
||||
if (time.Now().Sub(startTime) / time.Second) < initGraceTime {
|
||||
os.Exit(0)
|
||||
} else {
|
||||
endPoint := findEndpoint()
|
||||
u, err := url.Parse(fmt.Sprintf("http://%s%s", endPoint, healthPath))
|
||||
if err != nil {
|
||||
// Could not parse URL successfully
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// Minio server may be using self-signed or CA certificates. To avoid
|
||||
// making Docker setup complicated, we skip verifying certificates here.
|
||||
// This is because, following request tests for health status within
|
||||
// containerized environment, i.e. requests are always made to the Minio
|
||||
// server running on the same host.
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client := &http.Client{Transport: tr, Timeout: timeout}
|
||||
resp, err := client.Get(u.String())
|
||||
if err != nil {
|
||||
// GET failed exit
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
// Drain any response.
|
||||
xhttp.DrainBody(resp.Body)
|
||||
// exit with success
|
||||
os.Exit(0)
|
||||
}
|
||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
// Drain any response.
|
||||
xhttp.DrainBody(resp.Body)
|
||||
// GET failed exit
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
bodyString := string(bodyBytes)
|
||||
// Drain any response.
|
||||
xhttp.DrainBody(resp.Body)
|
||||
// This means sever is configured with https
|
||||
if resp.StatusCode == http.StatusForbidden && bodyString == "SSL required" {
|
||||
// Try with https
|
||||
u.Scheme = "https"
|
||||
resp, err = client.Get(u.String())
|
||||
if err != nil {
|
||||
// GET failed exit
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
// Drain any response.
|
||||
xhttp.DrainBody(resp.Body)
|
||||
// exit with success
|
||||
os.Exit(0)
|
||||
}
|
||||
// Drain any response.
|
||||
xhttp.DrainBody(resp.Body)
|
||||
}
|
||||
}
|
||||
// Execution reaching here means none of
|
||||
// the success cases were satisfied
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Minio Cloud Storage, (C) 2017 Minio, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
set -x
|
||||
|
||||
_init () {
|
||||
scheme="http://"
|
||||
address="$(netstat -nplt 2>/dev/null | awk ' /(.*\/minio)/ { gsub(":::","127.0.0.1:",$4); print $4}')"
|
||||
resource="/minio/health/live"
|
||||
start=$(stat -c "%Y" /proc/1)
|
||||
}
|
||||
|
||||
healthcheck_main () {
|
||||
# In distributed environment like Swarm, traffic is routed
|
||||
# to a container only when it reports a `healthy` status. So, we exit
|
||||
# with 0 to ensure healthy status till distributed Minio starts (120s).
|
||||
#
|
||||
# Refer: https://github.com/moby/moby/pull/28938#issuecomment-301753272
|
||||
if [ $(( $(date +%s) - start )) -lt 120 ]; then
|
||||
exit 0
|
||||
else
|
||||
# Get the http response code
|
||||
http_response=$(curl -s -k -o /dev/null -w "%{http_code}" ${scheme}${address}${resource})
|
||||
|
||||
# Get the http response body
|
||||
http_response_body=$(curl -k -s ${scheme}${address}${resource})
|
||||
|
||||
# server returns response 403 and body "SSL required" if non-TLS
|
||||
# connection is attempted on a TLS-configured server. Change
|
||||
# the scheme and try again
|
||||
if [ "$http_response" = "403" ] && \
|
||||
[ "$http_response_body" = "SSL required" ]; then
|
||||
scheme="https://"
|
||||
http_response=$(curl -s -k -o /dev/null -w "%{http_code}" ${scheme}${address}${resource})
|
||||
fi
|
||||
|
||||
# If http_response is 200 - server is up.
|
||||
[ "$http_response" = "200" ]
|
||||
fi
|
||||
}
|
||||
|
||||
_init && healthcheck_main
|
||||
@@ -5,7 +5,7 @@ version: '2'
|
||||
# 9001 through 9004.
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
volumes:
|
||||
- data1:/data
|
||||
ports:
|
||||
@@ -15,7 +15,7 @@ services:
|
||||
MINIO_SECRET_KEY: minio123
|
||||
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
volumes:
|
||||
- data2:/data
|
||||
ports:
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
MINIO_SECRET_KEY: minio123
|
||||
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
volumes:
|
||||
- data3:/data
|
||||
ports:
|
||||
@@ -35,7 +35,7 @@ services:
|
||||
MINIO_SECRET_KEY: minio123
|
||||
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
volumes:
|
||||
- data4:/data
|
||||
ports:
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.1'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -24,7 +24,7 @@ services:
|
||||
- access_key
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -46,7 +46,7 @@ services:
|
||||
- access_key
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -68,7 +68,7 @@ services:
|
||||
- access_key
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -24,7 +24,7 @@ services:
|
||||
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -46,7 +46,7 @@ services:
|
||||
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -68,7 +68,7 @@ services:
|
||||
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
@@ -30,7 +30,7 @@ spec:
|
||||
value: "minio"
|
||||
- name: MINIO_SECRET_KEY
|
||||
value: "minio123"
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
# Unfortunately you must manually define each server. Perhaps autodiscovery via DNS can be implemented in the future.
|
||||
args:
|
||||
- server
|
||||
|
||||
@@ -21,7 +21,7 @@ spec:
|
||||
value: "minio"
|
||||
- name: MINIO_SECRET_KEY
|
||||
value: "minio123"
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
args:
|
||||
- server
|
||||
- http://minio-0.minio.default.svc.cluster.local/data
|
||||
|
||||
@@ -21,7 +21,7 @@ spec:
|
||||
containers:
|
||||
- name: minio
|
||||
# Pulls the default Minio image from Docker Hub
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
args:
|
||||
- gateway
|
||||
- gcs
|
||||
|
||||
@@ -29,7 +29,7 @@ spec:
|
||||
- name: data
|
||||
mountPath: "/data"
|
||||
# Pulls the lastest Minio image from Docker Hub
|
||||
image: minio/minio:RELEASE.2019-02-12T21-58-47Z
|
||||
image: minio/minio:RELEASE.2019-02-14T00-21-45Z
|
||||
args:
|
||||
- server
|
||||
- /data
|
||||
|
||||
@@ -277,7 +277,7 @@ func (e *FuncExpr) aggregateRow(r Record) error {
|
||||
func (e *FuncExpr) getAggregate() (*Value, error) {
|
||||
switch e.getFunctionName() {
|
||||
case aggFnCount:
|
||||
return FromFloat(float64(e.aggregate.runningCount)), nil
|
||||
return FromInt(e.aggregate.runningCount), nil
|
||||
|
||||
case aggFnAvg:
|
||||
if e.aggregate.runningCount == 0 {
|
||||
|
||||
Reference in New Issue
Block a user