mirror of
https://github.com/pgsty/minio.git
synced 2026-10-11 20:25:58 +03:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be97ae4c5d | |||
| 4d7d008741 | |||
| 2d7a3d1516 | |||
| dfab400d43 | |||
| 70078eab10 | |||
| 3415c4dd1e | |||
| e200808ab7 | |||
| fae563b85d | |||
| 3e6dc02f8f | |||
| 95e4cbbfde | |||
| 2825294b7b | |||
| bce93b5cfa | |||
| 7a4b250c8b | |||
| e5335450a4 | |||
| a6ffdf1dd4 | |||
| 69e41f87ef | |||
| ee48f9f206 | |||
| 9ba39d7fad | |||
| d2fb371f80 | |||
| 2f9018f03b | |||
| eb990f64a9 | |||
| bbb64eaade | |||
| 7bd1d899bc | |||
| c91d1ec2e3 | |||
| c50b64027d |
@@ -165,9 +165,9 @@ hotfix-vars:
|
||||
$(eval VERSION := $(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD))
|
||||
|
||||
hotfix: hotfix-vars clean install ## builds minio binary with hotfix tags
|
||||
@wget -q -c https://github.com/minio/pkger/releases/download/v2.2.9/pkger_2.2.9_linux_amd64.deb
|
||||
@wget -q -c https://github.com/minio/pkger/releases/download/v2.3.1/pkger_2.3.1_linux_amd64.deb
|
||||
@wget -q -c https://raw.githubusercontent.com/minio/minio-service/v1.0.1/linux-systemd/distributed/minio.service
|
||||
@sudo apt install ./pkger_2.2.9_linux_amd64.deb --yes
|
||||
@sudo apt install ./pkger_2.3.1_linux_amd64.deb --yes
|
||||
@mkdir -p minio-release/$(GOOS)-$(GOARCH)/archive
|
||||
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio
|
||||
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION)
|
||||
|
||||
@@ -39,7 +39,7 @@ function start_minio_3_node() {
|
||||
|
||||
export MC_HOST_myminio="http://minio:minio123@127.0.0.1:$((start_port + 1))"
|
||||
|
||||
/tmp/mc ready myminio
|
||||
timeout 15m /tmp/mc ready myminio || fail
|
||||
|
||||
# Wait for all drives to be online and formatted
|
||||
while [ $(/tmp/mc admin info --json myminio | jq '.info.servers[].drives[].state | select(. != "ok")' | wc -l) -gt 0 ]; do sleep 1; done
|
||||
@@ -62,48 +62,23 @@ function start_minio_3_node() {
|
||||
fi
|
||||
|
||||
# Failure
|
||||
for i in $(seq 1 3); do
|
||||
echo "server$i log:"
|
||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||
done
|
||||
pkill -9 minio
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fail
|
||||
done
|
||||
|
||||
if ! ps -p $pid1 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/dist-minio-server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
echo "minio-server-1 is not running." && fail
|
||||
fi
|
||||
|
||||
if ! ps -p $pid2 1>&2 >/dev/null; then
|
||||
echo "server2 log:"
|
||||
cat "${WORK_DIR}/dist-minio-server2.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
echo "minio-server-2 is not running." && fail
|
||||
fi
|
||||
|
||||
if ! ps -p $pid3 1>&2 >/dev/null; then
|
||||
echo "server3 log:"
|
||||
cat "${WORK_DIR}/dist-minio-server3.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
echo "minio-server-3 is not running." && fail
|
||||
fi
|
||||
|
||||
if ! pkill minio; then
|
||||
for i in $(seq 1 3); do
|
||||
echo "server$i log:"
|
||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||
done
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fail
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
@@ -115,6 +90,16 @@ function start_minio_3_node() {
|
||||
fi
|
||||
}
|
||||
|
||||
function fail() {
|
||||
for i in $(seq 1 3); do
|
||||
echo "server$i log:"
|
||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||
done
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function check_online() {
|
||||
if ! grep -q 'Status:' ${WORK_DIR}/dist-minio-*.log; then
|
||||
echo "1"
|
||||
|
||||
@@ -47,43 +47,25 @@ function start_minio_3_node() {
|
||||
disown $pid3
|
||||
|
||||
export MC_HOST_myminio="http://minio:minio123@127.0.0.1:$((start_port + 1))"
|
||||
/tmp/mc ready myminio
|
||||
timeout 15m /tmp/mc ready myminio || fail
|
||||
|
||||
[ ${first_time} -eq 0 ] && upload_objects
|
||||
[ ${first_time} -ne 0 ] && sleep 120
|
||||
|
||||
if ! ps -p $pid1 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/dist-minio-server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
echo "minio server 1 is not running" && fail
|
||||
fi
|
||||
|
||||
if ! ps -p $pid2 1>&2 >/dev/null; then
|
||||
echo "server2 log:"
|
||||
cat "${WORK_DIR}/dist-minio-server2.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
echo "minio server 2 is not running" && fail
|
||||
fi
|
||||
|
||||
if ! ps -p $pid3 1>&2 >/dev/null; then
|
||||
echo "server3 log:"
|
||||
cat "${WORK_DIR}/dist-minio-server3.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
echo "minio server 3 is not running" && fail
|
||||
fi
|
||||
|
||||
if ! pkill minio; then
|
||||
for i in $(seq 1 3); do
|
||||
echo "server$i log:"
|
||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||
done
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fail
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
@@ -118,6 +100,17 @@ function purge() {
|
||||
rm -rf "$1"
|
||||
}
|
||||
|
||||
function fail() {
|
||||
for i in $(seq 1 3); do
|
||||
echo "server$i log:"
|
||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||
done
|
||||
pkill -9 minio
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function __init__() {
|
||||
echo "Initializing environment"
|
||||
mkdir -p "$WORK_DIR"
|
||||
@@ -155,14 +148,7 @@ function perform_test() {
|
||||
check_heal ${1}
|
||||
rv=$?
|
||||
if [ "$rv" == "1" ]; then
|
||||
for i in $(seq 1 3); do
|
||||
echo "server$i log:"
|
||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||
done
|
||||
pkill -9 minio
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fail
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -237,6 +237,7 @@ func (a adminAPIHandlers) ServerUpdateV2Handler(w http.ResponseWriter, r *http.R
|
||||
|
||||
if globalIsDistErasure {
|
||||
// Notify all other MinIO peers signal service.
|
||||
startTime := time.Now().Add(restartUpdateDelay)
|
||||
ng := WithNPeers(len(globalNotificationSys.peerClients))
|
||||
for idx, client := range globalNotificationSys.peerClients {
|
||||
_, ok := failedClients[idx]
|
||||
@@ -247,7 +248,7 @@ func (a adminAPIHandlers) ServerUpdateV2Handler(w http.ResponseWriter, r *http.R
|
||||
ng.Go(ctx, func() error {
|
||||
prs, ok := peerResults[client.String()]
|
||||
if ok && prs.CurrentVersion != prs.UpdatedVersion && prs.UpdatedVersion != "" {
|
||||
return client.SignalService(serviceRestart, "", dryRun)
|
||||
return client.SignalService(serviceRestart, "", dryRun, &startTime)
|
||||
}
|
||||
return nil
|
||||
}, idx, *client.host)
|
||||
@@ -542,9 +543,12 @@ func (a adminAPIHandlers) ServiceV2Handler(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
|
||||
var objectAPI ObjectLayer
|
||||
var execAt *time.Time
|
||||
switch serviceSig {
|
||||
case serviceRestart:
|
||||
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceRestartAdminAction)
|
||||
t := time.Now().Add(restartUpdateDelay)
|
||||
execAt = &t
|
||||
case serviceStop:
|
||||
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceStopAdminAction)
|
||||
case serviceFreeze, serviceUnFreeze:
|
||||
@@ -571,7 +575,7 @@ func (a adminAPIHandlers) ServiceV2Handler(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
|
||||
if globalIsDistErasure {
|
||||
for _, nerr := range globalNotificationSys.SignalServiceV2(serviceSig, dryRun) {
|
||||
for _, nerr := range globalNotificationSys.SignalServiceV2(serviceSig, dryRun, execAt) {
|
||||
if nerr.Err != nil && process {
|
||||
waitingDrives := map[string]madmin.DiskMetrics{}
|
||||
jerr := json.Unmarshal([]byte(nerr.Err.Error()), &waitingDrives)
|
||||
@@ -2417,7 +2421,7 @@ func getServerInfo(ctx context.Context, pools, metrics bool, r *http.Request) ma
|
||||
Mode: string(mode),
|
||||
Domain: domain,
|
||||
Region: globalSite.Region(),
|
||||
SQSARN: globalEventNotifier.GetARNList(false),
|
||||
SQSARN: globalEventNotifier.GetARNList(),
|
||||
DeploymentID: globalDeploymentID(),
|
||||
Buckets: buckets,
|
||||
Objects: objects,
|
||||
|
||||
+2
-2
@@ -436,7 +436,7 @@ func registerAPIRouter(router *mux.Router) {
|
||||
Queries("notification", "")
|
||||
// ListenNotification
|
||||
router.Methods(http.MethodGet).
|
||||
HandlerFunc(s3APIMiddleware(api.ListenNotificationHandler, noThrottleS3HFlag)).
|
||||
HandlerFunc(s3APIMiddleware(api.ListenNotificationHandler, noThrottleS3HFlag, traceHdrsS3HFlag)).
|
||||
Queries("events", "{events:.*}")
|
||||
// ResetBucketReplicationStatus - MinIO extension API
|
||||
router.Methods(http.MethodGet).
|
||||
@@ -615,7 +615,7 @@ func registerAPIRouter(router *mux.Router) {
|
||||
|
||||
// ListenNotification
|
||||
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).
|
||||
HandlerFunc(s3APIMiddleware(api.ListenNotificationHandler, noThrottleS3HFlag)).
|
||||
HandlerFunc(s3APIMiddleware(api.ListenNotificationHandler, noThrottleS3HFlag, traceHdrsS3HFlag)).
|
||||
Queries("events", "{events:.*}")
|
||||
|
||||
// ListBuckets
|
||||
|
||||
@@ -19,9 +19,13 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -43,10 +47,15 @@ type ServerSystemConfig struct {
|
||||
NEndpoints int
|
||||
CmdLines []string
|
||||
MinioEnv map[string]string
|
||||
Checksum string
|
||||
}
|
||||
|
||||
// Diff - returns error on first difference found in two configs.
|
||||
func (s1 *ServerSystemConfig) Diff(s2 *ServerSystemConfig) error {
|
||||
if s1.Checksum != s2.Checksum {
|
||||
return fmt.Errorf("Expected MinIO binary checksum: %s, seen: %s", s1.Checksum, s2.Checksum)
|
||||
}
|
||||
|
||||
ns1 := s1.NEndpoints
|
||||
ns2 := s2.NEndpoints
|
||||
if ns1 != ns2 {
|
||||
@@ -82,7 +91,7 @@ func (s1 *ServerSystemConfig) Diff(s2 *ServerSystemConfig) error {
|
||||
extra = append(extra, k)
|
||||
}
|
||||
}
|
||||
msg := "Expected same MINIO_ environment variables and values across all servers: "
|
||||
msg := "Expected MINIO_* environment name and values across all servers to be same: "
|
||||
if len(missing) > 0 {
|
||||
msg += fmt.Sprintf(`Missing environment values: %v. `, missing)
|
||||
}
|
||||
@@ -120,7 +129,7 @@ func getServerSystemCfg() *ServerSystemConfig {
|
||||
}
|
||||
envValues[envK] = logger.HashString(env.Get(envK, ""))
|
||||
}
|
||||
scfg := &ServerSystemConfig{NEndpoints: globalEndpoints.NEndpoints(), MinioEnv: envValues}
|
||||
scfg := &ServerSystemConfig{NEndpoints: globalEndpoints.NEndpoints(), MinioEnv: envValues, Checksum: binaryChecksum}
|
||||
var cmdLines []string
|
||||
for _, ep := range globalEndpoints {
|
||||
cmdLines = append(cmdLines, ep.CmdLine)
|
||||
@@ -167,6 +176,18 @@ func (client *bootstrapRESTClient) String() string {
|
||||
return client.gridConn.String()
|
||||
}
|
||||
|
||||
var binaryChecksum = getBinaryChecksum()
|
||||
|
||||
func getBinaryChecksum() string {
|
||||
mw := md5.New()
|
||||
b, err := os.Open(os.Args[0])
|
||||
if err == nil {
|
||||
defer b.Close()
|
||||
io.Copy(mw, b)
|
||||
}
|
||||
return hex.EncodeToString(mw.Sum(nil))
|
||||
}
|
||||
|
||||
func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointServerPools, gm *grid.Manager) error {
|
||||
srcCfg := getServerSystemCfg()
|
||||
clnts := newBootstrapRESTClients(endpointServerPools, gm)
|
||||
@@ -196,7 +217,7 @@ func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointS
|
||||
err := clnt.Verify(ctx, srcCfg)
|
||||
mu.Lock()
|
||||
if err != nil {
|
||||
bootstrapTraceMsg(fmt.Sprintf("clnt.Verify: %v, endpoint: %s", err, clnt))
|
||||
bootstrapTraceMsg(fmt.Sprintf("bootstrapVerify: %v, endpoint: %s", err, clnt))
|
||||
if !isNetworkError(err) {
|
||||
bootLogOnceIf(context.Background(), fmt.Errorf("%s has incorrect configuration: %w", clnt, err), "incorrect_"+clnt.String())
|
||||
incorrectConfigs = append(incorrectConfigs, fmt.Errorf("%s has incorrect configuration: %w", clnt, err))
|
||||
|
||||
@@ -79,6 +79,12 @@ func (z *ServerSystemConfig) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
}
|
||||
z.MinioEnv[za0002] = za0003
|
||||
}
|
||||
case "Checksum":
|
||||
z.Checksum, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Checksum")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = dc.Skip()
|
||||
if err != nil {
|
||||
@@ -92,9 +98,9 @@ func (z *ServerSystemConfig) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *ServerSystemConfig) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 3
|
||||
// map header, size 4
|
||||
// write "NEndpoints"
|
||||
err = en.Append(0x83, 0xaa, 0x4e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73)
|
||||
err = en.Append(0x84, 0xaa, 0x4e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -142,15 +148,25 @@ func (z *ServerSystemConfig) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// write "Checksum"
|
||||
err = en.Append(0xa8, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Checksum)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Checksum")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *ServerSystemConfig) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 3
|
||||
// map header, size 4
|
||||
// string "NEndpoints"
|
||||
o = append(o, 0x83, 0xaa, 0x4e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73)
|
||||
o = append(o, 0x84, 0xaa, 0x4e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73)
|
||||
o = msgp.AppendInt(o, z.NEndpoints)
|
||||
// string "CmdLines"
|
||||
o = append(o, 0xa8, 0x43, 0x6d, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x73)
|
||||
@@ -165,6 +181,9 @@ func (z *ServerSystemConfig) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.AppendString(o, za0002)
|
||||
o = msgp.AppendString(o, za0003)
|
||||
}
|
||||
// string "Checksum"
|
||||
o = append(o, 0xa8, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d)
|
||||
o = msgp.AppendString(o, z.Checksum)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -241,6 +260,12 @@ func (z *ServerSystemConfig) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
}
|
||||
z.MinioEnv[za0002] = za0003
|
||||
}
|
||||
case "Checksum":
|
||||
z.Checksum, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Checksum")
|
||||
return
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
@@ -266,5 +291,6 @@ func (z *ServerSystemConfig) Msgsize() (s int) {
|
||||
s += msgp.StringPrefixSize + len(za0002) + msgp.StringPrefixSize + len(za0003)
|
||||
}
|
||||
}
|
||||
s += 9 + msgp.StringPrefixSize + len(z.Checksum)
|
||||
return
|
||||
}
|
||||
|
||||
+6
-39
@@ -51,7 +51,6 @@ import (
|
||||
sse "github.com/minio/minio/internal/bucket/encryption"
|
||||
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
||||
"github.com/minio/minio/internal/bucket/replication"
|
||||
"github.com/minio/minio/internal/config/cache"
|
||||
"github.com/minio/minio/internal/config/dns"
|
||||
"github.com/minio/minio/internal/crypto"
|
||||
"github.com/minio/minio/internal/event"
|
||||
@@ -92,7 +91,7 @@ const (
|
||||
// -- If IP of the entry doesn't match, this means entry is
|
||||
//
|
||||
// for another instance. Log an error to console.
|
||||
func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
||||
func initFederatorBackend(buckets []string, objLayer ObjectLayer) {
|
||||
if len(buckets) == 0 {
|
||||
return
|
||||
}
|
||||
@@ -113,10 +112,10 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
||||
domainMissing := err == dns.ErrDomainMissing
|
||||
if dnsBuckets != nil {
|
||||
for _, bucket := range buckets {
|
||||
bucketsSet.Add(bucket.Name)
|
||||
r, ok := dnsBuckets[bucket.Name]
|
||||
bucketsSet.Add(bucket)
|
||||
r, ok := dnsBuckets[bucket]
|
||||
if !ok {
|
||||
bucketsToBeUpdated.Add(bucket.Name)
|
||||
bucketsToBeUpdated.Add(bucket)
|
||||
continue
|
||||
}
|
||||
if !globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(r)...)).IsEmpty() {
|
||||
@@ -135,7 +134,7 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
||||
// but if we do see a difference with local domain IPs with
|
||||
// hostSlice from etcd then we should update with newer
|
||||
// domainIPs, we proceed to do that here.
|
||||
bucketsToBeUpdated.Add(bucket.Name)
|
||||
bucketsToBeUpdated.Add(bucket)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -144,7 +143,7 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
|
||||
// bucket names are globally unique in federation at a given
|
||||
// path prefix, name collision is not allowed. We simply log
|
||||
// an error and continue.
|
||||
bucketsInConflict.Add(bucket.Name)
|
||||
bucketsInConflict.Add(bucket)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1342,22 +1341,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
continue
|
||||
}
|
||||
|
||||
asize, err := objInfo.GetActualSize()
|
||||
if err != nil {
|
||||
asize = objInfo.Size
|
||||
}
|
||||
|
||||
globalCacheConfig.Set(&cache.ObjectInfo{
|
||||
Key: objInfo.Name,
|
||||
Bucket: objInfo.Bucket,
|
||||
ETag: getDecryptedETag(formValues, objInfo, false),
|
||||
ModTime: objInfo.ModTime,
|
||||
Expires: objInfo.Expires.UTC().Format(http.TimeFormat),
|
||||
CacheControl: objInfo.CacheControl,
|
||||
Metadata: cleanReservedKeys(objInfo.UserDefined),
|
||||
Size: asize,
|
||||
})
|
||||
|
||||
fanOutResp = append(fanOutResp, minio.PutObjectFanOutResponse{
|
||||
Key: objInfo.Name,
|
||||
ETag: getDecryptedETag(formValues, objInfo, false),
|
||||
@@ -1452,22 +1435,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
w.Header().Set(xhttp.Location, obj)
|
||||
}
|
||||
|
||||
asize, err := objInfo.GetActualSize()
|
||||
if err != nil {
|
||||
asize = objInfo.Size
|
||||
}
|
||||
|
||||
defer globalCacheConfig.Set(&cache.ObjectInfo{
|
||||
Key: objInfo.Name,
|
||||
Bucket: objInfo.Bucket,
|
||||
ETag: etag,
|
||||
ModTime: objInfo.ModTime,
|
||||
Expires: objInfo.ExpiresStr(),
|
||||
CacheControl: objInfo.CacheControl,
|
||||
Metadata: cleanReservedKeys(objInfo.UserDefined),
|
||||
Size: asize,
|
||||
})
|
||||
|
||||
// Notify object created event.
|
||||
defer sendEvent(eventArgs{
|
||||
EventName: event.ObjectCreatedPost,
|
||||
|
||||
+14
-18
@@ -392,9 +392,7 @@ func (sys *BucketMetadataSys) GetReplicationConfig(ctx context.Context, bucket s
|
||||
return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket}
|
||||
}
|
||||
if reloaded {
|
||||
globalBucketTargetSys.set(BucketInfo{
|
||||
Name: bucket,
|
||||
}, meta)
|
||||
globalBucketTargetSys.set(bucket, meta)
|
||||
}
|
||||
return meta.replicationConfig, meta.ReplicationConfigUpdatedAt, nil
|
||||
}
|
||||
@@ -413,9 +411,7 @@ func (sys *BucketMetadataSys) GetBucketTargetsConfig(bucket string) (*madmin.Buc
|
||||
return nil, BucketRemoteTargetNotFound{Bucket: bucket}
|
||||
}
|
||||
if reloaded {
|
||||
globalBucketTargetSys.set(BucketInfo{
|
||||
Name: bucket,
|
||||
}, meta)
|
||||
globalBucketTargetSys.set(bucket, meta)
|
||||
}
|
||||
return meta.bucketTargetConfig, nil
|
||||
}
|
||||
@@ -471,7 +467,7 @@ func (sys *BucketMetadataSys) GetConfig(ctx context.Context, bucket string) (met
|
||||
}
|
||||
|
||||
// Init - initializes bucket metadata system for all buckets.
|
||||
func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) error {
|
||||
func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []string, objAPI ObjectLayer) error {
|
||||
if objAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
@@ -484,7 +480,7 @@ func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []BucketInfo, ob
|
||||
}
|
||||
|
||||
// concurrently load bucket metadata to speed up loading bucket metadata.
|
||||
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo, failedBuckets map[string]struct{}) {
|
||||
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []string, failedBuckets map[string]struct{}) {
|
||||
g := errgroup.WithNErrs(len(buckets))
|
||||
bucketMetas := make([]BucketMetadata, len(buckets))
|
||||
for index := range buckets {
|
||||
@@ -494,8 +490,8 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
|
||||
// herd upon start up sequence.
|
||||
time.Sleep(25*time.Millisecond + time.Duration(rand.Int63n(int64(100*time.Millisecond))))
|
||||
|
||||
_, _ = sys.objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{Recreate: true})
|
||||
meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[index].Name)
|
||||
_, _ = sys.objAPI.HealBucket(ctx, buckets[index], madmin.HealOpts{Recreate: true})
|
||||
meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[index])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -508,7 +504,7 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
|
||||
for index, err := range errs {
|
||||
if err != nil {
|
||||
internalLogOnceIf(ctx, fmt.Errorf("Unable to load bucket metadata, will be retried: %w", err),
|
||||
"load-bucket-metadata-"+buckets[index].Name, logger.WarningKind)
|
||||
"load-bucket-metadata-"+buckets[index], logger.WarningKind)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,7 +515,7 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
|
||||
if errs[i] != nil {
|
||||
continue
|
||||
}
|
||||
sys.metadataMap[buckets[i].Name] = meta
|
||||
sys.metadataMap[buckets[i]] = meta
|
||||
}
|
||||
sys.Unlock()
|
||||
|
||||
@@ -528,7 +524,7 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
|
||||
if failedBuckets == nil {
|
||||
failedBuckets = make(map[string]struct{})
|
||||
}
|
||||
failedBuckets[buckets[i].Name] = struct{}{}
|
||||
failedBuckets[buckets[i]] = struct{}{}
|
||||
continue
|
||||
}
|
||||
globalEventNotifier.set(buckets[i], meta) // set notification targets
|
||||
@@ -548,7 +544,7 @@ func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context, fa
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
buckets, err := sys.objAPI.ListBuckets(ctx, BucketOptions{})
|
||||
buckets, err := sys.objAPI.ListBuckets(ctx, BucketOptions{NoMetadata: true})
|
||||
if err != nil {
|
||||
internalLogIf(ctx, err, logger.WarningKind)
|
||||
break
|
||||
@@ -579,8 +575,8 @@ func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context, fa
|
||||
|
||||
// Initialize the failed buckets
|
||||
if _, ok := failedBuckets[buckets[i].Name]; ok {
|
||||
globalEventNotifier.set(buckets[i], meta)
|
||||
globalBucketTargetSys.set(buckets[i], meta)
|
||||
globalEventNotifier.set(buckets[i].Name, meta)
|
||||
globalBucketTargetSys.set(buckets[i].Name, meta)
|
||||
delete(failedBuckets, buckets[i].Name)
|
||||
}
|
||||
|
||||
@@ -600,8 +596,8 @@ func (sys *BucketMetadataSys) Initialized() bool {
|
||||
}
|
||||
|
||||
// Loads bucket metadata for all buckets into BucketMetadataSys.
|
||||
func (sys *BucketMetadataSys) init(ctx context.Context, buckets []BucketInfo) {
|
||||
count := 100 // load 100 bucket metadata at a time.
|
||||
func (sys *BucketMetadataSys) init(ctx context.Context, buckets []string) {
|
||||
count := globalEndpoints.ESCount() * 10
|
||||
failedBuckets := make(map[string]struct{})
|
||||
for {
|
||||
if len(buckets) < count {
|
||||
|
||||
@@ -3086,7 +3086,7 @@ func (p *ReplicationPool) deleteResyncMetadata(ctx context.Context, bucket strin
|
||||
}
|
||||
|
||||
// initResync - initializes bucket replication resync for all buckets.
|
||||
func (p *ReplicationPool) initResync(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) error {
|
||||
func (p *ReplicationPool) initResync(ctx context.Context, buckets []string, objAPI ObjectLayer) error {
|
||||
if objAPI == nil {
|
||||
return errServerNotInitialized
|
||||
}
|
||||
@@ -3095,7 +3095,7 @@ func (p *ReplicationPool) initResync(ctx context.Context, buckets []BucketInfo,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ReplicationPool) startResyncRoutine(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) {
|
||||
func (p *ReplicationPool) startResyncRoutine(ctx context.Context, buckets []string, objAPI ObjectLayer) {
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
// Run the replication resync in a loop
|
||||
for {
|
||||
@@ -3113,13 +3113,13 @@ func (p *ReplicationPool) startResyncRoutine(ctx context.Context, buckets []Buck
|
||||
}
|
||||
|
||||
// Loads bucket replication resync statuses into memory.
|
||||
func (p *ReplicationPool) loadResync(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) error {
|
||||
func (p *ReplicationPool) loadResync(ctx context.Context, buckets []string, objAPI ObjectLayer) error {
|
||||
// Make sure only one node running resync on the cluster.
|
||||
ctx, cancel := globalLeaderLock.GetLock(ctx)
|
||||
defer cancel()
|
||||
|
||||
for index := range buckets {
|
||||
bucket := buckets[index].Name
|
||||
bucket := buckets[index]
|
||||
|
||||
meta, err := loadBucketResyncMetadata(ctx, bucket, objAPI)
|
||||
if err != nil {
|
||||
|
||||
@@ -612,7 +612,7 @@ func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketT
|
||||
}
|
||||
|
||||
// create minio-go clients for buckets having remote targets
|
||||
func (sys *BucketTargetSys) set(bucket BucketInfo, meta BucketMetadata) {
|
||||
func (sys *BucketTargetSys) set(bucket string, meta BucketMetadata) {
|
||||
cfg := meta.bucketTargetConfig
|
||||
if cfg == nil || cfg.Empty() {
|
||||
return
|
||||
@@ -626,9 +626,9 @@ func (sys *BucketTargetSys) set(bucket BucketInfo, meta BucketMetadata) {
|
||||
continue
|
||||
}
|
||||
sys.arnRemotesMap[tgt.Arn] = arnTarget{Client: tgtClient}
|
||||
sys.updateBandwidthLimit(bucket.Name, tgt.Arn, tgt.BandwidthLimit)
|
||||
sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
|
||||
}
|
||||
sys.targetsMap[bucket.Name] = cfg.Targets
|
||||
sys.targetsMap[bucket] = cfg.Targets
|
||||
}
|
||||
|
||||
// Returns a minio-go Client configured to access remote host described in replication target config.
|
||||
|
||||
@@ -433,7 +433,6 @@ func buildServerCtxt(ctx *cli.Context, ctxt *serverCtxt) (err error) {
|
||||
ctxt.RecvBufSize = ctx.Int("recv-buf-size")
|
||||
ctxt.IdleTimeout = ctx.Duration("idle-timeout")
|
||||
ctxt.UserTimeout = ctx.Duration("conn-user-timeout")
|
||||
ctxt.ShutdownTimeout = ctx.Duration("shutdown-timeout")
|
||||
|
||||
if conf := ctx.String("config"); len(conf) > 0 {
|
||||
err = mergeServerCtxtFromConfigFile(conf, ctxt)
|
||||
|
||||
+34
-9
@@ -31,6 +31,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
@@ -249,7 +250,8 @@ type folderScanner struct {
|
||||
healObjectSelect uint32 // Do a heal check on an object once every n cycles. Must divide into healFolderInclude
|
||||
scanMode madmin.HealScanMode
|
||||
|
||||
weSleep func() bool
|
||||
weSleep func() bool
|
||||
shouldHeal func() bool
|
||||
|
||||
disks []StorageAPI
|
||||
disksQuorum int
|
||||
@@ -304,11 +306,12 @@ type folderScanner struct {
|
||||
// The returned cache will always be valid, but may not be updated from the existing.
|
||||
// Before each operation sleepDuration is called which can be used to temporarily halt the scanner.
|
||||
// If the supplied context is canceled the function will return at the first chance.
|
||||
func scanDataFolder(ctx context.Context, disks []StorageAPI, basePath string, cache dataUsageCache, getSize getSizeFn, scanMode madmin.HealScanMode, weSleep func() bool) (dataUsageCache, error) {
|
||||
func scanDataFolder(ctx context.Context, disks []StorageAPI, drive *xlStorage, cache dataUsageCache, getSize getSizeFn, scanMode madmin.HealScanMode, weSleep func() bool) (dataUsageCache, error) {
|
||||
switch cache.Info.Name {
|
||||
case "", dataUsageRoot:
|
||||
return cache, errors.New("internal error: root scan attempted")
|
||||
}
|
||||
basePath := drive.drivePath
|
||||
updatePath, closeDisk := globalScannerMetrics.currentPathUpdater(basePath, cache.Info.Name)
|
||||
defer closeDisk()
|
||||
|
||||
@@ -328,6 +331,26 @@ func scanDataFolder(ctx context.Context, disks []StorageAPI, basePath string, ca
|
||||
disksQuorum: len(disks) / 2,
|
||||
}
|
||||
|
||||
var skipHeal atomic.Bool
|
||||
if globalIsErasure || cache.Info.SkipHealing {
|
||||
skipHeal.Store(true)
|
||||
}
|
||||
|
||||
// Check if we should do healing at all.
|
||||
s.shouldHeal = func() bool {
|
||||
if skipHeal.Load() {
|
||||
return false
|
||||
}
|
||||
if s.healObjectSelect == 0 {
|
||||
return false
|
||||
}
|
||||
if di, _ := drive.DiskInfo(ctx, DiskInfoOptions{}); di.Healing {
|
||||
skipHeal.Store(true)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Enable healing in XL mode.
|
||||
if globalIsErasure && !cache.Info.SkipHealing {
|
||||
// Do a heal check on an object once every n cycles. Must divide into healFolderInclude
|
||||
@@ -482,14 +505,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
||||
replication: replicationCfg,
|
||||
}
|
||||
|
||||
item.heal.enabled = thisHash.modAlt(f.oldCache.Info.NextCycle/folder.objectHealProbDiv, f.healObjectSelect/folder.objectHealProbDiv) && globalIsErasure
|
||||
item.heal.enabled = thisHash.modAlt(f.oldCache.Info.NextCycle/folder.objectHealProbDiv, f.healObjectSelect/folder.objectHealProbDiv) && f.shouldHeal()
|
||||
item.heal.bitrot = f.scanMode == madmin.HealDeepScan
|
||||
|
||||
// if the drive belongs to an erasure set
|
||||
// that is already being healed, skip the
|
||||
// healing attempt on this drive.
|
||||
item.heal.enabled = item.heal.enabled && f.healObjectSelect > 0
|
||||
|
||||
sz, err := f.getSize(item)
|
||||
if err != nil && err != errIgnoreFileContrib {
|
||||
wait() // wait to proceed to next entry.
|
||||
@@ -652,7 +670,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
||||
}
|
||||
|
||||
// Scan for healing
|
||||
if f.healObjectSelect == 0 || len(abandonedChildren) == 0 {
|
||||
if len(abandonedChildren) == 0 || !f.shouldHeal() {
|
||||
// If we are not heal scanning, return now.
|
||||
break
|
||||
}
|
||||
@@ -687,6 +705,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
||||
|
||||
healObjectsPrefix := color.Green("healObjects:")
|
||||
for k := range abandonedChildren {
|
||||
if !f.shouldHeal() {
|
||||
break
|
||||
}
|
||||
bucket, prefix := path2BucketObject(k)
|
||||
stopFn := globalScannerMetrics.time(scannerMetricCheckMissing)
|
||||
f.updateCurrentPath(k)
|
||||
@@ -720,6 +741,10 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
||||
},
|
||||
// Some disks have data for this.
|
||||
partial: func(entries metaCacheEntries, errs []error) {
|
||||
if !f.shouldHeal() {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
entry, ok := entries.resolve(&resolver)
|
||||
if !ok {
|
||||
// check if we can get one entry at least
|
||||
|
||||
+20
-6
@@ -26,6 +26,9 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/internal/cachevalue"
|
||||
)
|
||||
|
||||
type usageTestFile struct {
|
||||
@@ -61,10 +64,13 @@ func TestDataUsageUpdate(t *testing.T) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
xls := xlStorage{drivePath: base, diskInfoCache: cachevalue.New[DiskInfo]()}
|
||||
xls.diskInfoCache.InitOnce(time.Second, cachevalue.Opts{}, func(ctx context.Context) (DiskInfo, error) {
|
||||
return DiskInfo{Total: 1 << 40, Free: 1 << 40}, nil
|
||||
})
|
||||
weSleep := func() bool { return false }
|
||||
|
||||
got, err := scanDataFolder(context.Background(), nil, base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize, 0, weSleep)
|
||||
got, err := scanDataFolder(context.Background(), nil, &xls, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize, 0, weSleep)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -174,7 +180,7 @@ func TestDataUsageUpdate(t *testing.T) {
|
||||
}
|
||||
// Changed dir must be picked up in this many cycles.
|
||||
for i := 0; i < dataUsageUpdateDirCycles; i++ {
|
||||
got, err = scanDataFolder(context.Background(), nil, base, got, getSize, 0, weSleep)
|
||||
got, err = scanDataFolder(context.Background(), nil, &xls, got, getSize, 0, weSleep)
|
||||
got.Info.NextCycle++
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -283,8 +289,12 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
|
||||
}
|
||||
|
||||
weSleep := func() bool { return false }
|
||||
xls := xlStorage{drivePath: base, diskInfoCache: cachevalue.New[DiskInfo]()}
|
||||
xls.diskInfoCache.InitOnce(time.Second, cachevalue.Opts{}, func(ctx context.Context) (DiskInfo, error) {
|
||||
return DiskInfo{Total: 1 << 40, Free: 1 << 40}, nil
|
||||
})
|
||||
|
||||
got, err := scanDataFolder(context.Background(), nil, base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize, 0, weSleep)
|
||||
got, err := scanDataFolder(context.Background(), nil, &xls, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize, 0, weSleep)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -419,7 +429,7 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
|
||||
}
|
||||
// Changed dir must be picked up in this many cycles.
|
||||
for i := 0; i < dataUsageUpdateDirCycles; i++ {
|
||||
got, err = scanDataFolder(context.Background(), nil, base, got, getSize, 0, weSleep)
|
||||
got, err = scanDataFolder(context.Background(), nil, &xls, got, getSize, 0, weSleep)
|
||||
got.Info.NextCycle++
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -567,8 +577,12 @@ func TestDataUsageCacheSerialize(t *testing.T) {
|
||||
}
|
||||
return
|
||||
}
|
||||
xls := xlStorage{drivePath: base, diskInfoCache: cachevalue.New[DiskInfo]()}
|
||||
xls.diskInfoCache.InitOnce(time.Second, cachevalue.Opts{}, func(ctx context.Context) (DiskInfo, error) {
|
||||
return DiskInfo{Total: 1 << 40, Free: 1 << 40}, nil
|
||||
})
|
||||
weSleep := func() bool { return false }
|
||||
want, err := scanDataFolder(context.Background(), nil, base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize, 0, weSleep)
|
||||
want, err := scanDataFolder(context.Background(), nil, &xls, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize, 0, weSleep)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -253,6 +253,14 @@ type PoolEndpoints struct {
|
||||
// EndpointServerPools - list of list of endpoints
|
||||
type EndpointServerPools []PoolEndpoints
|
||||
|
||||
// ESCount returns the total number of erasure sets in this cluster
|
||||
func (l EndpointServerPools) ESCount() (count int) {
|
||||
for _, p := range l {
|
||||
count += p.SetCount
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetNodes returns a sorted list of nodes in this cluster
|
||||
func (l EndpointServerPools) GetNodes() (nodes []Node) {
|
||||
nodesMap := make(map[string]Node)
|
||||
|
||||
+8
-31
@@ -576,35 +576,11 @@ func (er erasureObjects) deleteIfDangling(ctx context.Context, bucket, object st
|
||||
}
|
||||
|
||||
func fileInfoFromRaw(ri RawFileInfo, bucket, object string, readData, inclFreeVers, allParts bool) (FileInfo, error) {
|
||||
var xl xlMetaV2
|
||||
if err := xl.LoadOrConvert(ri.Buf); err != nil {
|
||||
return FileInfo{}, errFileCorrupt
|
||||
}
|
||||
|
||||
fi, err := xl.ToFileInfo(bucket, object, "", inclFreeVers, allParts)
|
||||
if err != nil {
|
||||
return FileInfo{}, err
|
||||
}
|
||||
|
||||
if !fi.IsValid() {
|
||||
return FileInfo{}, errFileCorrupt
|
||||
}
|
||||
|
||||
versionID := fi.VersionID
|
||||
if versionID == "" {
|
||||
versionID = nullVersionID
|
||||
}
|
||||
|
||||
fileInfo, err := xl.ToFileInfo(bucket, object, versionID, inclFreeVers, allParts)
|
||||
if err != nil {
|
||||
return FileInfo{}, err
|
||||
}
|
||||
|
||||
if readData {
|
||||
fileInfo.Data = xl.data.find(versionID)
|
||||
}
|
||||
|
||||
return fileInfo, nil
|
||||
return getFileInfo(ri.Buf, bucket, object, "", fileInfoOpts{
|
||||
Data: readData,
|
||||
InclFreeVersions: inclFreeVers,
|
||||
AllParts: allParts,
|
||||
})
|
||||
}
|
||||
|
||||
func readAllRawFileInfo(ctx context.Context, disks []StorageAPI, bucket, object string, readData bool) ([]RawFileInfo, []error) {
|
||||
@@ -756,8 +732,9 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
||||
disks := er.getDisks()
|
||||
|
||||
ropts := ReadOptions{
|
||||
ReadData: readData,
|
||||
Healing: false,
|
||||
ReadData: readData,
|
||||
InclFreeVersions: opts.InclFreeVersions,
|
||||
Healing: false,
|
||||
}
|
||||
|
||||
mrfCheck := make(chan FileInfo)
|
||||
|
||||
+50
-16
@@ -2008,10 +2008,12 @@ func (z *erasureServerPools) ListBuckets(ctx context.Context, opts BucketOptions
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range buckets {
|
||||
createdAt, err := globalBucketMetadataSys.CreatedAt(buckets[i].Name)
|
||||
if err == nil {
|
||||
buckets[i].Created = createdAt
|
||||
if !opts.NoMetadata {
|
||||
for i := range buckets {
|
||||
createdAt, err := globalBucketMetadataSys.CreatedAt(buckets[i].Name)
|
||||
if err == nil {
|
||||
buckets[i].Created = createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
return buckets, nil
|
||||
@@ -2025,10 +2027,13 @@ func (z *erasureServerPools) ListBuckets(ctx context.Context, opts BucketOptions
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range buckets {
|
||||
createdAt, err := globalBucketMetadataSys.CreatedAt(buckets[i].Name)
|
||||
if err == nil {
|
||||
buckets[i].Created = createdAt
|
||||
|
||||
if !opts.NoMetadata {
|
||||
for i := range buckets {
|
||||
createdAt, err := globalBucketMetadataSys.CreatedAt(buckets[i].Name)
|
||||
if err == nil {
|
||||
buckets[i].Created = createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
return buckets, nil
|
||||
@@ -2082,7 +2087,6 @@ func (z *erasureServerPools) HealBucket(ctx context.Context, bucket string, opts
|
||||
|
||||
// Walk a bucket, optionally prefix recursively, until we have returned
|
||||
// all the contents of the provided bucket+prefix.
|
||||
// TODO: Note that most errors will result in a truncated listing.
|
||||
func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, results chan<- itemOrErr[ObjectInfo], opts WalkOptions) error {
|
||||
if err := checkListObjsArgs(ctx, bucket, prefix, ""); err != nil {
|
||||
xioutil.SafeClose(results)
|
||||
@@ -2199,9 +2203,8 @@ func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, re
|
||||
// Convert and filter merged entries.
|
||||
merged := make(chan metaCacheEntry, 100)
|
||||
vcfg, _ := globalBucketVersioningSys.Get(bucket)
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
defer cancelCause(nil)
|
||||
defer xioutil.SafeClose(results)
|
||||
sentErr := false
|
||||
sendErr := func(err error) {
|
||||
if !sentErr {
|
||||
@@ -2212,6 +2215,15 @@ func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, re
|
||||
}
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
sendErr(ctx.Err())
|
||||
default:
|
||||
}
|
||||
xioutil.SafeClose(results)
|
||||
cancelCause(nil)
|
||||
}()
|
||||
send := func(oi ObjectInfo) bool {
|
||||
select {
|
||||
case results <- itemOrErr[ObjectInfo]{Item: oi}:
|
||||
@@ -2266,12 +2278,16 @@ func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, re
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := <-errCh; err != nil {
|
||||
sendErr(err)
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer close(errCh)
|
||||
// Merge all entries from all disks.
|
||||
// We leave quorum at 1, since entries are already resolved to have the desired quorum.
|
||||
// mergeEntryChannels will close 'merged' channel upon completion or cancellation.
|
||||
storageLogIf(ctx, mergeEntryChannels(ctx, entries, merged, 1))
|
||||
errCh <- mergeEntryChannels(ctx, entries, merged, 1)
|
||||
}()
|
||||
|
||||
return nil
|
||||
@@ -2449,6 +2465,24 @@ type HealthResult struct {
|
||||
UsingDefaults bool
|
||||
}
|
||||
|
||||
func (hr HealthResult) String() string {
|
||||
var str strings.Builder
|
||||
for i, es := range hr.ESHealth {
|
||||
str.WriteString("(Pool: ")
|
||||
str.WriteString(strconv.Itoa(es.PoolID))
|
||||
str.WriteString(" Set: ")
|
||||
str.WriteString(strconv.Itoa(es.SetID))
|
||||
str.WriteString(" Healthy: ")
|
||||
str.WriteString(strconv.FormatBool(es.Healthy))
|
||||
if i == 0 {
|
||||
str.WriteString(")")
|
||||
} else {
|
||||
str.WriteString("), ")
|
||||
}
|
||||
}
|
||||
return str.String()
|
||||
}
|
||||
|
||||
// Health - returns current status of the object layer health,
|
||||
// provides if write access exists across sets, additionally
|
||||
// can be used to query scenarios if health may be lost
|
||||
@@ -2568,16 +2602,16 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
|
||||
healthy := erasureSetUpCount[poolIdx][setIdx].online >= poolWriteQuorums[poolIdx]
|
||||
if !healthy {
|
||||
storageLogIf(logger.SetReqInfo(ctx, reqInfo),
|
||||
fmt.Errorf("Write quorum may be lost on pool: %d, set: %d, expected write quorum: %d",
|
||||
poolIdx, setIdx, poolWriteQuorums[poolIdx]), logger.FatalKind)
|
||||
fmt.Errorf("Write quorum could not be established on pool: %d, set: %d, expected write quorum: %d, drives-online: %d",
|
||||
poolIdx, setIdx, poolWriteQuorums[poolIdx], erasureSetUpCount[poolIdx][setIdx].online), logger.FatalKind)
|
||||
}
|
||||
result.Healthy = result.Healthy && healthy
|
||||
|
||||
healthyRead := erasureSetUpCount[poolIdx][setIdx].online >= poolReadQuorums[poolIdx]
|
||||
if !healthyRead {
|
||||
storageLogIf(logger.SetReqInfo(ctx, reqInfo),
|
||||
fmt.Errorf("Read quorum may be lost on pool: %d, set: %d, expected read quorum: %d",
|
||||
poolIdx, setIdx, poolReadQuorums[poolIdx]))
|
||||
fmt.Errorf("Read quorum could not be established on pool: %d, set: %d, expected read quorum: %d, drives-online: %d",
|
||||
poolIdx, setIdx, poolReadQuorums[poolIdx], erasureSetUpCount[poolIdx][setIdx].online))
|
||||
}
|
||||
result.HealthyRead = result.HealthyRead && healthyRead
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import (
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/dsync"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v3/console"
|
||||
"github.com/minio/pkg/v3/sync/errgroup"
|
||||
@@ -80,10 +79,6 @@ type erasureSets struct {
|
||||
|
||||
poolIndex int
|
||||
|
||||
// A channel to send the set index to the MRF when
|
||||
// any disk belonging to that set is connected
|
||||
setReconnectEvent chan int
|
||||
|
||||
// Distribution algorithm of choice.
|
||||
distributionAlgo string
|
||||
deploymentID [16]byte
|
||||
@@ -380,7 +375,6 @@ func newErasureSets(ctx context.Context, endpoints PoolEndpoints, storageDisks [
|
||||
setDriveCount: setDriveCount,
|
||||
defaultParityCount: defaultParityCount,
|
||||
format: format,
|
||||
setReconnectEvent: make(chan int),
|
||||
distributionAlgo: format.Erasure.DistributionAlgo,
|
||||
deploymentID: uuid.MustParse(format.ID),
|
||||
poolIndex: poolIdx,
|
||||
@@ -662,14 +656,6 @@ func (s *erasureSets) Shutdown(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
select {
|
||||
case _, ok := <-s.setReconnectEvent:
|
||||
if ok {
|
||||
xioutil.SafeClose(s.setReconnectEvent)
|
||||
}
|
||||
default:
|
||||
xioutil.SafeClose(s.setReconnectEvent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -49,23 +49,18 @@ func NewEventNotifier(ctx context.Context) *EventNotifier {
|
||||
}
|
||||
|
||||
// GetARNList - returns available ARNs.
|
||||
func (evnot *EventNotifier) GetARNList(onlyActive bool) []string {
|
||||
func (evnot *EventNotifier) GetARNList() []string {
|
||||
arns := []string{}
|
||||
if evnot == nil {
|
||||
return arns
|
||||
}
|
||||
region := globalSite.Region()
|
||||
for targetID, target := range evnot.targetList.TargetMap() {
|
||||
for targetID := range evnot.targetList.TargetMap() {
|
||||
// httpclient target is part of ListenNotification
|
||||
// which doesn't need to be listed as part of the ARN list
|
||||
// This list is only meant for external targets, filter
|
||||
// this out pro-actively.
|
||||
if !strings.HasPrefix(targetID.ID, "httpclient+") {
|
||||
if onlyActive {
|
||||
if _, err := target.IsActive(); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
arns = append(arns, targetID.ToARN(region).String())
|
||||
}
|
||||
}
|
||||
@@ -74,7 +69,7 @@ func (evnot *EventNotifier) GetARNList(onlyActive bool) []string {
|
||||
}
|
||||
|
||||
// Loads notification policies for all buckets into EventNotifier.
|
||||
func (evnot *EventNotifier) set(bucket BucketInfo, meta BucketMetadata) {
|
||||
func (evnot *EventNotifier) set(bucket string, meta BucketMetadata) {
|
||||
config := meta.notificationConfig
|
||||
if config == nil {
|
||||
return
|
||||
@@ -86,7 +81,7 @@ func (evnot *EventNotifier) set(bucket BucketInfo, meta BucketMetadata) {
|
||||
internalLogIf(GlobalContext, err)
|
||||
}
|
||||
}
|
||||
evnot.AddRulesMap(bucket.Name, config.ToRulesMap())
|
||||
evnot.AddRulesMap(bucket, config.ToRulesMap())
|
||||
}
|
||||
|
||||
// Targets returns all the registered targets
|
||||
|
||||
+6
-2
@@ -163,7 +163,10 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
}
|
||||
|
||||
for _, bucket := range healBuckets {
|
||||
_, err := objAPI.HealBucket(ctx, bucket, madmin.HealOpts{ScanMode: scanMode})
|
||||
_, err := objAPI.HealBucket(ctx, bucket, madmin.HealOpts{
|
||||
Recreate: true,
|
||||
ScanMode: scanMode,
|
||||
})
|
||||
if err != nil {
|
||||
// Log bucket healing error if any, we shall retry again.
|
||||
healingLogIf(ctx, err)
|
||||
@@ -262,6 +265,7 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
// Heal current bucket again in case if it is failed
|
||||
// in the beginning of erasure set healing
|
||||
if _, err := objAPI.HealBucket(ctx, bucket, madmin.HealOpts{
|
||||
Recreate: true,
|
||||
ScanMode: scanMode,
|
||||
}); err != nil {
|
||||
// Set this such that when we return this function
|
||||
@@ -513,7 +517,7 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
// we let the caller retry this disk again for the
|
||||
// buckets it failed to list.
|
||||
retErr = err
|
||||
healingLogIf(ctx, err)
|
||||
healingLogIf(ctx, fmt.Errorf("listing failed with: %v on bucket: %v", err, bucket))
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +163,6 @@ type serverCtxt struct {
|
||||
MemLimit uint64
|
||||
|
||||
UserTimeout time.Duration
|
||||
ShutdownTimeout time.Duration
|
||||
IdleTimeout time.Duration
|
||||
ReadHeaderTimeout time.Duration
|
||||
MaxIdleConnsPerHost int
|
||||
|
||||
+3
-1
@@ -189,12 +189,14 @@ func printMinIOVersion(c *cli.Context) {
|
||||
io.Copy(c.App.Writer, versionBanner(c))
|
||||
}
|
||||
|
||||
var debugNoExit = env.Get("_MINIO_DEBUG_NO_EXIT", "") != ""
|
||||
|
||||
// Main main for minio server.
|
||||
func Main(args []string) {
|
||||
// Set the minio app name.
|
||||
appName := filepath.Base(args[0])
|
||||
|
||||
if env.Get("_MINIO_DEBUG_NO_EXIT", "") != "" {
|
||||
if debugNoExit {
|
||||
freeze := func(_ int) {
|
||||
// Infinite blocking op
|
||||
<-make(chan struct{})
|
||||
|
||||
@@ -259,7 +259,7 @@ func (e *metaCacheEntry) fileInfo(bucket string) (FileInfo, error) {
|
||||
}
|
||||
return e.cached.ToFileInfo(bucket, e.name, "", false, false)
|
||||
}
|
||||
return getFileInfo(e.metadata, bucket, e.name, "", false, false)
|
||||
return getFileInfo(e.metadata, bucket, e.name, "", fileInfoOpts{})
|
||||
}
|
||||
|
||||
// xlmeta returns the decoded metadata.
|
||||
|
||||
+1
-1
@@ -1688,7 +1688,7 @@ func getMinioProcMetrics() *MetricsGroupV2 {
|
||||
cacheInterval: 10 * time.Second,
|
||||
}
|
||||
mg.RegisterRead(func(ctx context.Context) (metrics []MetricV2) {
|
||||
if runtime.GOOS == "windows" {
|
||||
if runtime.GOOS == globalWindowsOSName || runtime.GOOS == globalMacOSName {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -55,15 +55,17 @@ func loadCPUMetrics(ctx context.Context, m MetricValues, c *metricsCache) error
|
||||
}
|
||||
|
||||
ts := cpuMetrics.TimesStat
|
||||
tot := ts.User + ts.System + ts.Idle + ts.Iowait + ts.Nice + ts.Steal
|
||||
cpuUserVal := math.Round(ts.User/tot*100*100) / 100
|
||||
m.Set(sysCPUUser, cpuUserVal)
|
||||
cpuSystemVal := math.Round(ts.System/tot*100*100) / 100
|
||||
m.Set(sysCPUSystem, cpuSystemVal)
|
||||
cpuNiceVal := math.Round(ts.Nice/tot*100*100) / 100
|
||||
m.Set(sysCPUNice, cpuNiceVal)
|
||||
cpuStealVal := math.Round(ts.Steal/tot*100*100) / 100
|
||||
m.Set(sysCPUSteal, cpuStealVal)
|
||||
if ts != nil {
|
||||
tot := ts.User + ts.System + ts.Idle + ts.Iowait + ts.Nice + ts.Steal
|
||||
cpuUserVal := math.Round(ts.User/tot*100*100) / 100
|
||||
m.Set(sysCPUUser, cpuUserVal)
|
||||
cpuSystemVal := math.Round(ts.System/tot*100*100) / 100
|
||||
m.Set(sysCPUSystem, cpuSystemVal)
|
||||
cpuNiceVal := math.Round(ts.Nice/tot*100*100) / 100
|
||||
m.Set(sysCPUNice, cpuNiceVal)
|
||||
cpuStealVal := math.Round(ts.Steal/tot*100*100) / 100
|
||||
m.Set(sysCPUSteal, cpuStealVal)
|
||||
}
|
||||
|
||||
// metrics-resource.go runs a job to collect resource metrics including their Avg values and
|
||||
// stores them in resourceMetricsMap. We can use it to get the Avg values of CPU idle and IOWait.
|
||||
|
||||
@@ -156,11 +156,13 @@ func loadProcessMetrics(ctx context.Context, m MetricValues, c *metricsCache) er
|
||||
m.Set(processUptimeSeconds, time.Since(globalBootTime).Seconds())
|
||||
}
|
||||
|
||||
p, err := procfs.Self()
|
||||
if err != nil {
|
||||
metricsLogIf(ctx, err)
|
||||
} else {
|
||||
loadProcFSMetrics(ctx, p, m)
|
||||
if runtime.GOOS != globalWindowsOSName && runtime.GOOS != globalMacOSName {
|
||||
p, err := procfs.Self()
|
||||
if err != nil {
|
||||
metricsLogIf(ctx, err)
|
||||
} else {
|
||||
loadProcFSMetrics(ctx, p, m)
|
||||
}
|
||||
}
|
||||
|
||||
if globalIsDistErasure && globalLockServer != nil {
|
||||
|
||||
+28
-11
@@ -19,7 +19,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"runtime"
|
||||
@@ -32,8 +31,16 @@ import (
|
||||
xnet "github.com/minio/pkg/v3/net"
|
||||
)
|
||||
|
||||
// IPv4 addresses of local host.
|
||||
var localIP4 = mustGetLocalIP4()
|
||||
var (
|
||||
// IPv4 addresses of localhost.
|
||||
localIP4 = mustGetLocalIP4()
|
||||
|
||||
// IPv6 addresses of localhost.
|
||||
localIP6 = mustGetLocalIP6()
|
||||
|
||||
// List of all local loopback addresses.
|
||||
localLoopbacks = mustGetLocalLoopbacks()
|
||||
)
|
||||
|
||||
// mustSplitHostPort is a wrapper to net.SplitHostPort() where error is assumed to be a fatal.
|
||||
func mustSplitHostPort(hostPort string) (host, port string) {
|
||||
@@ -74,6 +81,16 @@ func mustGetLocalIPs() (ipList []net.IP) {
|
||||
return ipList
|
||||
}
|
||||
|
||||
func mustGetLocalLoopbacks() (ipList set.StringSet) {
|
||||
ipList = set.NewStringSet()
|
||||
for _, ip := range mustGetLocalIPs() {
|
||||
if ip != nil && ip.IsLoopback() {
|
||||
ipList.Add(ip.String())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// mustGetLocalIP4 returns IPv4 addresses of localhost. It panics on error.
|
||||
func mustGetLocalIP4() (ipList set.StringSet) {
|
||||
ipList = set.NewStringSet()
|
||||
@@ -160,15 +177,15 @@ func getConsoleEndpoints() (consoleEndpoints []string) {
|
||||
}
|
||||
var ipList []string
|
||||
if globalMinioConsoleHost == "" {
|
||||
ipList = sortIPs(mustGetLocalIP4().ToSlice())
|
||||
ipList = append(ipList, mustGetLocalIP6().ToSlice()...)
|
||||
ipList = sortIPs(localIP4.ToSlice())
|
||||
ipList = append(ipList, localIP6.ToSlice()...)
|
||||
} else {
|
||||
ipList = []string{globalMinioConsoleHost}
|
||||
}
|
||||
|
||||
consoleEndpoints = make([]string, 0, len(ipList))
|
||||
for _, ip := range ipList {
|
||||
endpoint := fmt.Sprintf("%s://%s", getURLScheme(globalIsTLS), net.JoinHostPort(ip, globalMinioConsolePort))
|
||||
consoleEndpoints = append(consoleEndpoints, endpoint)
|
||||
consoleEndpoints = append(consoleEndpoints, getURLScheme(globalIsTLS)+"://"+net.JoinHostPort(ip, globalMinioConsolePort))
|
||||
}
|
||||
|
||||
return consoleEndpoints
|
||||
@@ -180,15 +197,15 @@ func getAPIEndpoints() (apiEndpoints []string) {
|
||||
}
|
||||
var ipList []string
|
||||
if globalMinioHost == "" {
|
||||
ipList = sortIPs(mustGetLocalIP4().ToSlice())
|
||||
ipList = append(ipList, mustGetLocalIP6().ToSlice()...)
|
||||
ipList = sortIPs(localIP4.ToSlice())
|
||||
ipList = append(ipList, localIP6.ToSlice()...)
|
||||
} else {
|
||||
ipList = []string{globalMinioHost}
|
||||
}
|
||||
|
||||
apiEndpoints = make([]string, 0, len(ipList))
|
||||
for _, ip := range ipList {
|
||||
endpoint := fmt.Sprintf("%s://%s", getURLScheme(globalIsTLS), net.JoinHostPort(ip, globalMinioPort))
|
||||
apiEndpoints = append(apiEndpoints, endpoint)
|
||||
apiEndpoints = append(apiEndpoints, getURLScheme(globalIsTLS)+"://"+net.JoinHostPort(ip, globalMinioPort))
|
||||
}
|
||||
|
||||
return apiEndpoints
|
||||
|
||||
+5
-5
@@ -424,7 +424,7 @@ func (sys *NotificationSys) SignalConfigReload(subSys string) []NotificationPeer
|
||||
}
|
||||
client := client
|
||||
ng.Go(GlobalContext, func() error {
|
||||
return client.SignalService(serviceReloadDynamic, subSys, false)
|
||||
return client.SignalService(serviceReloadDynamic, subSys, false, nil)
|
||||
}, idx, *client.host)
|
||||
}
|
||||
return ng.Wait()
|
||||
@@ -440,14 +440,14 @@ func (sys *NotificationSys) SignalService(sig serviceSignal) []NotificationPeerE
|
||||
client := client
|
||||
ng.Go(GlobalContext, func() error {
|
||||
// force == true preserves the current behavior
|
||||
return client.SignalService(sig, "", false)
|
||||
return client.SignalService(sig, "", false, nil)
|
||||
}, idx, *client.host)
|
||||
}
|
||||
return ng.Wait()
|
||||
}
|
||||
|
||||
// SignalServiceV2 - calls signal service RPC call on all peers with v2 API
|
||||
func (sys *NotificationSys) SignalServiceV2(sig serviceSignal, dryRun bool) []NotificationPeerErr {
|
||||
func (sys *NotificationSys) SignalServiceV2(sig serviceSignal, dryRun bool, execAt *time.Time) []NotificationPeerErr {
|
||||
ng := WithNPeers(len(sys.peerClients))
|
||||
for idx, client := range sys.peerClients {
|
||||
if client == nil {
|
||||
@@ -455,7 +455,7 @@ func (sys *NotificationSys) SignalServiceV2(sig serviceSignal, dryRun bool) []No
|
||||
}
|
||||
client := client
|
||||
ng.Go(GlobalContext, func() error {
|
||||
return client.SignalService(sig, "", dryRun)
|
||||
return client.SignalService(sig, "", dryRun, execAt)
|
||||
}, idx, *client.host)
|
||||
}
|
||||
return ng.Wait()
|
||||
@@ -1314,7 +1314,7 @@ func (sys *NotificationSys) ServiceFreeze(ctx context.Context, freeze bool) []No
|
||||
}
|
||||
client := client
|
||||
ng.Go(GlobalContext, func() error {
|
||||
return client.SignalService(serviceSig, "", false)
|
||||
return client.SignalService(serviceSig, "", false, nil)
|
||||
}, idx, *client.host)
|
||||
}
|
||||
nerrs := ng.Wait()
|
||||
|
||||
@@ -179,8 +179,9 @@ type DeleteBucketOptions struct {
|
||||
|
||||
// BucketOptions provides options for ListBuckets and GetBucketInfo call.
|
||||
type BucketOptions struct {
|
||||
Deleted bool // true only when site replication is enabled
|
||||
Cached bool // true only when we are requesting a cached response instead of hitting the disk for example ListBuckets() call.
|
||||
Deleted bool // true only when site replication is enabled
|
||||
Cached bool // true only when we are requesting a cached response instead of hitting the disk for example ListBuckets() call.
|
||||
NoMetadata bool
|
||||
}
|
||||
|
||||
// SetReplicaStatus sets replica status and timestamp for delete operations in ObjectOptions
|
||||
|
||||
@@ -9,13 +9,16 @@ import (
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z BucketOptions) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 2
|
||||
// map header, size 3
|
||||
// string "Deleted"
|
||||
o = append(o, 0x82, 0xa7, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64)
|
||||
o = append(o, 0x83, 0xa7, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64)
|
||||
o = msgp.AppendBool(o, z.Deleted)
|
||||
// string "Cached"
|
||||
o = append(o, 0xa6, 0x43, 0x61, 0x63, 0x68, 0x65, 0x64)
|
||||
o = msgp.AppendBool(o, z.Cached)
|
||||
// string "NoMetadata"
|
||||
o = append(o, 0xaa, 0x4e, 0x6f, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61)
|
||||
o = msgp.AppendBool(o, z.NoMetadata)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -49,6 +52,12 @@ func (z *BucketOptions) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
err = msgp.WrapError(err, "Cached")
|
||||
return
|
||||
}
|
||||
case "NoMetadata":
|
||||
z.NoMetadata, bts, err = msgp.ReadBoolBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "NoMetadata")
|
||||
return
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
@@ -63,7 +72,7 @@ func (z *BucketOptions) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z BucketOptions) Msgsize() (s int) {
|
||||
s = 1 + 8 + msgp.BoolSize + 7 + msgp.BoolSize
|
||||
s = 1 + 8 + msgp.BoolSize + 7 + msgp.BoolSize + 11 + msgp.BoolSize
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+34
-71
@@ -610,8 +610,6 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5))
|
||||
}
|
||||
// Never store encrypted objects in cache.
|
||||
update = false
|
||||
objInfo.ETag = getDecryptedETag(r.Header, objInfo, false)
|
||||
}
|
||||
|
||||
@@ -620,39 +618,6 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
||||
hash.AddChecksumHeader(w, objInfo.decryptChecksums(opts.PartNumber, r.Header))
|
||||
}
|
||||
|
||||
var buf *bytebufferpool.ByteBuffer
|
||||
if update {
|
||||
if globalCacheConfig.MatchesSize(objInfo.Size) {
|
||||
buf = bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
}
|
||||
defer func() {
|
||||
var data []byte
|
||||
if buf != nil {
|
||||
data = buf.Bytes()
|
||||
}
|
||||
|
||||
asize, err := objInfo.GetActualSize()
|
||||
if err != nil {
|
||||
asize = objInfo.Size
|
||||
}
|
||||
|
||||
globalCacheConfig.Set(&cache.ObjectInfo{
|
||||
Key: objInfo.Name,
|
||||
Bucket: objInfo.Bucket,
|
||||
ETag: objInfo.ETag,
|
||||
ModTime: objInfo.ModTime,
|
||||
Expires: objInfo.ExpiresStr(),
|
||||
CacheControl: objInfo.CacheControl,
|
||||
Metadata: cleanReservedKeys(objInfo.UserDefined),
|
||||
Range: rangeHeader,
|
||||
PartNumber: opts.PartNumber,
|
||||
Size: asize,
|
||||
Data: data,
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
if err = setObjectHeaders(ctx, w, objInfo, rs, opts); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
@@ -665,6 +630,12 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
||||
|
||||
setHeadGetRespHeaders(w, r.Form)
|
||||
|
||||
var buf *bytebufferpool.ByteBuffer
|
||||
if update && globalCacheConfig.MatchesSize(objInfo.Size) {
|
||||
buf = bytebufferpool.Get()
|
||||
defer bytebufferpool.Put(buf)
|
||||
}
|
||||
|
||||
var iw io.Writer
|
||||
iw = w
|
||||
if buf != nil {
|
||||
@@ -696,6 +667,31 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
var data []byte
|
||||
if buf != nil {
|
||||
data = buf.Bytes()
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
globalCacheConfig.Set(&cache.ObjectInfo{
|
||||
Key: objInfo.Name,
|
||||
Bucket: objInfo.Bucket,
|
||||
ETag: objInfo.ETag,
|
||||
ModTime: objInfo.ModTime,
|
||||
Expires: objInfo.ExpiresStr(),
|
||||
CacheControl: objInfo.CacheControl,
|
||||
Metadata: cleanReservedKeys(objInfo.UserDefined),
|
||||
Range: rangeHeader,
|
||||
PartNumber: opts.PartNumber,
|
||||
Size: int64(len(data)),
|
||||
Data: data,
|
||||
})
|
||||
}()
|
||||
|
||||
// Notify object accessed via a GET request.
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.ObjectAccessedGet,
|
||||
@@ -1939,22 +1935,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
Host: handlers.GetSourceIP(r),
|
||||
})
|
||||
|
||||
asize, err := objInfo.GetActualSize()
|
||||
if err != nil {
|
||||
asize = objInfo.Size
|
||||
}
|
||||
|
||||
defer globalCacheConfig.Set(&cache.ObjectInfo{
|
||||
Key: objInfo.Name,
|
||||
Bucket: objInfo.Bucket,
|
||||
ETag: objInfo.ETag,
|
||||
ModTime: objInfo.ModTime,
|
||||
Expires: objInfo.ExpiresStr(),
|
||||
CacheControl: objInfo.CacheControl,
|
||||
Size: asize,
|
||||
Metadata: cleanReservedKeys(objInfo.UserDefined),
|
||||
})
|
||||
|
||||
if !remoteCallRequired && !globalTierConfigMgr.Empty() {
|
||||
// Schedule object for immediate transition if eligible.
|
||||
objInfo.ETag = origETag
|
||||
@@ -2343,9 +2323,8 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
data = buf.Bytes()
|
||||
}
|
||||
|
||||
asize, err := objInfo.GetActualSize()
|
||||
if err != nil {
|
||||
asize = objInfo.Size
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
globalCacheConfig.Set(&cache.ObjectInfo{
|
||||
@@ -2355,7 +2334,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
ModTime: objInfo.ModTime,
|
||||
Expires: objInfo.ExpiresStr(),
|
||||
CacheControl: objInfo.CacheControl,
|
||||
Size: asize,
|
||||
Size: int64(len(data)),
|
||||
Metadata: cleanReservedKeys(objInfo.UserDefined),
|
||||
Data: data,
|
||||
})
|
||||
@@ -2712,22 +2691,6 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
|
||||
scheduleReplication(ctx, objInfo, objectAPI, dsc, replication.ObjectReplicationType)
|
||||
}
|
||||
|
||||
asize, err := objInfo.GetActualSize()
|
||||
if err != nil {
|
||||
asize = objInfo.Size
|
||||
}
|
||||
|
||||
defer globalCacheConfig.Set(&cache.ObjectInfo{
|
||||
Key: objInfo.Name,
|
||||
Bucket: objInfo.Bucket,
|
||||
ETag: objInfo.ETag,
|
||||
ModTime: objInfo.ModTime,
|
||||
Expires: objInfo.ExpiresStr(),
|
||||
CacheControl: objInfo.CacheControl,
|
||||
Size: asize,
|
||||
Metadata: cleanReservedKeys(objInfo.UserDefined),
|
||||
})
|
||||
|
||||
// Notify object created event.
|
||||
evt := eventArgs{
|
||||
EventName: event.ObjectCreatedPut,
|
||||
|
||||
@@ -36,7 +36,6 @@ import (
|
||||
sse "github.com/minio/minio/internal/bucket/encryption"
|
||||
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
||||
"github.com/minio/minio/internal/bucket/replication"
|
||||
"github.com/minio/minio/internal/config/cache"
|
||||
"github.com/minio/minio/internal/config/dns"
|
||||
"github.com/minio/minio/internal/config/storageclass"
|
||||
"github.com/minio/minio/internal/crypto"
|
||||
@@ -1054,22 +1053,6 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||
}
|
||||
sendEvent(evt)
|
||||
|
||||
asize, err := objInfo.GetActualSize()
|
||||
if err != nil {
|
||||
asize = objInfo.Size
|
||||
}
|
||||
|
||||
defer globalCacheConfig.Set(&cache.ObjectInfo{
|
||||
Key: objInfo.Name,
|
||||
Bucket: objInfo.Bucket,
|
||||
ETag: objInfo.ETag,
|
||||
ModTime: objInfo.ModTime,
|
||||
Expires: objInfo.ExpiresStr(),
|
||||
CacheControl: objInfo.CacheControl,
|
||||
Size: asize,
|
||||
Metadata: cleanReservedKeys(objInfo.UserDefined),
|
||||
})
|
||||
|
||||
if objInfo.NumVersions > int(scannerExcessObjectVersions.Load()) {
|
||||
evt.EventName = event.ObjectManyVersions
|
||||
sendEvent(evt)
|
||||
|
||||
@@ -427,11 +427,14 @@ func (client *peerRESTClient) CommitBinary(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// SignalService - sends signal to peer nodes.
|
||||
func (client *peerRESTClient) SignalService(sig serviceSignal, subSys string, dryRun bool) error {
|
||||
func (client *peerRESTClient) SignalService(sig serviceSignal, subSys string, dryRun bool, execAt *time.Time) error {
|
||||
values := grid.NewMSS()
|
||||
values.Set(peerRESTSignal, strconv.Itoa(int(sig)))
|
||||
values.Set(peerRESTDryRun, strconv.FormatBool(dryRun))
|
||||
values.Set(peerRESTSubSys, subSys)
|
||||
if execAt != nil {
|
||||
values.Set(peerRESTExecAt, execAt.Format(time.RFC3339Nano))
|
||||
}
|
||||
_, err := signalServiceRPC.Call(context.Background(), client.gridConn(), values)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
package cmd
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
peerRESTVersion = "v39" // add more flags to speedtest API
|
||||
peerRESTVersionPrefix = SlashSeparator + peerRESTVersion
|
||||
@@ -69,6 +71,7 @@ const (
|
||||
peerRESTURL = "url"
|
||||
peerRESTSha256Sum = "sha256sum"
|
||||
peerRESTReleaseInfo = "releaseinfo"
|
||||
peerRESTExecAt = "exec-at"
|
||||
|
||||
peerRESTListenBucket = "bucket"
|
||||
peerRESTListenPrefix = "prefix"
|
||||
@@ -76,3 +79,5 @@ const (
|
||||
peerRESTListenEvents = "events"
|
||||
peerRESTLogMask = "log-mask"
|
||||
)
|
||||
|
||||
const restartUpdateDelay = 250 * time.Millisecond
|
||||
|
||||
@@ -693,6 +693,18 @@ func (s *peerRESTServer) SignalServiceHandler(vars *grid.MSS) (np grid.NoPayload
|
||||
if err != nil {
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
// Wait until the specified time before executing the signal.
|
||||
if t := vars.Get(peerRESTExecAt); t != "" {
|
||||
execAt, err := time.Parse(time.RFC3339Nano, vars.Get(peerRESTExecAt))
|
||||
if err != nil {
|
||||
logger.LogIf(GlobalContext, "signalservice", err)
|
||||
execAt = time.Now().Add(restartUpdateDelay)
|
||||
}
|
||||
if d := time.Until(execAt); d > 0 {
|
||||
time.Sleep(d)
|
||||
}
|
||||
}
|
||||
signal := serviceSignal(si)
|
||||
switch signal {
|
||||
case serviceRestart, serviceStop:
|
||||
|
||||
@@ -138,8 +138,12 @@ func (sys *S3PeerSys) HealBucket(ctx context.Context, bucket string, opts madmin
|
||||
poolErrs = append(poolErrs, reduceWriteQuorumErrs(ctx, perPoolErrs, bucketOpIgnoredErrs, quorum))
|
||||
}
|
||||
|
||||
opts.Remove = isAllBucketsNotFound(poolErrs)
|
||||
opts.Recreate = !opts.Remove
|
||||
if !opts.Recreate {
|
||||
// when there is no force recreate look for pool
|
||||
// errors to recreate the bucket on all pools.
|
||||
opts.Remove = isAllBucketsNotFound(poolErrs)
|
||||
opts.Recreate = !opts.Remove
|
||||
}
|
||||
|
||||
g = errgroup.WithNErrs(len(sys.peerClients))
|
||||
healBucketResults := make([]madmin.HealResultItem, len(sys.peerClients))
|
||||
|
||||
@@ -123,7 +123,7 @@ func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (
|
||||
g.Wait()
|
||||
}
|
||||
|
||||
// Create the quorum lost volume only if its nor makred for delete
|
||||
// Create the lost volume only if its not marked for delete
|
||||
if !opts.Remove {
|
||||
// Initialize sync waitgroup.
|
||||
g = errgroup.WithNErrs(len(localDrives))
|
||||
|
||||
+27
-11
@@ -84,11 +84,12 @@ var ServerFlags = []cli.Flag{
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "shutdown-timeout",
|
||||
Value: xhttp.DefaultShutdownTimeout,
|
||||
Usage: "shutdown timeout to gracefully shutdown server",
|
||||
Value: time.Second * 30,
|
||||
Usage: "shutdown timeout to gracefully shutdown server (DEPRECATED)",
|
||||
EnvVar: "MINIO_SHUTDOWN_TIMEOUT",
|
||||
Hidden: true,
|
||||
},
|
||||
|
||||
cli.DurationFlag{
|
||||
Name: "idle-timeout",
|
||||
Value: xhttp.DefaultIdleTimeout,
|
||||
@@ -680,10 +681,8 @@ func getServerListenAddrs() []string {
|
||||
// Use a string set to avoid duplication
|
||||
addrs := set.NewStringSet()
|
||||
// Listen on local interface to receive requests from Console
|
||||
for _, ip := range mustGetLocalIPs() {
|
||||
if ip != nil && ip.IsLoopback() {
|
||||
addrs.Add(net.JoinHostPort(ip.String(), globalMinioPort))
|
||||
}
|
||||
for _, ip := range localLoopbacks.ToSlice() {
|
||||
addrs.Add(net.JoinHostPort(ip, globalMinioPort))
|
||||
}
|
||||
host, _ := mustSplitHostPort(globalMinioAddr)
|
||||
if host != "" {
|
||||
@@ -740,6 +739,8 @@ func initializeLogRotate(ctx *cli.Context) (io.WriteCloser, error) {
|
||||
|
||||
// serverMain handler called for 'minio server' command.
|
||||
func serverMain(ctx *cli.Context) {
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
var warnings []string
|
||||
|
||||
signal.Notify(globalOSSignalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
@@ -866,7 +867,6 @@ func serverMain(ctx *cli.Context) {
|
||||
httpServer := xhttp.NewServer(getServerListenAddrs()).
|
||||
UseHandler(setCriticalErrorHandler(corsHandler(handler))).
|
||||
UseTLSConfig(newTLSConfig(getCert)).
|
||||
UseShutdownTimeout(globalServerCtxt.ShutdownTimeout).
|
||||
UseIdleTimeout(globalServerCtxt.IdleTimeout).
|
||||
UseReadHeaderTimeout(globalServerCtxt.ReadHeaderTimeout).
|
||||
UseBaseContext(GlobalContext).
|
||||
@@ -920,6 +920,20 @@ func serverMain(ctx *cli.Context) {
|
||||
globalNodeNamesHex[hex.EncodeToString(nodeNameSum[:])] = struct{}{}
|
||||
}
|
||||
|
||||
bootstrapTrace("waitForQuorum", func() {
|
||||
result := newObject.Health(context.Background(), HealthOptions{})
|
||||
for !result.HealthyRead {
|
||||
if debugNoExit {
|
||||
logger.Info("Not waiting for quorum since we are debugging.. possible cause unhealthy sets (%s)", result)
|
||||
break
|
||||
}
|
||||
d := time.Duration(r.Float64() * float64(time.Second))
|
||||
logger.Info("Waiting for quorum READ healthcheck to succeed.. possible cause unhealthy sets (%s), retrying in %s", result, d)
|
||||
time.Sleep(d)
|
||||
result = newObject.Health(context.Background(), HealthOptions{})
|
||||
}
|
||||
})
|
||||
|
||||
var err error
|
||||
bootstrapTrace("initServerConfig", func() {
|
||||
if err = initServerConfig(GlobalContext, newObject); err != nil {
|
||||
@@ -986,8 +1000,6 @@ func serverMain(ctx *cli.Context) {
|
||||
}()
|
||||
|
||||
go func() {
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
if !globalDisableFreezeOnBoot {
|
||||
defer bootstrapTrace("unfreezeServices", unfreezeServices)
|
||||
t := time.AfterFunc(5*time.Minute, func() {
|
||||
@@ -1041,11 +1053,11 @@ func serverMain(ctx *cli.Context) {
|
||||
bootLogIf(GlobalContext, globalEventNotifier.InitBucketTargets(GlobalContext, newObject))
|
||||
})
|
||||
|
||||
var buckets []BucketInfo
|
||||
var buckets []string
|
||||
// List buckets to initialize bucket metadata sub-sys.
|
||||
bootstrapTrace("listBuckets", func() {
|
||||
for {
|
||||
buckets, err = newObject.ListBuckets(GlobalContext, BucketOptions{})
|
||||
bucketsList, err := newObject.ListBuckets(GlobalContext, BucketOptions{NoMetadata: true})
|
||||
if err != nil {
|
||||
if configRetriableErrors(err) {
|
||||
logger.Info("Waiting for list buckets to succeed to initialize buckets.. possible cause (%v)", err)
|
||||
@@ -1055,6 +1067,10 @@ func serverMain(ctx *cli.Context) {
|
||||
bootLogIf(GlobalContext, fmt.Errorf("Unable to list buckets to initialize bucket metadata sub-system: %w", err))
|
||||
}
|
||||
|
||||
buckets = make([]string, len(bucketsList))
|
||||
for i := range bucketsList {
|
||||
buckets[i] = bucketsList[i].Name
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
@@ -158,7 +158,7 @@ func printEventNotifiers() {
|
||||
return
|
||||
}
|
||||
|
||||
arns := globalEventNotifier.GetARNList(true)
|
||||
arns := globalEventNotifier.GetARNList()
|
||||
if len(arns) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -520,13 +520,14 @@ func (client *storageRESTClient) ReadVersion(ctx context.Context, origvolume, vo
|
||||
// Use websocket when not reading data.
|
||||
if !opts.ReadData {
|
||||
resp, err := storageReadVersionRPC.Call(ctx, client.gridConn, grid.NewMSSWith(map[string]string{
|
||||
storageRESTDiskID: *client.diskID.Load(),
|
||||
storageRESTOrigVolume: origvolume,
|
||||
storageRESTVolume: volume,
|
||||
storageRESTFilePath: path,
|
||||
storageRESTVersionID: versionID,
|
||||
storageRESTReadData: strconv.FormatBool(opts.ReadData),
|
||||
storageRESTHealing: strconv.FormatBool(opts.Healing),
|
||||
storageRESTDiskID: *client.diskID.Load(),
|
||||
storageRESTOrigVolume: origvolume,
|
||||
storageRESTVolume: volume,
|
||||
storageRESTFilePath: path,
|
||||
storageRESTVersionID: versionID,
|
||||
storageRESTInclFreeVersions: strconv.FormatBool(opts.InclFreeVersions),
|
||||
storageRESTReadData: strconv.FormatBool(opts.ReadData),
|
||||
storageRESTHealing: strconv.FormatBool(opts.Healing),
|
||||
}))
|
||||
if err != nil {
|
||||
return fi, toStorageErr(err)
|
||||
@@ -539,6 +540,7 @@ func (client *storageRESTClient) ReadVersion(ctx context.Context, origvolume, vo
|
||||
values.Set(storageRESTVolume, volume)
|
||||
values.Set(storageRESTFilePath, path)
|
||||
values.Set(storageRESTVersionID, versionID)
|
||||
values.Set(storageRESTInclFreeVersions, strconv.FormatBool(opts.InclFreeVersions))
|
||||
values.Set(storageRESTReadData, strconv.FormatBool(opts.ReadData))
|
||||
values.Set(storageRESTHealing, strconv.FormatBool(opts.Healing))
|
||||
|
||||
|
||||
+26
-25
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||
// Copyright (c) 2015-2024 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
@@ -20,7 +20,7 @@ package cmd
|
||||
//go:generate msgp -file $GOFILE -unexported
|
||||
|
||||
const (
|
||||
storageRESTVersion = "v58" // Change VerifyFile signature
|
||||
storageRESTVersion = "v59" // Change ReadOptions inclFreeVersions
|
||||
storageRESTVersionPrefix = SlashSeparator + storageRESTVersion
|
||||
storageRESTPrefix = minioReservedBucketPath + "/storage"
|
||||
)
|
||||
@@ -46,29 +46,30 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
storageRESTVolume = "volume"
|
||||
storageRESTVolumes = "volumes"
|
||||
storageRESTDirPath = "dir-path"
|
||||
storageRESTFilePath = "file-path"
|
||||
storageRESTVersionID = "version-id"
|
||||
storageRESTReadData = "read-data"
|
||||
storageRESTHealing = "healing"
|
||||
storageRESTTotalVersions = "total-versions"
|
||||
storageRESTSrcVolume = "source-volume"
|
||||
storageRESTSrcPath = "source-path"
|
||||
storageRESTDstVolume = "destination-volume"
|
||||
storageRESTDstPath = "destination-path"
|
||||
storageRESTOffset = "offset"
|
||||
storageRESTLength = "length"
|
||||
storageRESTCount = "count"
|
||||
storageRESTBitrotAlgo = "bitrot-algo"
|
||||
storageRESTBitrotHash = "bitrot-hash"
|
||||
storageRESTDiskID = "disk-id"
|
||||
storageRESTForceDelete = "force-delete"
|
||||
storageRESTGlob = "glob"
|
||||
storageRESTMetrics = "metrics"
|
||||
storageRESTDriveQuorum = "drive-quorum"
|
||||
storageRESTOrigVolume = "orig-volume"
|
||||
storageRESTVolume = "volume"
|
||||
storageRESTVolumes = "volumes"
|
||||
storageRESTDirPath = "dir-path"
|
||||
storageRESTFilePath = "file-path"
|
||||
storageRESTVersionID = "version-id"
|
||||
storageRESTReadData = "read-data"
|
||||
storageRESTHealing = "healing"
|
||||
storageRESTTotalVersions = "total-versions"
|
||||
storageRESTSrcVolume = "source-volume"
|
||||
storageRESTSrcPath = "source-path"
|
||||
storageRESTDstVolume = "destination-volume"
|
||||
storageRESTDstPath = "destination-path"
|
||||
storageRESTOffset = "offset"
|
||||
storageRESTLength = "length"
|
||||
storageRESTCount = "count"
|
||||
storageRESTBitrotAlgo = "bitrot-algo"
|
||||
storageRESTBitrotHash = "bitrot-hash"
|
||||
storageRESTDiskID = "disk-id"
|
||||
storageRESTForceDelete = "force-delete"
|
||||
storageRESTGlob = "glob"
|
||||
storageRESTMetrics = "metrics"
|
||||
storageRESTDriveQuorum = "drive-quorum"
|
||||
storageRESTOrigVolume = "orig-volume"
|
||||
storageRESTInclFreeVersions = "incl-free-versions"
|
||||
)
|
||||
|
||||
type nsScannerOptions struct {
|
||||
|
||||
@@ -378,7 +378,16 @@ func (s *storageRESTServer) ReadVersionHandlerWS(params *grid.MSS) (*FileInfo, *
|
||||
return nil, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
fi, err := s.getStorage().ReadVersion(context.Background(), origvolume, volume, filePath, versionID, ReadOptions{ReadData: readData, Healing: healing})
|
||||
inclFreeVersions, err := strconv.ParseBool(params.Get(storageRESTInclFreeVersions))
|
||||
if err != nil {
|
||||
return nil, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
fi, err := s.getStorage().ReadVersion(context.Background(), origvolume, volume, filePath, versionID, ReadOptions{
|
||||
InclFreeVersions: inclFreeVersions,
|
||||
ReadData: readData,
|
||||
Healing: healing,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, grid.NewRemoteErr(err)
|
||||
}
|
||||
@@ -404,7 +413,18 @@ func (s *storageRESTServer) ReadVersionHandler(w http.ResponseWriter, r *http.Re
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
fi, err := s.getStorage().ReadVersion(r.Context(), origvolume, volume, filePath, versionID, ReadOptions{ReadData: readData, Healing: healing})
|
||||
|
||||
inclFreeVersions, err := strconv.ParseBool(r.Form.Get(storageRESTInclFreeVersions))
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
fi, err := s.getStorage().ReadVersion(r.Context(), origvolume, volume, filePath, versionID, ReadOptions{
|
||||
InclFreeVersions: inclFreeVersions,
|
||||
ReadData: readData,
|
||||
Healing: healing,
|
||||
})
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"cloud.google.com/go/storage"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
@@ -118,14 +117,9 @@ func newWarmBackendGCS(conf madmin.TierGCS, tier string) (*warmBackendGCS, error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clnt := &http.Client{
|
||||
Transport: globalRemoteTargetTransport,
|
||||
}
|
||||
|
||||
client, err := storage.NewClient(context.Background(),
|
||||
option.WithCredentialsJSON(credsJSON),
|
||||
option.WithScopes(storage.ScopeReadWrite),
|
||||
option.WithHTTPClient(clnt),
|
||||
option.WithUserAgent(fmt.Sprintf("gcs-tier-%s", tier)+SlashSeparator+ReleaseTag),
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -103,7 +103,13 @@ func getAllFileInfoVersions(xlMetaBuf []byte, volume, path string, allParts bool
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getFileInfo(xlMetaBuf []byte, volume, path, versionID string, data, allParts bool) (FileInfo, error) {
|
||||
type fileInfoOpts struct {
|
||||
InclFreeVersions bool
|
||||
Data bool
|
||||
AllParts bool
|
||||
}
|
||||
|
||||
func getFileInfo(xlMetaBuf []byte, volume, path, versionID string, opts fileInfoOpts) (FileInfo, error) {
|
||||
var fi FileInfo
|
||||
var err error
|
||||
var inData xlMetaInlineData
|
||||
@@ -111,7 +117,7 @@ func getFileInfo(xlMetaBuf []byte, volume, path, versionID string, data, allPart
|
||||
return FileInfo{}, e
|
||||
} else if buf != nil {
|
||||
inData = data
|
||||
fi, err = buf.ToFileInfo(volume, path, versionID, allParts)
|
||||
fi, err = buf.ToFileInfo(volume, path, versionID, opts.AllParts)
|
||||
if len(buf) != 0 && errors.Is(err, errFileNotFound) {
|
||||
// This special case is needed to handle len(xlMeta.versions) == 0
|
||||
return FileInfo{
|
||||
@@ -140,15 +146,16 @@ func getFileInfo(xlMetaBuf []byte, volume, path, versionID string, data, allPart
|
||||
}, nil
|
||||
}
|
||||
inData = xlMeta.data
|
||||
fi, err = xlMeta.ToFileInfo(volume, path, versionID, false, allParts)
|
||||
fi, err = xlMeta.ToFileInfo(volume, path, versionID, opts.InclFreeVersions, opts.AllParts)
|
||||
}
|
||||
if !data || err != nil {
|
||||
if !opts.Data || err != nil {
|
||||
return fi, err
|
||||
}
|
||||
versionID = fi.VersionID
|
||||
if versionID == "" {
|
||||
versionID = nullVersionID
|
||||
}
|
||||
|
||||
fi.Data = inData.find(versionID)
|
||||
if len(fi.Data) == 0 {
|
||||
// PR #11758 used DataDir, preserve it
|
||||
|
||||
+9
-20
@@ -561,7 +561,7 @@ func (s *xlStorage) NSScanner(ctx context.Context, cache dataUsageCache, updates
|
||||
|
||||
cache.Info.updates = updates
|
||||
|
||||
dataUsageInfo, err := scanDataFolder(ctx, disks, s.drivePath, cache, func(item scannerItem) (sizeSummary, error) {
|
||||
dataUsageInfo, err := scanDataFolder(ctx, disks, s, cache, func(item scannerItem) (sizeSummary, error) {
|
||||
// Look for `xl.meta/xl.json' at the leaf.
|
||||
if !strings.HasSuffix(item.Path, SlashSeparator+xlStorageFormatFile) &&
|
||||
!strings.HasSuffix(item.Path, SlashSeparator+xlStorageFormatFileV1) {
|
||||
@@ -1612,8 +1612,9 @@ func (s *xlStorage) ReadXL(ctx context.Context, volume, path string, readData bo
|
||||
|
||||
// ReadOptions optional inputs for ReadVersion
|
||||
type ReadOptions struct {
|
||||
ReadData bool
|
||||
Healing bool
|
||||
InclFreeVersions bool
|
||||
ReadData bool
|
||||
Healing bool
|
||||
}
|
||||
|
||||
// ReadVersion - reads metadata and returns FileInfo at path `xl.meta`
|
||||
@@ -1657,7 +1658,11 @@ func (s *xlStorage) ReadVersion(ctx context.Context, origvolume, volume, path, v
|
||||
return fi, err
|
||||
}
|
||||
|
||||
fi, err = getFileInfo(buf, volume, path, versionID, readData, true)
|
||||
fi, err = getFileInfo(buf, volume, path, versionID, fileInfoOpts{
|
||||
Data: opts.ReadData,
|
||||
InclFreeVersions: opts.InclFreeVersions,
|
||||
AllParts: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fi, err
|
||||
}
|
||||
@@ -1710,22 +1715,6 @@ func (s *xlStorage) ReadVersion(ctx context.Context, origvolume, volume, path, v
|
||||
}
|
||||
}
|
||||
|
||||
if !skipAccessChecks(volume) && !opts.Healing && fi.TransitionStatus == "" && !fi.InlineData() && len(fi.Data) == 0 && fi.DataDir != "" && fi.DataDir != emptyUUID && fi.VersionPurgeStatus().Empty() {
|
||||
// Verify if the dataDir is present or not when the data
|
||||
// is not inlined to make sure we return correct errors
|
||||
// during HeadObject().
|
||||
|
||||
// Healing must not come here and return error, since healing
|
||||
// deals with dataDirs directly, let healing fix things automatically.
|
||||
if lerr := Access(pathJoin(volumeDir, path, fi.DataDir)); lerr != nil {
|
||||
if os.IsNotExist(lerr) {
|
||||
// Data dir is missing we must return errFileCorrupted
|
||||
return FileInfo{}, errFileCorrupt
|
||||
}
|
||||
return FileInfo{}, osErrToFileErr(lerr)
|
||||
}
|
||||
}
|
||||
|
||||
return fi, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ func TestXLStorageReadVersion(t *testing.T) {
|
||||
}
|
||||
|
||||
xlMeta, _ := os.ReadFile("testdata/xl.meta")
|
||||
fi, _ := getFileInfo(xlMeta, "exists", "as-file", "", false, true)
|
||||
fi, _ := getFileInfo(xlMeta, "exists", "as-file", "", fileInfoOpts{Data: false, AllParts: true})
|
||||
|
||||
// Create files for the test cases.
|
||||
if err = xlStorage.MakeVol(context.Background(), "exists"); err != nil {
|
||||
|
||||
@@ -20,6 +20,7 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@@ -34,6 +35,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/klauspost/compress/zip"
|
||||
@@ -237,7 +239,7 @@ FLAGS:
|
||||
}
|
||||
|
||||
if c.Bool("data") {
|
||||
b, err := data.json()
|
||||
b, err := data.json(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -562,7 +564,7 @@ func (x xlMetaInlineData) versionOK() bool {
|
||||
return x[0] > 0 && x[0] <= xlMetaInlineDataVer
|
||||
}
|
||||
|
||||
func (x xlMetaInlineData) json() ([]byte, error) {
|
||||
func (x xlMetaInlineData) json(value bool) ([]byte, error) {
|
||||
if len(x) == 0 {
|
||||
return []byte("{}"), nil
|
||||
}
|
||||
@@ -607,6 +609,17 @@ func (x xlMetaInlineData) json() ([]byte, error) {
|
||||
} else {
|
||||
s += ", \"bitrot_valid\": false"
|
||||
}
|
||||
if value {
|
||||
if utf8.Valid(data) {
|
||||
// Encode as JSON string.
|
||||
b, err := json.Marshal(string(data))
|
||||
if err == nil {
|
||||
s += `, "data_string": ` + string(b)
|
||||
}
|
||||
}
|
||||
// Base64 encode.
|
||||
s += `, "data_base64": "` + base64.StdEncoding.EncodeToString(data) + `"`
|
||||
}
|
||||
s += "}"
|
||||
}
|
||||
res = append(res, []byte(s)...)
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
# Settings and configurations that are common for all containers
|
||||
x-minio-common: &minio-common
|
||||
image: quay.io/minio/minio:RELEASE.2024-06-11T03-13-30Z
|
||||
image: quay.io/minio/minio:RELEASE.2024-06-13T22-53-53Z
|
||||
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
|
||||
expose:
|
||||
- "9000"
|
||||
|
||||
@@ -51,7 +51,7 @@ var (
|
||||
DefaultKVS = config.KVS{
|
||||
config.KV{
|
||||
Key: browserCSPPolicy,
|
||||
Value: "default-src 'self' 'unsafe-eval' 'unsafe-inline';",
|
||||
Value: "default-src 'self' 'unsafe-eval' 'unsafe-inline'; script-src 'self' https://unpkg.com; connect-src 'self' https://unpkg.com;",
|
||||
},
|
||||
config.KV{
|
||||
Key: browserHSTSSeconds,
|
||||
|
||||
@@ -639,9 +639,17 @@ func (dm *DRWMutex) Unlock(ctx context.Context) {
|
||||
tolerance := len(restClnts) / 2
|
||||
|
||||
isReadLock := false
|
||||
for !releaseAll(ctx, dm.clnt, tolerance, owner, &locks, isReadLock, restClnts, dm.Names...) {
|
||||
time.Sleep(time.Duration(dm.rng.Float64() * float64(dm.lockRetryMinInterval)))
|
||||
}
|
||||
started := time.Now()
|
||||
// Do async unlocking.
|
||||
// This means unlock will no longer block on the network or missing quorum.
|
||||
go func() {
|
||||
for !releaseAll(ctx, dm.clnt, tolerance, owner, &locks, isReadLock, restClnts, dm.Names...) {
|
||||
time.Sleep(time.Duration(dm.rng.Float64() * float64(dm.lockRetryMinInterval)))
|
||||
if time.Since(started) > dm.clnt.Timeouts.UnlockCall {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// RUnlock releases a read lock held on dm.
|
||||
@@ -678,11 +686,20 @@ func (dm *DRWMutex) RUnlock(ctx context.Context) {
|
||||
|
||||
// Tolerance is not set, defaults to half of the locker clients.
|
||||
tolerance := len(restClnts) / 2
|
||||
|
||||
isReadLock := true
|
||||
for !releaseAll(ctx, dm.clnt, tolerance, owner, &locks, isReadLock, restClnts, dm.Names...) {
|
||||
time.Sleep(time.Duration(dm.rng.Float64() * float64(dm.lockRetryMinInterval)))
|
||||
}
|
||||
started := time.Now()
|
||||
// Do async unlocking.
|
||||
// This means unlock will no longer block on the network or missing quorum.
|
||||
go func() {
|
||||
for !releaseAll(ctx, dm.clnt, tolerance, owner, &locks, isReadLock, restClnts, dm.Names...) {
|
||||
time.Sleep(time.Duration(dm.rng.Float64() * float64(dm.lockRetryMinInterval)))
|
||||
// If we have been waiting for more than the force unlock timeout, return
|
||||
// Remotes will have canceled due to the missing refreshes anyway.
|
||||
if time.Since(started) > dm.clnt.Timeouts.UnlockCall {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// sendRelease sends a release message to a node that previously granted a lock
|
||||
|
||||
@@ -293,6 +293,8 @@ func TestUnlockShouldNotTimeout(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
||||
defer cancel()
|
||||
dm.Unlock(ctx)
|
||||
// Unlock is not blocking. Try to get a new lock.
|
||||
dm.GetLock(ctx, nil, id, source, Options{Timeout: 5 * time.Minute})
|
||||
unlockReturned <- struct{}{}
|
||||
}()
|
||||
|
||||
|
||||
@@ -854,6 +854,37 @@ func (c *Connection) handleIncoming(ctx context.Context, conn net.Conn, req conn
|
||||
// caller *must* hold reconnectMu.
|
||||
func (c *Connection) reconnected() {
|
||||
c.updateState(StateConnectionError)
|
||||
|
||||
// Drain the outQueue, so any blocked messages can be sent.
|
||||
// We keep the queue, but start draining it, if it gets full.
|
||||
stopDraining := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
defer func() {
|
||||
close(stopDraining)
|
||||
wg.Wait()
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-stopDraining:
|
||||
return
|
||||
default:
|
||||
if cap(c.outQueue)-len(c.outQueue) > 100 {
|
||||
// Queue is not full, wait a bit.
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case v := <-c.outQueue:
|
||||
PutByteBuffer(v)
|
||||
case <-stopDraining:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Close all active requests.
|
||||
if debugReqs {
|
||||
fmt.Println(c.String(), "Reconnected. Clearing outgoing.")
|
||||
|
||||
+7
-61
@@ -22,11 +22,8 @@ import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -43,11 +40,6 @@ var (
|
||||
)
|
||||
|
||||
const (
|
||||
shutdownPollIntervalMax = 500 * time.Millisecond
|
||||
|
||||
// DefaultShutdownTimeout - default shutdown timeout to gracefully shutdown server.
|
||||
DefaultShutdownTimeout = 5 * time.Second
|
||||
|
||||
// DefaultIdleTimeout for idle inactive connections
|
||||
DefaultIdleTimeout = 30 * time.Second
|
||||
|
||||
@@ -61,13 +53,12 @@ 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.
|
||||
TCPOptions TCPOptions // all the configurable TCP conn specific configurable options.
|
||||
ShutdownTimeout time.Duration // timeout used for graceful server shutdown.
|
||||
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.
|
||||
TCPOptions TCPOptions // all the configurable TCP conn specific configurable options.
|
||||
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.
|
||||
@@ -166,53 +157,8 @@ func (srv *Server) Shutdown() error {
|
||||
return err
|
||||
}
|
||||
|
||||
pollIntervalBase := time.Millisecond
|
||||
nextPollInterval := func() time.Duration {
|
||||
// Add 10% jitter.
|
||||
interval := pollIntervalBase + time.Duration(rand.Intn(int(pollIntervalBase/10)))
|
||||
// Double and clamp for next time.
|
||||
pollIntervalBase *= 2
|
||||
if pollIntervalBase > shutdownPollIntervalMax {
|
||||
pollIntervalBase = shutdownPollIntervalMax
|
||||
}
|
||||
return interval
|
||||
}
|
||||
|
||||
// Wait for opened connection to be closed up to Shutdown timeout.
|
||||
shutdownTimeout := srv.ShutdownTimeout
|
||||
shutdownTimer := time.NewTimer(shutdownTimeout)
|
||||
defer shutdownTimer.Stop()
|
||||
|
||||
timer := time.NewTimer(nextPollInterval())
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-shutdownTimer.C:
|
||||
if atomic.LoadInt32(&srv.requestCount) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write all running goroutines.
|
||||
tmp, err := os.CreateTemp("", "minio-goroutines-*.txt")
|
||||
if err == nil {
|
||||
_ = pprof.Lookup("goroutine").WriteTo(tmp, 1)
|
||||
tmp.Close()
|
||||
return errors.New("timed out. some connections are still active. goroutines written to " + tmp.Name())
|
||||
}
|
||||
return errors.New("timed out. some connections are still active")
|
||||
case <-timer.C:
|
||||
if atomic.LoadInt32(&srv.requestCount) <= 0 {
|
||||
return nil
|
||||
}
|
||||
timer.Reset(nextPollInterval())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UseShutdownTimeout configure server shutdown timeout
|
||||
func (srv *Server) UseShutdownTimeout(d time.Duration) *Server {
|
||||
srv.ShutdownTimeout = d
|
||||
return srv
|
||||
return nil
|
||||
}
|
||||
|
||||
// UseIdleTimeout configure idle connection timeout
|
||||
|
||||
@@ -48,8 +48,7 @@ func TestNewServer(t *testing.T) {
|
||||
|
||||
for i, testCase := range testCases {
|
||||
server := NewServer(testCase.addrs).
|
||||
UseHandler(testCase.handler).
|
||||
UseShutdownTimeout(DefaultShutdownTimeout)
|
||||
UseHandler(testCase.handler)
|
||||
if testCase.certFn != nil {
|
||||
server = server.UseTLSConfig(&tls.Config{
|
||||
PreferServerCipherSuites: true,
|
||||
@@ -72,10 +71,6 @@ func TestNewServer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if server.ShutdownTimeout != DefaultShutdownTimeout {
|
||||
t.Fatalf("Case %v: server.ShutdownTimeout: expected: %v, got: %v", (i + 1), DefaultShutdownTimeout, server.ShutdownTimeout)
|
||||
}
|
||||
|
||||
if server.MaxHeaderBytes != DefaultMaxHeaderBytes {
|
||||
t.Fatalf("Case %v: server.MaxHeaderBytes: expected: %v, got: %v", (i + 1), DefaultMaxHeaderBytes, server.MaxHeaderBytes)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user