mirror of
https://github.com/pgsty/minio.git
synced 2026-11-07 00:02:11 +03:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 17747db93f | |||
| 3fe27c8411 | |||
| 14b1c9f8e4 | |||
| d84fc58cac | |||
| 187c3f62df | |||
| 4a447a439a | |||
| 4bfc50411c | |||
| 5e8392c8ef | |||
| d3c81a6e93 | |||
| 7342b5355f | |||
| 1341bf5a9e | |||
| 48aebf2d9d | |||
| 7b14e9b660 | |||
| 07eb24b775 | |||
| cd849bc2ff | |||
| 9c66812b99 | |||
| ec91fa55db | |||
| 00d3cc4b69 | |||
| 14ff7f5fcf | |||
| a97ce3c96e | |||
| cfc5681b36 | |||
| 369a876ebe | |||
| 778e9c864f | |||
| a2616b8227 | |||
| 8d8f28eae4 | |||
| e7d7d5232c | |||
| 1d65ef3201 | |||
| 8d425e3372 | |||
| 3939c6f6e7 | |||
| 60d91234b9 | |||
| 37c14207d6 | |||
| 3b9fbf80ad | |||
| c2fdf73491 | |||
| 143f9371c6 | |||
| 3f1902face | |||
| c0adb52213 | |||
| 3520e946a2 | |||
| f38adc1865 | |||
| d5ff1c8e3b | |||
| ad7417bc50 |
@@ -46,11 +46,11 @@ class PreviewObjectModal extends React.Component {
|
||||
<ModalBody>
|
||||
<div className="input-group">
|
||||
{this.state.url && (
|
||||
<img
|
||||
alt="Image broken"
|
||||
src={this.state.url}
|
||||
style={{ display: "block", width: "100%" }}
|
||||
/>
|
||||
<object data={this.state.url} style={{ display: "block", width: "100%" }}>
|
||||
<h3 style={{ textAlign: "center", display: "block", width: "100%" }}>
|
||||
Do not have read permissions to preview "{this.props.object.name}"
|
||||
</h3>
|
||||
</object>
|
||||
)}
|
||||
</div>
|
||||
</ModalBody>
|
||||
|
||||
@@ -349,6 +349,7 @@ div.fesl-row {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
@@ -495,4 +496,4 @@ div.fesl-row {
|
||||
.opacity(1);
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
-37
File diff suppressed because one or more lines are too long
+17
-45
@@ -1419,23 +1419,12 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
||||
// Fetching the Storage information, ignore any errors.
|
||||
storageInfo, _ := objectAPI.StorageInfo(ctx, false)
|
||||
|
||||
var OnDisks int
|
||||
var OffDisks int
|
||||
var backend interface{}
|
||||
|
||||
if storageInfo.Backend.Type == BackendType(madmin.Erasure) {
|
||||
|
||||
for _, v := range storageInfo.Backend.OnlineDisks {
|
||||
OnDisks += v
|
||||
}
|
||||
for _, v := range storageInfo.Backend.OfflineDisks {
|
||||
OffDisks += v
|
||||
}
|
||||
|
||||
backend = madmin.ErasureBackend{
|
||||
Type: madmin.ErasureType,
|
||||
OnlineDisks: OnDisks,
|
||||
OfflineDisks: OffDisks,
|
||||
OnlineDisks: storageInfo.Backend.OnlineDisks.Sum(),
|
||||
OfflineDisks: storageInfo.Backend.OfflineDisks.Sum(),
|
||||
StandardSCData: storageInfo.Backend.StandardSCData,
|
||||
StandardSCParity: storageInfo.Backend.StandardSCParity,
|
||||
RRSCData: storageInfo.Backend.RRSCData,
|
||||
@@ -1456,38 +1445,6 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
||||
servers := globalNotificationSys.ServerInfo()
|
||||
servers = append(servers, server)
|
||||
|
||||
for _, sp := range servers {
|
||||
for i, di := range sp.Disks {
|
||||
path := ""
|
||||
if globalIsErasure {
|
||||
path = di.DrivePath
|
||||
}
|
||||
if globalIsDistErasure {
|
||||
path = sp.Endpoint + di.DrivePath
|
||||
}
|
||||
// For distributed
|
||||
for a := range storageInfo.Backend.Sets {
|
||||
for b := range storageInfo.Backend.Sets[a] {
|
||||
ep := storageInfo.Backend.Sets[a][b].Endpoint
|
||||
|
||||
if globalIsDistErasure {
|
||||
if strings.Replace(ep, "http://", "", -1) == path || strings.Replace(ep, "https://", "", -1) == path {
|
||||
sp.Disks[i].State = storageInfo.Backend.Sets[a][b].State
|
||||
sp.Disks[i].UUID = storageInfo.Backend.Sets[a][b].UUID
|
||||
}
|
||||
}
|
||||
if globalIsErasure {
|
||||
if ep == path {
|
||||
sp.Disks[i].State = storageInfo.Backend.Sets[a][b].State
|
||||
sp.Disks[i].UUID = storageInfo.Backend.Sets[a][b].UUID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
domain := globalDomainNames
|
||||
services := madmin.Services{
|
||||
Vault: vault,
|
||||
@@ -1497,6 +1454,21 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
|
||||
Notifications: notifyTarget,
|
||||
}
|
||||
|
||||
// find all disks which belong to each respective endpoints
|
||||
for i := range servers {
|
||||
for _, disk := range storageInfo.Disks {
|
||||
if strings.Contains(disk.Endpoint, servers[i].Endpoint) {
|
||||
servers[i].Disks = append(servers[i].Disks, disk)
|
||||
}
|
||||
}
|
||||
}
|
||||
// add all the disks local to this server.
|
||||
for _, disk := range storageInfo.Disks {
|
||||
if disk.Endpoint == disk.DrivePath {
|
||||
servers[len(servers)-1].Disks = append(servers[len(servers)-1].Disks, disk)
|
||||
}
|
||||
}
|
||||
|
||||
infoMsg := madmin.InfoMessage{
|
||||
Mode: mode,
|
||||
Domain: domain,
|
||||
|
||||
+23
-24
@@ -63,7 +63,7 @@ var (
|
||||
errHealStopSignalled = fmt.Errorf("heal stop signaled")
|
||||
|
||||
errFnHealFromAPIErr = func(ctx context.Context, err error) error {
|
||||
apiErr := toAPIError(ctx, err)
|
||||
apiErr := toAdminAPIErr(ctx, err)
|
||||
return fmt.Errorf("Heal internal error: %s: %s",
|
||||
apiErr.Code, apiErr.Description)
|
||||
}
|
||||
@@ -151,7 +151,7 @@ func (ahs *allHealState) stopHealSequence(path string) ([]byte, APIError) {
|
||||
he, exists := ahs.getHealSequence(path)
|
||||
if !exists {
|
||||
hsp = madmin.HealStopSuccess{
|
||||
ClientToken: "invalid",
|
||||
ClientToken: "unknown",
|
||||
StartTime: UTCNow(),
|
||||
}
|
||||
} else {
|
||||
@@ -193,21 +193,14 @@ func (ahs *allHealState) stopHealSequence(path string) ([]byte, APIError) {
|
||||
func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
|
||||
respBytes []byte, apiErr APIError, errMsg string) {
|
||||
|
||||
existsAndLive := false
|
||||
he, exists := ahs.getHealSequence(pathJoin(h.bucket, h.object))
|
||||
if exists {
|
||||
existsAndLive = !he.hasEnded()
|
||||
}
|
||||
|
||||
if existsAndLive {
|
||||
// A heal sequence exists on the given path.
|
||||
if h.forceStarted {
|
||||
// stop the running heal sequence - wait for it to finish.
|
||||
he.stop()
|
||||
for !he.hasEnded() {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
} else {
|
||||
if h.forceStarted {
|
||||
_, apiErr = ahs.stopHealSequence(pathJoin(h.bucket, h.object))
|
||||
if apiErr.Code != "" {
|
||||
return respBytes, apiErr, ""
|
||||
}
|
||||
} else {
|
||||
oh, exists := ahs.getHealSequence(pathJoin(h.bucket, h.object))
|
||||
if exists && !oh.hasEnded() {
|
||||
errMsg = "Heal is already running on the given path " +
|
||||
"(use force-start option to stop and start afresh). " +
|
||||
fmt.Sprintf("The heal was started by IP %s at %s, token is %s",
|
||||
@@ -224,7 +217,6 @@ func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
|
||||
hpath := pathJoin(h.bucket, h.object)
|
||||
for k, hSeq := range ahs.healSeqMap {
|
||||
if !hSeq.hasEnded() && (HasPrefix(k, hpath) || HasPrefix(hpath, k)) {
|
||||
|
||||
errMsg = "The provided heal sequence path overlaps with an existing " +
|
||||
fmt.Sprintf("heal path: %s", k)
|
||||
return nil, errorCodes.ToAPIErr(ErrHealOverlappingPaths), errMsg
|
||||
@@ -249,7 +241,7 @@ func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
|
||||
})
|
||||
if err != nil {
|
||||
logger.LogIf(h.ctx, err)
|
||||
return nil, toAPIError(h.ctx, err), ""
|
||||
return nil, toAdminAPIErr(h.ctx, err), ""
|
||||
}
|
||||
return b, noError, ""
|
||||
}
|
||||
@@ -264,8 +256,11 @@ func (ahs *allHealState) PopHealStatusJSON(hpath string,
|
||||
// fetch heal state for given path
|
||||
h, exists := ahs.getHealSequence(hpath)
|
||||
if !exists {
|
||||
// If there is no such heal sequence, return error.
|
||||
return nil, ErrHealNoSuchProcess
|
||||
// heal sequence doesn't exist, must have finished.
|
||||
jbytes, err := json.Marshal(healSequenceStatus{
|
||||
Summary: healFinishedStatus,
|
||||
})
|
||||
return jbytes, toAdminAPIErrCode(GlobalContext, err)
|
||||
}
|
||||
|
||||
// Check if client-token is valid
|
||||
@@ -606,8 +601,7 @@ func (h *healSequence) healSequenceStart() {
|
||||
case <-h.ctx.Done():
|
||||
h.mutex.Lock()
|
||||
h.endTime = UTCNow()
|
||||
h.currentStatus.Summary = healStoppedStatus
|
||||
h.currentStatus.FailureDetail = errHealStopSignalled.Error()
|
||||
h.currentStatus.Summary = healFinishedStatus
|
||||
h.mutex.Unlock()
|
||||
|
||||
// drain traverse channel so the traversal
|
||||
@@ -704,7 +698,12 @@ func (h *healSequence) healItemsFromSourceCh() error {
|
||||
}
|
||||
|
||||
if err := h.queueHealTask(source, itemType); err != nil {
|
||||
logger.LogIf(h.ctx, err)
|
||||
switch err.(type) {
|
||||
case ObjectExistsAsDirectory:
|
||||
default:
|
||||
logger.LogIf(h.ctx, fmt.Errorf("Heal attempt failed for %s: %w",
|
||||
pathJoin(source.bucket, source.object), err))
|
||||
}
|
||||
}
|
||||
|
||||
h.scannedItemsMap[itemType]++
|
||||
|
||||
@@ -18,16 +18,13 @@ package cmd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/minio/minio/pkg/disk"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
// getLocalServerProperty - returns madmin.ServerProperties for only the
|
||||
// local endpoints from given list of endpoints
|
||||
func getLocalServerProperty(endpointZones EndpointZones, r *http.Request) madmin.ServerProperties {
|
||||
var disks []madmin.Disk
|
||||
addr := r.Host
|
||||
if globalIsDistErasure {
|
||||
addr = GetLocalPeer(endpointZones)
|
||||
@@ -42,33 +39,14 @@ func getLocalServerProperty(endpointZones EndpointZones, r *http.Request) madmin
|
||||
if endpoint.IsLocal {
|
||||
// Only proceed for local endpoints
|
||||
network[nodeName] = "online"
|
||||
var di = madmin.Disk{
|
||||
DrivePath: endpoint.Path,
|
||||
}
|
||||
diInfo, err := disk.GetInfo(endpoint.Path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || isSysErrPathNotFound(err) {
|
||||
di.State = madmin.DriveStateMissing
|
||||
} else {
|
||||
di.State = madmin.DriveStateCorrupt
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, present := network[nodeName]
|
||||
if !present {
|
||||
if err := IsServerResolvable(endpoint); err == nil {
|
||||
network[nodeName] = "online"
|
||||
} else {
|
||||
di.State = madmin.DriveStateOk
|
||||
di.DrivePath = endpoint.Path
|
||||
di.TotalSpace = diInfo.Total
|
||||
di.UsedSpace = diInfo.Total - diInfo.Free
|
||||
di.Utilization = float64((diInfo.Total - diInfo.Free) / diInfo.Total * 100)
|
||||
}
|
||||
disks = append(disks, di)
|
||||
} else {
|
||||
_, present := network[nodeName]
|
||||
if !present {
|
||||
err := IsServerResolvable(endpoint)
|
||||
if err == nil {
|
||||
network[nodeName] = "online"
|
||||
} else {
|
||||
network[nodeName] = "offline"
|
||||
}
|
||||
network[nodeName] = "offline"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,6 +59,5 @@ func getLocalServerProperty(endpointZones EndpointZones, r *http.Request) madmin
|
||||
Version: Version,
|
||||
CommitID: CommitID,
|
||||
Network: network,
|
||||
Disks: disks,
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -27,8 +27,8 @@ import (
|
||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"google.golang.org/api/googleapi"
|
||||
|
||||
minio "github.com/minio/minio-go/v6"
|
||||
"github.com/minio/minio-go/v6/pkg/tags"
|
||||
minio "github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/cmd/config/etcd/dns"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
|
||||
+1
-2
@@ -396,8 +396,7 @@ func getObjectLocation(r *http.Request, domains []string, bucket, object string)
|
||||
}
|
||||
// If domain is set then we need to use bucket DNS style.
|
||||
for _, domain := range domains {
|
||||
if strings.Contains(r.Host, domain) {
|
||||
u.Host = bucket + "." + r.Host
|
||||
if strings.HasPrefix(r.Host, bucket+"."+domain) {
|
||||
u.Path = path.Join(SlashSeparator, object)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ func TestObjectLocation(t *testing.T) {
|
||||
// Server with virtual domain name.
|
||||
{
|
||||
request: &http.Request{
|
||||
Host: "mys3.bucket.org",
|
||||
Host: "mybucket.mys3.bucket.org",
|
||||
Header: map[string][]string{},
|
||||
},
|
||||
domains: []string{"mys3.bucket.org"},
|
||||
@@ -87,7 +87,7 @@ func TestObjectLocation(t *testing.T) {
|
||||
},
|
||||
{
|
||||
request: &http.Request{
|
||||
Host: "mys3.bucket.org",
|
||||
Host: "mybucket.mys3.bucket.org",
|
||||
Header: map[string][]string{
|
||||
"X-Forwarded-Scheme": {httpsScheme},
|
||||
},
|
||||
@@ -98,11 +98,14 @@ func TestObjectLocation(t *testing.T) {
|
||||
expectedLocation: "https://mybucket.mys3.bucket.org/test/1.txt",
|
||||
},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
gotLocation := getObjectLocation(testCase.request, testCase.domains, testCase.bucket, testCase.object)
|
||||
if testCase.expectedLocation != gotLocation {
|
||||
t.Errorf("Test %d: expected %s, got %s", i+1, testCase.expectedLocation, gotLocation)
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run("", func(t *testing.T) {
|
||||
gotLocation := getObjectLocation(testCase.request, testCase.domains, testCase.bucket, testCase.object)
|
||||
if testCase.expectedLocation != gotLocation {
|
||||
t.Errorf("expected %s, got %s", testCase.expectedLocation, gotLocation)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-23
@@ -84,16 +84,7 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
|
||||
apiRouter := router.PathPrefix(SlashSeparator).Subrouter()
|
||||
var routers []*mux.Router
|
||||
for _, domainName := range globalDomainNames {
|
||||
if globalMinioPort == "80" || globalMinioPort == "443" {
|
||||
// For standard ports its possible, incoming requests
|
||||
// might not have a port assigned, in such scenarios use
|
||||
// have wildcard routing.
|
||||
// FIXME: remove this code once https://github.com/gorilla/mux/pull/579
|
||||
// is merged and released upstream, as this entire change can become
|
||||
// a single line.
|
||||
routers = append(routers, apiRouter.Host("{bucket:.+}."+domainName).Subrouter())
|
||||
}
|
||||
routers = append(routers, apiRouter.Host("{bucket:.+}."+domainName+":{port:.*}").Subrouter())
|
||||
routers = append(routers, apiRouter.Host("{bucket:.+}."+domainName).Subrouter())
|
||||
}
|
||||
routers = append(routers, apiRouter.PathPrefix("/{bucket}").Subrouter())
|
||||
|
||||
@@ -300,21 +291,14 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool)
|
||||
apiRouter.Methods(http.MethodGet).Path(SlashSeparator + SlashSeparator).HandlerFunc(
|
||||
maxClients(collectAPIStats("listbuckets", httpTraceAll(api.ListBucketsHandler))))
|
||||
|
||||
// Supports cors only for S3 handlers
|
||||
apiRouter.Methods(http.MethodOptions).Path(SlashSeparator).HandlerFunc(
|
||||
maxClients(collectAPIStats("cors", httpTraceAll(corsHandlerFunc()))))
|
||||
|
||||
apiRouter.Methods(http.MethodOptions).Path(SlashSeparator + SlashSeparator).HandlerFunc(
|
||||
maxClients(collectAPIStats("cors", httpTraceAll(corsHandlerFunc()))))
|
||||
|
||||
// If none of the routes match add default error handler routes
|
||||
apiRouter.NotFoundHandler = http.HandlerFunc(collectAPIStats("notfound", httpTraceAll(errorResponseHandler)))
|
||||
apiRouter.MethodNotAllowedHandler = http.HandlerFunc(collectAPIStats("methodnotallowed", httpTraceAll(errorResponseHandler)))
|
||||
|
||||
}
|
||||
|
||||
// setCorsHandler handler for CORS (Cross Origin Resource Sharing)
|
||||
func corsHandlerFunc() http.HandlerFunc {
|
||||
// corsHandler handler for CORS (Cross Origin Resource Sharing)
|
||||
func corsHandler(handler http.Handler) http.Handler {
|
||||
commonS3Headers := []string{
|
||||
xhttp.Date,
|
||||
xhttp.ETag,
|
||||
@@ -330,7 +314,7 @@ func corsHandlerFunc() http.HandlerFunc {
|
||||
"*",
|
||||
}
|
||||
|
||||
c := cors.New(cors.Options{
|
||||
return cors.New(cors.Options{
|
||||
AllowOriginFunc: func(origin string) bool {
|
||||
for _, allowedOrigin := range globalAPIConfig.getCorsAllowOrigins() {
|
||||
if wildcard.MatchSimple(allowedOrigin, origin) {
|
||||
@@ -351,7 +335,5 @@ func corsHandlerFunc() http.HandlerFunc {
|
||||
AllowedHeaders: commonS3Headers,
|
||||
ExposedHeaders: commonS3Headers,
|
||||
AllowCredentials: true,
|
||||
})
|
||||
|
||||
return c.HandlerFunc
|
||||
}).Handler(handler)
|
||||
}
|
||||
|
||||
+4
-3
@@ -213,15 +213,15 @@ func getClaimsFromToken(r *http.Request, token string) (map[string]interface{},
|
||||
|
||||
if globalPolicyOPA == nil {
|
||||
// If OPA is not set and if ldap claim key is set, allow the claim.
|
||||
if _, ok := claims.Lookup(ldapUser); ok {
|
||||
if _, ok := claims.MapClaims[ldapUser]; ok {
|
||||
return claims.Map(), nil
|
||||
}
|
||||
|
||||
// If OPA is not set, session token should
|
||||
// have a policy and its mandatory, reject
|
||||
// requests without policy claim.
|
||||
_, pokOpenID := claims.Lookup(iamPolicyClaimNameOpenID())
|
||||
_, pokSA := claims.Lookup(iamPolicyClaimNameSA())
|
||||
_, pokOpenID := claims.MapClaims[iamPolicyClaimNameOpenID()]
|
||||
_, pokSA := claims.MapClaims[iamPolicyClaimNameSA()]
|
||||
if !pokOpenID && !pokSA {
|
||||
return nil, errAuthentication
|
||||
}
|
||||
@@ -360,6 +360,7 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio
|
||||
// Request is allowed return the appropriate access key.
|
||||
return cred.AccessKey, owner, ErrNone
|
||||
}
|
||||
|
||||
return cred.AccessKey, owner, ErrAccessDenied
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,11 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
)
|
||||
|
||||
const defaultMonitorNewDiskInterval = time.Minute * 10
|
||||
const defaultMonitorNewDiskInterval = time.Minute * 3
|
||||
|
||||
func initLocalDisksAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
||||
go monitorLocalDisksAndHeal(ctx, objAPI)
|
||||
@@ -83,6 +84,13 @@ func monitorLocalDisksAndHeal(ctx context.Context, objAPI ObjectLayer) {
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Info("New unformatted drives detected attempting to heal...")
|
||||
for i, disks := range localDisksInZoneHeal {
|
||||
for _, disk := range disks {
|
||||
logger.Info("Healing disk '%s' on %s zone", disk, humanize.Ordinal(i+1))
|
||||
}
|
||||
}
|
||||
|
||||
// Reformat disks
|
||||
bgSeq.sourceCh <- healSource{bucket: SlashSeparator}
|
||||
|
||||
@@ -97,13 +105,13 @@ func monitorLocalDisksAndHeal(ctx context.Context, objAPI ObjectLayer) {
|
||||
// Load the new format of this passed endpoint
|
||||
_, format, err := connectEndpoint(endpoint)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
printEndpointError(endpoint, err, true)
|
||||
continue
|
||||
}
|
||||
// Calculate the set index where the current endpoint belongs
|
||||
setIndex, _, err := findDiskIndex(z.zones[i].format, format)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
printEndpointError(endpoint, err, false)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/cmd/rest"
|
||||
@@ -205,11 +205,7 @@ func newBootstrapRESTClients(endpointZones EndpointZones) []*bootstrapRESTClient
|
||||
|
||||
// Only proceed for remote endpoints.
|
||||
if !endpoint.IsLocal {
|
||||
clnt, err := newBootstrapRESTClient(endpoint)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
clnts = append(clnts, clnt)
|
||||
clnts = append(clnts, newBootstrapRESTClient(endpoint))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +213,7 @@ func newBootstrapRESTClients(endpointZones EndpointZones) []*bootstrapRESTClient
|
||||
}
|
||||
|
||||
// Returns a new bootstrap client.
|
||||
func newBootstrapRESTClient(endpoint Endpoint) (*bootstrapRESTClient, error) {
|
||||
func newBootstrapRESTClient(endpoint Endpoint) *bootstrapRESTClient {
|
||||
serverURL := &url.URL{
|
||||
Scheme: endpoint.Scheme,
|
||||
Host: endpoint.Host,
|
||||
@@ -233,19 +229,17 @@ func newBootstrapRESTClient(endpoint Endpoint) (*bootstrapRESTClient, error) {
|
||||
}
|
||||
|
||||
trFn := newCustomHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)
|
||||
restClient, err := rest.NewClient(serverURL, trFn, newAuthToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
restClient := rest.NewClient(serverURL, trFn, newAuthToken)
|
||||
restClient.HealthCheckFn = func() bool {
|
||||
ctx, cancel := context.WithTimeout(GlobalContext, restClient.HealthCheckTimeout)
|
||||
respBody, err := restClient.CallWithContext(ctx, bootstrapRESTMethodHealth, nil, nil, -1)
|
||||
// Instantiate a new rest client for healthcheck
|
||||
// to avoid recursive healthCheckFn()
|
||||
respBody, err := rest.NewClient(serverURL, trFn, newAuthToken).CallWithContext(ctx, bootstrapRESTMethodHealth, nil, nil, -1)
|
||||
xhttp.DrainBody(respBody)
|
||||
cancel()
|
||||
var ne *rest.NetworkError
|
||||
return !errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &ne)
|
||||
}
|
||||
|
||||
return &bootstrapRESTClient{endpoint: endpoint, restClient: restClient}, nil
|
||||
return &bootstrapRESTClient{endpoint: endpoint, restClient: restClient}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ import (
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v6/pkg/tags"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/cmd/config/etcd/dns"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/tags"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
||||
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/tags"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
"time"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
miniogopolicy "github.com/minio/minio-go/v6/pkg/policy"
|
||||
miniogopolicy "github.com/minio/minio-go/v7/pkg/policy"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
|
||||
+90
-129
@@ -22,9 +22,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/env"
|
||||
"github.com/minio/minio/pkg/event"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
@@ -120,149 +118,112 @@ func enforceBucketQuota(ctx context.Context, bucket string, size int64) error {
|
||||
return globalBucketQuotaSys.check(ctx, bucket, size)
|
||||
}
|
||||
|
||||
const (
|
||||
bgQuotaInterval = 1 * time.Hour
|
||||
)
|
||||
|
||||
// initQuotaEnforcement starts the routine that deletes objects in bucket
|
||||
// that exceeds the FIFO quota
|
||||
func initQuotaEnforcement(ctx context.Context, objAPI ObjectLayer) {
|
||||
if env.Get(envDataUsageCrawlConf, config.EnableOn) == config.EnableOn {
|
||||
go startBucketQuotaEnforcement(ctx, objAPI)
|
||||
}
|
||||
}
|
||||
|
||||
func startBucketQuotaEnforcement(ctx context.Context, objAPI ObjectLayer) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-time.NewTimer(bgQuotaInterval).C:
|
||||
enforceFIFOQuota(ctx, objAPI)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// enforceFIFOQuota deletes objects in FIFO order until sufficient objects
|
||||
// have been deleted so as to bring bucket usage within quota
|
||||
func enforceFIFOQuota(ctx context.Context, objectAPI ObjectLayer) {
|
||||
// Turn off quota enforcement if data usage info is unavailable.
|
||||
if env.Get(envDataUsageCrawlConf, config.EnableOn) == config.EnableOff {
|
||||
// have been deleted so as to bring bucket usage within quota.
|
||||
func enforceFIFOQuotaBucket(ctx context.Context, objectAPI ObjectLayer, bucket string, bui BucketUsageInfo) {
|
||||
// Check if the current bucket has quota restrictions, if not skip it
|
||||
cfg, err := globalBucketQuotaSys.Get(bucket)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buckets, err := objectAPI.ListBuckets(ctx)
|
||||
if cfg.Type != madmin.FIFOQuota {
|
||||
return
|
||||
}
|
||||
|
||||
var toFree uint64
|
||||
if bui.Size > cfg.Quota && cfg.Quota > 0 {
|
||||
toFree = bui.Size - cfg.Quota
|
||||
}
|
||||
|
||||
if toFree <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Allocate new results channel to receive ObjectInfo.
|
||||
objInfoCh := make(chan ObjectInfo)
|
||||
|
||||
versioned := globalBucketVersioningSys.Enabled(bucket)
|
||||
|
||||
// Walk through all objects
|
||||
if err := objectAPI.Walk(ctx, bucket, "", objInfoCh, ObjectOptions{WalkVersions: versioned}); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
// reuse the fileScorer used by disk cache to score entries by
|
||||
// ModTime to find the oldest objects in bucket to delete. In
|
||||
// the context of bucket quota enforcement - number of hits are
|
||||
// irrelevant.
|
||||
scorer, err := newFileScorer(toFree, time.Now().Unix(), 1)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objectAPI)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, binfo := range buckets {
|
||||
bucket := binfo.Name
|
||||
|
||||
bui, ok := dataUsageInfo.BucketsUsage[bucket]
|
||||
if !ok {
|
||||
// bucket doesn't exist anymore, or we
|
||||
// do not have any information to proceed.
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if the current bucket has quota restrictions, if not skip it
|
||||
cfg, err := globalBucketQuotaSys.Get(bucket)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if cfg.Type != madmin.FIFOQuota {
|
||||
continue
|
||||
}
|
||||
|
||||
var toFree uint64
|
||||
if bui.Size > cfg.Quota && cfg.Quota > 0 {
|
||||
toFree = bui.Size - cfg.Quota
|
||||
}
|
||||
|
||||
if toFree == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Allocate new results channel to receive ObjectInfo.
|
||||
objInfoCh := make(chan ObjectInfo)
|
||||
|
||||
versioned := globalBucketVersioningSys.Enabled(bucket)
|
||||
|
||||
// Walk through all objects
|
||||
if err := objectAPI.Walk(ctx, bucket, "", objInfoCh, ObjectOptions{WalkVersions: versioned}); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// reuse the fileScorer used by disk cache to score entries by
|
||||
// ModTime to find the oldest objects in bucket to delete. In
|
||||
// the context of bucket quota enforcement - number of hits are
|
||||
// irrelevant.
|
||||
scorer, err := newFileScorer(toFree, time.Now().Unix(), 1)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
}
|
||||
|
||||
rcfg, _ := globalBucketObjectLockSys.Get(bucket)
|
||||
for obj := range objInfoCh {
|
||||
if obj.DeleteMarker {
|
||||
// Delete markers are automatically added for FIFO purge.
|
||||
scorer.addFileWithObjInfo(obj, 1)
|
||||
continue
|
||||
}
|
||||
// skip objects currently under retention
|
||||
if rcfg.LockEnabled && enforceRetentionForDeletion(ctx, obj) {
|
||||
continue
|
||||
}
|
||||
rcfg, _ := globalBucketObjectLockSys.Get(bucket)
|
||||
for obj := range objInfoCh {
|
||||
if obj.DeleteMarker {
|
||||
// Delete markers are automatically added for FIFO purge.
|
||||
scorer.addFileWithObjInfo(obj, 1)
|
||||
continue
|
||||
}
|
||||
// skip objects currently under retention
|
||||
if rcfg.LockEnabled && enforceRetentionForDeletion(ctx, obj) {
|
||||
continue
|
||||
}
|
||||
scorer.addFileWithObjInfo(obj, 1)
|
||||
}
|
||||
|
||||
// If we saw less than quota we are good.
|
||||
if scorer.seenBytes <= cfg.Quota {
|
||||
return
|
||||
}
|
||||
// Calculate how much we want to delete now.
|
||||
toFreeNow := scorer.seenBytes - cfg.Quota
|
||||
// We were less over quota than we thought. Adjust so we delete less.
|
||||
// If we are more over, leave it for the next run to pick up.
|
||||
if toFreeNow < toFree {
|
||||
if !scorer.adjustSaveBytes(int64(toFreeNow) - int64(toFree)) {
|
||||
// We got below or at quota.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var objects []ObjectToDelete
|
||||
numKeys := len(scorer.fileObjInfos())
|
||||
for i, obj := range scorer.fileObjInfos() {
|
||||
objects = append(objects, ObjectToDelete{
|
||||
ObjectName: obj.Name,
|
||||
VersionID: obj.VersionID,
|
||||
})
|
||||
if len(objects) < maxDeleteList && (i < numKeys-1) {
|
||||
// skip deletion until maxDeleteList or end of slice
|
||||
continue
|
||||
}
|
||||
|
||||
var objects []ObjectToDelete
|
||||
numKeys := len(scorer.fileObjInfos())
|
||||
for i, obj := range scorer.fileObjInfos() {
|
||||
objects = append(objects, ObjectToDelete{
|
||||
ObjectName: obj.Name,
|
||||
VersionID: obj.VersionID,
|
||||
})
|
||||
if len(objects) < maxDeleteList && (i < numKeys-1) {
|
||||
// skip deletion until maxDeleteList or end of slice
|
||||
if len(objects) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Deletes a list of objects.
|
||||
_, deleteErrs := objectAPI.DeleteObjects(ctx, bucket, objects, ObjectOptions{
|
||||
Versioned: versioned,
|
||||
})
|
||||
for i := range deleteErrs {
|
||||
if deleteErrs[i] != nil {
|
||||
logger.LogIf(ctx, deleteErrs[i])
|
||||
continue
|
||||
}
|
||||
|
||||
if len(objects) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Deletes a list of objects.
|
||||
_, deleteErrs := objectAPI.DeleteObjects(ctx, bucket, objects, ObjectOptions{
|
||||
Versioned: versioned,
|
||||
// Notify object deleted event.
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.ObjectRemovedDelete,
|
||||
BucketName: bucket,
|
||||
Object: obj,
|
||||
Host: "Internal: [FIFO-QUOTA-EXPIRY]",
|
||||
})
|
||||
for i := range deleteErrs {
|
||||
if deleteErrs[i] != nil {
|
||||
logger.LogIf(ctx, deleteErrs[i])
|
||||
continue
|
||||
}
|
||||
|
||||
// Notify object deleted event.
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.ObjectRemovedDelete,
|
||||
BucketName: bucket,
|
||||
Object: obj,
|
||||
Host: "Internal: [FIFO-QUOTA-EXPIRY]",
|
||||
})
|
||||
}
|
||||
objects = nil
|
||||
}
|
||||
objects = nil
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import (
|
||||
|
||||
dns2 "github.com/miekg/dns"
|
||||
"github.com/minio/cli"
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/config/cache"
|
||||
@@ -2506,7 +2507,7 @@ func checkConfigVersion(objAPI ObjectLayer, configFile string, version string) (
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if globalConfigEncrypted {
|
||||
if globalConfigEncrypted && !utf8.Valid(data) {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
if err == madmin.ErrMaliciousData {
|
||||
|
||||
+4
-3
@@ -23,6 +23,7 @@ import (
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
@@ -63,7 +64,7 @@ func listServerConfigHistory(ctx context.Context, objAPI ObjectLayer, withData b
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if globalConfigEncrypted {
|
||||
if globalConfigEncrypted && !utf8.Valid(data) {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -102,7 +103,7 @@ func readServerConfigHistory(ctx context.Context, objAPI ObjectLayer, uuidKV str
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if globalConfigEncrypted {
|
||||
if globalConfigEncrypted && !utf8.Valid(data) {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
}
|
||||
|
||||
@@ -155,7 +156,7 @@ func readServerConfig(ctx context.Context, objAPI ObjectLayer) (config.Config, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if globalConfigEncrypted {
|
||||
if globalConfigEncrypted && !utf8.Valid(configData) {
|
||||
configData, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(configData))
|
||||
if err != nil {
|
||||
if err == madmin.ErrMaliciousData {
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/env"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
|
||||
"github.com/coredns/coredns/plugin/etcd/msg"
|
||||
"go.etcd.io/etcd/v3/clientv3"
|
||||
|
||||
@@ -123,8 +123,8 @@ func GetDefaultExpiration(dsecs string) (time.Duration, error) {
|
||||
|
||||
// The duration, in seconds, of the role session.
|
||||
// The value can range from 900 seconds (15 minutes)
|
||||
// to 12 hours.
|
||||
if expirySecs < 900 || expirySecs > 43200 {
|
||||
// up to 7 days.
|
||||
if expirySecs < 900 || expirySecs > 604800 {
|
||||
return 0, auth.ErrInvalidDuration
|
||||
}
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ func TestDefaultExpiryDuration(t *testing.T) {
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
reqURL: "http://localhost:8443/?DurationSeconds=43201",
|
||||
reqURL: "http://localhost:8443/?DurationSeconds=604801",
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -335,6 +335,21 @@ func (d *dataUsageCache) bucketsUsageInfo(buckets []BucketInfo) map[string]Bucke
|
||||
return dst
|
||||
}
|
||||
|
||||
// bucketUsageInfo returns the buckets usage info.
|
||||
// If not found all values returned are zero values.
|
||||
func (d *dataUsageCache) bucketUsageInfo(bucket string) BucketUsageInfo {
|
||||
e := d.find(bucket)
|
||||
if e == nil {
|
||||
return BucketUsageInfo{}
|
||||
}
|
||||
flat := d.flatten(*e)
|
||||
return BucketUsageInfo{
|
||||
Size: uint64(flat.Size),
|
||||
ObjectsCount: uint64(flat.Objects),
|
||||
ObjectSizesHistogram: flat.ObjSizes.toMap(),
|
||||
}
|
||||
}
|
||||
|
||||
// sizeRecursive returns the path as a flattened entry.
|
||||
func (d *dataUsageCache) sizeRecursive(path string) *dataUsageEntry {
|
||||
root := d.find(path)
|
||||
|
||||
@@ -916,7 +916,7 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang
|
||||
// case of incomplete read.
|
||||
pipeCloser := func() { pr.Close() }
|
||||
|
||||
gr, gerr := fn(pr, h, opts.CheckCopyPrecondFn, pipeCloser)
|
||||
gr, gerr := fn(pr, h, opts.CheckPrecondFn, pipeCloser)
|
||||
if gerr != nil {
|
||||
return gr, numHits, gerr
|
||||
}
|
||||
|
||||
@@ -295,6 +295,7 @@ type fileScorer struct {
|
||||
// The list is kept sorted according to score, highest at top, lowest at bottom.
|
||||
queue list.List
|
||||
queuedBytes uint64
|
||||
seenBytes uint64
|
||||
}
|
||||
|
||||
type queuedFile struct {
|
||||
@@ -337,6 +338,7 @@ func (f *fileScorer) addFileWithObjInfo(objInfo ObjectInfo, hits int) {
|
||||
versionID: objInfo.VersionID,
|
||||
size: uint64(objInfo.Size),
|
||||
}
|
||||
f.seenBytes += uint64(objInfo.Size)
|
||||
|
||||
var score float64
|
||||
if objInfo.ModTime.IsZero() {
|
||||
@@ -369,9 +371,14 @@ func (f *fileScorer) addFileWithObjInfo(objInfo ObjectInfo, hits int) {
|
||||
|
||||
// adjustSaveBytes allows to adjust the number of bytes to save.
|
||||
// This can be used to adjust the count on the fly.
|
||||
// Returns true if there still is a need to delete files (saveBytes >0),
|
||||
// Returns true if there still is a need to delete files (n+saveBytes >0),
|
||||
// false if no more bytes needs to be saved.
|
||||
func (f *fileScorer) adjustSaveBytes(n int64) bool {
|
||||
if int64(f.saveBytes)+n <= 0 {
|
||||
f.saveBytes = 0
|
||||
f.trimQueue()
|
||||
return false
|
||||
}
|
||||
if n < 0 {
|
||||
f.saveBytes -= ^uint64(n - 1)
|
||||
} else {
|
||||
|
||||
+29
-43
@@ -613,7 +613,7 @@ func getDecryptedETag(headers http.Header, objInfo ObjectInfo, copySource bool)
|
||||
// As per AWS S3 Spec, ETag for SSE-C encrypted objects need not be MD5Sum of the data.
|
||||
// Since server side copy with same source and dest just replaces the ETag, we save
|
||||
// encrypted content MD5Sum as ETag for both SSE-C and SSE-S3, we standardize the ETag
|
||||
//encryption across SSE-C and SSE-S3, and only return last 32 bytes for SSE-C
|
||||
// encryption across SSE-C and SSE-S3, and only return last 32 bytes for SSE-C
|
||||
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) && !copySource {
|
||||
return objInfo.ETag[len(objInfo.ETag)-32:]
|
||||
}
|
||||
@@ -777,34 +777,6 @@ func (o *ObjectInfo) EncryptedSize() int64 {
|
||||
return int64(size)
|
||||
}
|
||||
|
||||
// DecryptCopyObjectInfo tries to decrypt the provided object if it is encrypted.
|
||||
// It fails if the object is encrypted and the HTTP headers don't contain
|
||||
// SSE-C headers or the object is not encrypted but SSE-C headers are provided. (AWS behavior)
|
||||
// DecryptObjectInfo returns 'ErrNone' if the object is not encrypted or the
|
||||
// decryption succeeded.
|
||||
//
|
||||
// DecryptCopyObjectInfo also returns whether the object is encrypted or not.
|
||||
func DecryptCopyObjectInfo(info *ObjectInfo, headers http.Header) (errCode APIErrorCode, encrypted bool) {
|
||||
// Directories are never encrypted.
|
||||
if info.IsDir {
|
||||
return ErrNone, false
|
||||
}
|
||||
if errCode, encrypted = ErrNone, crypto.IsEncrypted(info.UserDefined); !encrypted && crypto.SSECopy.IsRequested(headers) {
|
||||
errCode = ErrInvalidEncryptionParameters
|
||||
} else if encrypted {
|
||||
if (!crypto.SSECopy.IsRequested(headers) && crypto.SSEC.IsEncrypted(info.UserDefined)) ||
|
||||
(crypto.SSECopy.IsRequested(headers) && crypto.S3.IsEncrypted(info.UserDefined)) {
|
||||
errCode = ErrSSEEncryptedObject
|
||||
return
|
||||
}
|
||||
var err error
|
||||
if info.Size, err = info.DecryptedSize(); err != nil {
|
||||
errCode = toAPIErrorCode(GlobalContext, err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DecryptObjectInfo tries to decrypt the provided object if it is encrypted.
|
||||
// It fails if the object is encrypted and the HTTP headers don't contain
|
||||
// SSE-C headers or the object is not encrypted but SSE-C headers are provided. (AWS behavior)
|
||||
@@ -812,32 +784,46 @@ func DecryptCopyObjectInfo(info *ObjectInfo, headers http.Header) (errCode APIEr
|
||||
// decryption succeeded.
|
||||
//
|
||||
// DecryptObjectInfo also returns whether the object is encrypted or not.
|
||||
func DecryptObjectInfo(info *ObjectInfo, headers http.Header) (encrypted bool, err error) {
|
||||
func DecryptObjectInfo(info *ObjectInfo, r *http.Request) (encrypted bool, err error) {
|
||||
// Directories are never encrypted.
|
||||
if info.IsDir {
|
||||
return false, nil
|
||||
}
|
||||
// disallow X-Amz-Server-Side-Encryption header on HEAD and GET
|
||||
if crypto.S3.IsRequested(headers) {
|
||||
err = errInvalidEncryptionParameters
|
||||
return
|
||||
if r == nil {
|
||||
return false, errInvalidArgument
|
||||
}
|
||||
if err, encrypted = nil, crypto.IsEncrypted(info.UserDefined); !encrypted && crypto.SSEC.IsRequested(headers) {
|
||||
err = errInvalidEncryptionParameters
|
||||
} else if encrypted {
|
||||
|
||||
headers := r.Header
|
||||
|
||||
// disallow X-Amz-Server-Side-Encryption header on HEAD and GET
|
||||
switch r.Method {
|
||||
case http.MethodGet, http.MethodHead:
|
||||
if crypto.S3.IsRequested(headers) {
|
||||
return false, errInvalidEncryptionParameters
|
||||
}
|
||||
}
|
||||
|
||||
encrypted = crypto.IsEncrypted(info.UserDefined)
|
||||
if !encrypted && crypto.SSEC.IsRequested(headers) {
|
||||
return false, errInvalidEncryptionParameters
|
||||
}
|
||||
|
||||
if encrypted {
|
||||
if (crypto.SSEC.IsEncrypted(info.UserDefined) && !crypto.SSEC.IsRequested(headers)) ||
|
||||
(crypto.S3.IsEncrypted(info.UserDefined) && crypto.SSEC.IsRequested(headers)) {
|
||||
err = errEncryptedObject
|
||||
return
|
||||
return encrypted, errEncryptedObject
|
||||
}
|
||||
|
||||
if _, err = info.DecryptedSize(); err != nil {
|
||||
return encrypted, err
|
||||
}
|
||||
_, err = info.DecryptedSize()
|
||||
|
||||
if crypto.IsEncrypted(info.UserDefined) && !crypto.IsMultiPart(info.UserDefined) {
|
||||
info.ETag = getDecryptedETag(headers, *info, false)
|
||||
info.ETag = getDecryptedETag(headers, *info, headers.Get(crypto.SSECopyAlgorithm) != "")
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
|
||||
return encrypted, nil
|
||||
}
|
||||
|
||||
// The customer key in the header is used by the gateway for encryption in the case of
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"testing"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/minio-go/v6/pkg/encrypt"
|
||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
"github.com/minio/sio"
|
||||
)
|
||||
@@ -80,44 +80,49 @@ func TestEncryptRequest(t *testing.T) {
|
||||
|
||||
var decryptObjectInfoTests = []struct {
|
||||
info ObjectInfo
|
||||
headers http.Header
|
||||
request *http.Request
|
||||
expErr error
|
||||
}{
|
||||
{
|
||||
info: ObjectInfo{Size: 100},
|
||||
headers: http.Header{},
|
||||
request: &http.Request{Header: http.Header{}},
|
||||
expErr: nil,
|
||||
},
|
||||
{
|
||||
info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}},
|
||||
headers: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}},
|
||||
request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}},
|
||||
expErr: nil,
|
||||
},
|
||||
{
|
||||
info: ObjectInfo{Size: 0, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}},
|
||||
headers: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}},
|
||||
request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}},
|
||||
expErr: nil,
|
||||
},
|
||||
{
|
||||
info: ObjectInfo{Size: 100, UserDefined: map[string]string{crypto.SSECSealedKey: "EAAfAAAAAAD7v1hQq3PFRUHsItalxmrJqrOq6FwnbXNarxOOpb8jTWONPPKyM3Gfjkjyj6NCf+aB/VpHCLCTBA=="}},
|
||||
headers: http.Header{},
|
||||
request: &http.Request{Header: http.Header{}},
|
||||
expErr: errEncryptedObject,
|
||||
},
|
||||
{
|
||||
info: ObjectInfo{Size: 100, UserDefined: map[string]string{}},
|
||||
headers: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}},
|
||||
request: &http.Request{Method: http.MethodGet, Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}},
|
||||
expErr: errInvalidEncryptionParameters,
|
||||
},
|
||||
{
|
||||
info: ObjectInfo{Size: 100, UserDefined: map[string]string{}},
|
||||
request: &http.Request{Method: http.MethodHead, Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}},
|
||||
expErr: errInvalidEncryptionParameters,
|
||||
},
|
||||
{
|
||||
info: ObjectInfo{Size: 31, UserDefined: map[string]string{crypto.SSESealAlgorithm: crypto.InsecureSealAlgorithm}},
|
||||
headers: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}},
|
||||
request: &http.Request{Header: http.Header{crypto.SSECAlgorithm: []string{crypto.SSEAlgorithmAES256}}},
|
||||
expErr: errObjectTampered,
|
||||
},
|
||||
}
|
||||
|
||||
func TestDecryptObjectInfo(t *testing.T) {
|
||||
for i, test := range decryptObjectInfoTests {
|
||||
if encrypted, err := DecryptObjectInfo(&test.info, test.headers); err != test.expErr {
|
||||
if encrypted, err := DecryptObjectInfo(&test.info, test.request); err != test.expErr {
|
||||
t.Errorf("Test %d: Decryption returned wrong error code: got %d , want %d", i, err, test.expErr)
|
||||
} else if enc := crypto.IsEncrypted(test.info.UserDefined); encrypted && enc != encrypted {
|
||||
t.Errorf("Test %d: Decryption thinks object is encrypted but it is not", i)
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/pkg/ellipses"
|
||||
"github.com/minio/minio/pkg/env"
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ import (
|
||||
"time"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/cmd/rest"
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
)
|
||||
|
||||
func TestNewEndpoint(t *testing.T) {
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/sync/errgroup"
|
||||
)
|
||||
|
||||
+18
-3
@@ -204,7 +204,7 @@ func shouldHealObjectOnDisk(erErr, dataErr error, meta FileInfo, quorumModTime t
|
||||
return true
|
||||
}
|
||||
if erErr == nil {
|
||||
// If er.meta was read fine but there may be problem with the part.N files.
|
||||
// If xl.meta was read fine but there may be problem with the part.N files.
|
||||
if IsErr(dataErr, []error{
|
||||
errFileNotFound,
|
||||
errFileVersionNotFound,
|
||||
@@ -212,6 +212,9 @@ func shouldHealObjectOnDisk(erErr, dataErr error, meta FileInfo, quorumModTime t
|
||||
}...) {
|
||||
return true
|
||||
}
|
||||
if meta.XLV1 {
|
||||
return true
|
||||
}
|
||||
if !quorumModTime.Equal(meta.ModTime) {
|
||||
return true
|
||||
}
|
||||
@@ -356,6 +359,7 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
||||
|
||||
// We write at temporary location and then rename to final location.
|
||||
tmpID := mustGetUUID()
|
||||
migrateDataDir := mustGetUUID()
|
||||
|
||||
for i := range outDatedDisks {
|
||||
if outDatedDisks[i] == nil {
|
||||
@@ -396,6 +400,9 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
||||
}
|
||||
checksumInfo := partsMetadata[i].Erasure.GetChecksumInfo(partNumber)
|
||||
partPath := pathJoin(object, latestMeta.DataDir, fmt.Sprintf("part.%d", partNumber))
|
||||
if latestMeta.XLV1 {
|
||||
partPath = pathJoin(object, fmt.Sprintf("part.%d", partNumber))
|
||||
}
|
||||
readers[i] = newBitrotReader(disk, bucket, partPath, tillOffset, checksumAlgo, checksumInfo.Hash, erasure.ShardSize())
|
||||
}
|
||||
writers := make([]io.Writer, len(outDatedDisks))
|
||||
@@ -404,6 +411,9 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
||||
continue
|
||||
}
|
||||
partPath := pathJoin(tmpID, latestMeta.DataDir, fmt.Sprintf("part.%d", partNumber))
|
||||
if latestMeta.XLV1 {
|
||||
partPath = pathJoin(tmpID, migrateDataDir, fmt.Sprintf("part.%d", partNumber))
|
||||
}
|
||||
writers[i] = newBitrotWriter(disk, minioMetaTmpBucket, partPath, tillOffset, DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
}
|
||||
err = erasure.Heal(ctx, readers, writers, partSize)
|
||||
@@ -427,6 +437,9 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
||||
continue
|
||||
}
|
||||
|
||||
if partsMetadata[i].XLV1 {
|
||||
partsMetadata[i].DataDir = migrateDataDir
|
||||
}
|
||||
partsMetadata[i].AddObjectPart(partNumber, "", partSize, partActualSize)
|
||||
partsMetadata[i].Erasure.AddChecksumInfo(ChecksumInfo{
|
||||
PartNumber: partNumber,
|
||||
@@ -458,8 +471,10 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
||||
}
|
||||
|
||||
// Attempt a rename now from healed data to final location.
|
||||
if err = disk.RenameData(minioMetaTmpBucket, tmpID, latestMeta.DataDir, bucket, object); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
if err = disk.RenameData(minioMetaTmpBucket, tmpID, partsMetadata[0].DataDir, bucket, object); err != nil {
|
||||
if err != errIsNotRegular && err != errFileNotFound {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
return result, toObjectErr(err, bucket, object)
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/mimedb"
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/tags"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/mimedb"
|
||||
@@ -173,7 +173,7 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
|
||||
// case of incomplete read.
|
||||
pipeCloser := func() { pr.Close() }
|
||||
|
||||
return fn(pr, h, opts.CheckCopyPrecondFn, pipeCloser)
|
||||
return fn(pr, h, opts.CheckPrecondFn, pipeCloser)
|
||||
}
|
||||
|
||||
// GetObject - reads an object erasured coded across multiple
|
||||
|
||||
+13
-67
@@ -28,7 +28,7 @@ import (
|
||||
|
||||
"github.com/dchest/siphash"
|
||||
"github.com/google/uuid"
|
||||
"github.com/minio/minio-go/v6/pkg/tags"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/cmd/config/storageclass"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/bpool"
|
||||
@@ -204,14 +204,14 @@ func (s *erasureSets) connectDisks() {
|
||||
defer wg.Done()
|
||||
disk, format, err := connectEndpoint(endpoint)
|
||||
if err != nil {
|
||||
printEndpointError(endpoint, err)
|
||||
printEndpointError(endpoint, err, true)
|
||||
return
|
||||
}
|
||||
setIndex, diskIndex, err := findDiskIndex(s.format, format)
|
||||
if err != nil {
|
||||
// Close the internal connection to avoid connection leaks.
|
||||
disk.Close()
|
||||
printEndpointError(endpoint, err)
|
||||
printEndpointError(endpoint, err, false)
|
||||
return
|
||||
}
|
||||
disk.SetDiskID(format.Erasure.This)
|
||||
@@ -395,10 +395,7 @@ func (s *erasureSets) StorageUsageInfo(ctx context.Context) StorageInfo {
|
||||
g.Wait()
|
||||
|
||||
for _, lstorageInfo := range storageInfos {
|
||||
storageInfo.Used = append(storageInfo.Used, lstorageInfo.Used...)
|
||||
storageInfo.Total = append(storageInfo.Total, lstorageInfo.Total...)
|
||||
storageInfo.Available = append(storageInfo.Available, lstorageInfo.Available...)
|
||||
storageInfo.MountPaths = append(storageInfo.MountPaths, lstorageInfo.MountPaths...)
|
||||
storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...)
|
||||
storageInfo.Backend.OnlineDisks = storageInfo.Backend.OnlineDisks.Merge(lstorageInfo.Backend.OnlineDisks)
|
||||
storageInfo.Backend.OfflineDisks = storageInfo.Backend.OfflineDisks.Merge(lstorageInfo.Backend.OfflineDisks)
|
||||
}
|
||||
@@ -438,10 +435,7 @@ func (s *erasureSets) StorageInfo(ctx context.Context, local bool) (StorageInfo,
|
||||
g.Wait()
|
||||
|
||||
for _, lstorageInfo := range storageInfos {
|
||||
storageInfo.Used = append(storageInfo.Used, lstorageInfo.Used...)
|
||||
storageInfo.Total = append(storageInfo.Total, lstorageInfo.Total...)
|
||||
storageInfo.Available = append(storageInfo.Available, lstorageInfo.Available...)
|
||||
storageInfo.MountPaths = append(storageInfo.MountPaths, lstorageInfo.MountPaths...)
|
||||
storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...)
|
||||
storageInfo.Backend.OnlineDisks = storageInfo.Backend.OnlineDisks.Merge(lstorageInfo.Backend.OnlineDisks)
|
||||
storageInfo.Backend.OfflineDisks = storageInfo.Backend.OfflineDisks.Merge(lstorageInfo.Backend.OfflineDisks)
|
||||
}
|
||||
@@ -457,57 +451,12 @@ func (s *erasureSets) StorageInfo(ctx context.Context, local bool) (StorageInfo,
|
||||
storageInfo.Backend.RRSCData = s.drivesPerSet - rrSCParity
|
||||
storageInfo.Backend.RRSCParity = rrSCParity
|
||||
|
||||
storageInfo.Backend.Sets = make([][]madmin.DriveInfo, s.setCount)
|
||||
for i := range storageInfo.Backend.Sets {
|
||||
storageInfo.Backend.Sets[i] = make([]madmin.DriveInfo, s.drivesPerSet)
|
||||
}
|
||||
|
||||
if local {
|
||||
// if local is true, we are not interested in the drive UUID info.
|
||||
// this is called primarily by prometheus
|
||||
return storageInfo, nil
|
||||
}
|
||||
|
||||
for i, set := range s.sets {
|
||||
storageDisks := set.getDisks()
|
||||
endpointStrings := set.getEndpoints()
|
||||
for j, storageErr := range storageInfoErrs[i] {
|
||||
if storageDisks[j] == OfflineDisk {
|
||||
storageInfo.Backend.Sets[i][j] = madmin.DriveInfo{
|
||||
State: madmin.DriveStateOffline,
|
||||
Endpoint: endpointStrings[j],
|
||||
}
|
||||
continue
|
||||
}
|
||||
var diskID string
|
||||
if storageErr == nil {
|
||||
// No errors returned by storage, look for its DiskID()
|
||||
diskID, storageErr = storageDisks[j].GetDiskID()
|
||||
}
|
||||
if storageErr == nil {
|
||||
storageInfo.Backend.Sets[i][j] = madmin.DriveInfo{
|
||||
State: madmin.DriveStateOk,
|
||||
Endpoint: storageDisks[j].String(),
|
||||
UUID: diskID,
|
||||
}
|
||||
continue
|
||||
}
|
||||
if storageErr == errUnformattedDisk {
|
||||
storageInfo.Backend.Sets[i][j] = madmin.DriveInfo{
|
||||
State: madmin.DriveStateUnformatted,
|
||||
Endpoint: storageDisks[j].String(),
|
||||
UUID: "",
|
||||
}
|
||||
} else {
|
||||
storageInfo.Backend.Sets[i][j] = madmin.DriveInfo{
|
||||
State: madmin.DriveStateCorrupt,
|
||||
Endpoint: storageDisks[j].String(),
|
||||
UUID: "",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for i := range s.sets {
|
||||
errs = append(errs, storageInfoErrs[i]...)
|
||||
@@ -1195,8 +1144,8 @@ else
|
||||
fi
|
||||
*/
|
||||
|
||||
func formatsToDrivesInfo(endpoints Endpoints, formats []*formatErasureV3, sErrs []error) (beforeDrives []madmin.DriveInfo) {
|
||||
beforeDrives = make([]madmin.DriveInfo, len(endpoints))
|
||||
func formatsToDrivesInfo(endpoints Endpoints, formats []*formatErasureV3, sErrs []error) (beforeDrives []madmin.HealDriveInfo) {
|
||||
beforeDrives = make([]madmin.HealDriveInfo, len(endpoints))
|
||||
// Existing formats are available (i.e. ok), so save it in
|
||||
// result, also populate disks to be healed.
|
||||
for i, format := range formats {
|
||||
@@ -1210,7 +1159,7 @@ func formatsToDrivesInfo(endpoints Endpoints, formats []*formatErasureV3, sErrs
|
||||
case sErrs[i] == errDiskNotFound:
|
||||
state = madmin.DriveStateOffline
|
||||
}
|
||||
beforeDrives[i] = madmin.DriveInfo{
|
||||
beforeDrives[i] = madmin.HealDriveInfo{
|
||||
UUID: func() string {
|
||||
if format != nil {
|
||||
return format.Erasure.This
|
||||
@@ -1307,8 +1256,9 @@ func (s *erasureSets) ReloadFormat(ctx context.Context, dryRun bool) (err error)
|
||||
func isTestSetup(infos []DiskInfo, errs []error) bool {
|
||||
rootDiskCount := 0
|
||||
for i := range errs {
|
||||
if errs[i] != nil {
|
||||
// On error it is safer to assume that this is not a test setup.
|
||||
if errs[i] != nil && errs[i] != errUnformattedDisk {
|
||||
// On any error which is not unformatted disk
|
||||
// it is safer to reject healing.
|
||||
return false
|
||||
}
|
||||
if infos[i].RootDisk {
|
||||
@@ -1319,7 +1269,7 @@ func isTestSetup(infos []DiskInfo, errs []error) bool {
|
||||
return rootDiskCount == len(infos)
|
||||
}
|
||||
|
||||
func getAllDiskInfos(storageDisks []StorageAPI) ([]DiskInfo, []error) {
|
||||
func getHealDiskInfos(storageDisks []StorageAPI) ([]DiskInfo, []error) {
|
||||
infos := make([]DiskInfo, len(storageDisks))
|
||||
g := errgroup.WithNErrs(len(storageDisks))
|
||||
for index := range storageDisks {
|
||||
@@ -1340,16 +1290,12 @@ func getAllDiskInfos(storageDisks []StorageAPI) ([]DiskInfo, []error) {
|
||||
|
||||
// Mark root disks as down so as not to heal them.
|
||||
func markRootDisksAsDown(storageDisks []StorageAPI) {
|
||||
infos, errs := getAllDiskInfos(storageDisks)
|
||||
infos, errs := getHealDiskInfos(storageDisks)
|
||||
if isTestSetup(infos, errs) {
|
||||
// Allow healing of disks for test setups to help with testing.
|
||||
return
|
||||
}
|
||||
for i := range storageDisks {
|
||||
if errs[i] != nil {
|
||||
storageDisks[i] = nil
|
||||
continue
|
||||
}
|
||||
if infos[i].RootDisk {
|
||||
// We should not heal on root disk. i.e in a situation where the minio-administrator has unmounted a
|
||||
// defective drive we should not heal a path on the root disk.
|
||||
|
||||
+28
-32
@@ -26,8 +26,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v6/pkg/tags"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/cmd/config/storageclass"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
@@ -149,12 +149,10 @@ func (z *erasureZones) getZonesAvailableSpace(ctx context.Context, size int64) z
|
||||
|
||||
for i, zinfo := range storageInfos {
|
||||
var available uint64
|
||||
for _, davailable := range zinfo.Available {
|
||||
available += davailable
|
||||
}
|
||||
var total uint64
|
||||
for _, dtotal := range zinfo.Total {
|
||||
total += dtotal
|
||||
for _, disk := range zinfo.Disks {
|
||||
total += disk.TotalSpace
|
||||
available += disk.TotalSpace - disk.UsedSpace
|
||||
}
|
||||
// Make sure we can fit "size" on to the disk without getting above the diskFillFraction
|
||||
if available < uint64(size) {
|
||||
@@ -260,13 +258,9 @@ func (z *erasureZones) StorageInfo(ctx context.Context, local bool) (StorageInfo
|
||||
g.Wait()
|
||||
|
||||
for _, lstorageInfo := range storageInfos {
|
||||
storageInfo.Used = append(storageInfo.Used, lstorageInfo.Used...)
|
||||
storageInfo.Total = append(storageInfo.Total, lstorageInfo.Total...)
|
||||
storageInfo.Available = append(storageInfo.Available, lstorageInfo.Available...)
|
||||
storageInfo.MountPaths = append(storageInfo.MountPaths, lstorageInfo.MountPaths...)
|
||||
storageInfo.Disks = append(storageInfo.Disks, lstorageInfo.Disks...)
|
||||
storageInfo.Backend.OnlineDisks = storageInfo.Backend.OnlineDisks.Merge(lstorageInfo.Backend.OnlineDisks)
|
||||
storageInfo.Backend.OfflineDisks = storageInfo.Backend.OfflineDisks.Merge(lstorageInfo.Backend.OfflineDisks)
|
||||
storageInfo.Backend.Sets = append(storageInfo.Backend.Sets, lstorageInfo.Backend.Sets...)
|
||||
}
|
||||
|
||||
storageInfo.Backend.Type = storageInfos[0].Backend.Type
|
||||
@@ -343,13 +337,15 @@ func (z *erasureZones) CrawlAndGetDataUsage(ctx context.Context, bf *bloomFilter
|
||||
updateTicker := time.NewTicker(30 * time.Second)
|
||||
defer updateTicker.Stop()
|
||||
var lastUpdate time.Time
|
||||
|
||||
// We need to merge since we will get the same buckets from each zone.
|
||||
// Therefore to get the exact bucket sizes we must merge before we can convert.
|
||||
allMerged := dataUsageCache{Info: dataUsageCacheInfo{Name: dataUsageRoot}}
|
||||
|
||||
update := func() {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
// We need to merge since we will get the same buckets from each zone.
|
||||
// Therefore to get the exact bucket sizes we must merge before we can convert.
|
||||
allMerged := dataUsageCache{Info: dataUsageCacheInfo{Name: dataUsageRoot}}
|
||||
for _, info := range results {
|
||||
if info.Info.LastUpdate.IsZero() {
|
||||
// Not filled yet.
|
||||
@@ -368,6 +364,10 @@ func (z *erasureZones) CrawlAndGetDataUsage(ctx context.Context, bf *bloomFilter
|
||||
return
|
||||
case v := <-updateCloser:
|
||||
update()
|
||||
// Enforce quotas when all is done.
|
||||
for _, b := range allBuckets {
|
||||
enforceFIFOQuotaBucket(ctx, z, b.Name, allMerged.bucketUsageInfo(b.Name))
|
||||
}
|
||||
close(v)
|
||||
return
|
||||
case <-updateTicker.C:
|
||||
@@ -681,10 +681,9 @@ func (z *erasureZones) listObjectsNonSlash(ctx context.Context, bucket, prefix,
|
||||
endWalkCh := make(chan struct{})
|
||||
defer close(endWalkCh)
|
||||
|
||||
const ndisks = 3
|
||||
for _, zone := range z.zones {
|
||||
zonesEntryChs = append(zonesEntryChs,
|
||||
zone.startMergeWalksN(ctx, bucket, prefix, "", true, endWalkCh, ndisks))
|
||||
zone.startMergeWalksN(ctx, bucket, prefix, "", true, endWalkCh, zone.drivesPerSet))
|
||||
}
|
||||
|
||||
var objInfos []ObjectInfo
|
||||
@@ -709,8 +708,8 @@ func (z *erasureZones) listObjectsNonSlash(ctx context.Context, bucket, prefix,
|
||||
break
|
||||
}
|
||||
|
||||
if quorumCount < ndisks-1 {
|
||||
// Skip entries which are not found on upto ndisks.
|
||||
if quorumCount < z.zones[0].drivesPerSet/2 {
|
||||
// Skip entries which are not found on upto ndisks/2.
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -797,18 +796,17 @@ func (z *erasureZones) listObjectsSplunk(ctx context.Context, bucket, prefix, ma
|
||||
var zonesEntryChs [][]FileInfoCh
|
||||
var zonesEndWalkCh []chan struct{}
|
||||
|
||||
const ndisks = 3
|
||||
for _, zone := range z.zones {
|
||||
entryChs, endWalkCh := zone.poolSplunk.Release(listParams{bucket, recursive, marker, prefix})
|
||||
if entryChs == nil {
|
||||
endWalkCh = make(chan struct{})
|
||||
entryChs = zone.startSplunkMergeWalksN(ctx, bucket, prefix, marker, endWalkCh, ndisks)
|
||||
entryChs = zone.startSplunkMergeWalksN(ctx, bucket, prefix, marker, endWalkCh, zone.drivesPerSet)
|
||||
}
|
||||
zonesEntryChs = append(zonesEntryChs, entryChs)
|
||||
zonesEndWalkCh = append(zonesEndWalkCh, endWalkCh)
|
||||
}
|
||||
|
||||
entries := mergeZonesEntriesCh(zonesEntryChs, maxKeys, ndisks)
|
||||
entries := mergeZonesEntriesCh(zonesEntryChs, maxKeys, z.zones[0].drivesPerSet)
|
||||
if len(entries.Files) == 0 {
|
||||
return loi, nil
|
||||
}
|
||||
@@ -888,18 +886,17 @@ func (z *erasureZones) listObjects(ctx context.Context, bucket, prefix, marker,
|
||||
var zonesEntryChs [][]FileInfoCh
|
||||
var zonesEndWalkCh []chan struct{}
|
||||
|
||||
const ndisks = 3
|
||||
for _, zone := range z.zones {
|
||||
entryChs, endWalkCh := zone.pool.Release(listParams{bucket, recursive, marker, prefix})
|
||||
if entryChs == nil {
|
||||
endWalkCh = make(chan struct{})
|
||||
entryChs = zone.startMergeWalksN(ctx, bucket, prefix, marker, recursive, endWalkCh, ndisks)
|
||||
entryChs = zone.startMergeWalksN(ctx, bucket, prefix, marker, recursive, endWalkCh, zone.drivesPerSet)
|
||||
}
|
||||
zonesEntryChs = append(zonesEntryChs, entryChs)
|
||||
zonesEndWalkCh = append(zonesEndWalkCh, endWalkCh)
|
||||
}
|
||||
|
||||
entries := mergeZonesEntriesCh(zonesEntryChs, maxKeys, ndisks)
|
||||
entries := mergeZonesEntriesCh(zonesEntryChs, maxKeys, z.zones[0].drivesPerSet)
|
||||
if len(entries.Files) == 0 {
|
||||
return loi, nil
|
||||
}
|
||||
@@ -1118,8 +1115,8 @@ func mergeZonesEntriesVersionsCh(zonesEntryChs [][]FileInfoVersionsCh, maxKeys i
|
||||
break
|
||||
}
|
||||
|
||||
if quorumCount < ndisks-1 {
|
||||
// Skip entries which are not found on upto ndisks.
|
||||
if quorumCount < ndisks/2 {
|
||||
// Skip entries which are not found on upto ndisks/2.
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1149,8 +1146,8 @@ func mergeZonesEntriesCh(zonesEntryChs [][]FileInfoCh, maxKeys int, ndisks int)
|
||||
break
|
||||
}
|
||||
|
||||
if quorumCount < ndisks-1 {
|
||||
// Skip entries which are not found on upto ndisks.
|
||||
if quorumCount < ndisks/2 {
|
||||
// Skip entries which are not found on upto ndisks/2.
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1292,18 +1289,17 @@ func (z *erasureZones) listObjectVersions(ctx context.Context, bucket, prefix, m
|
||||
var zonesEntryChs [][]FileInfoVersionsCh
|
||||
var zonesEndWalkCh []chan struct{}
|
||||
|
||||
const ndisks = 3
|
||||
for _, zone := range z.zones {
|
||||
entryChs, endWalkCh := zone.poolVersions.Release(listParams{bucket, recursive, marker, prefix})
|
||||
if entryChs == nil {
|
||||
endWalkCh = make(chan struct{})
|
||||
entryChs = zone.startMergeWalksVersionsN(ctx, bucket, prefix, marker, recursive, endWalkCh, ndisks)
|
||||
entryChs = zone.startMergeWalksVersionsN(ctx, bucket, prefix, marker, recursive, endWalkCh, zone.drivesPerSet)
|
||||
}
|
||||
zonesEntryChs = append(zonesEntryChs, entryChs)
|
||||
zonesEndWalkCh = append(zonesEndWalkCh, endWalkCh)
|
||||
}
|
||||
|
||||
entries := mergeZonesEntriesVersionsCh(zonesEntryChs, maxKeys, ndisks)
|
||||
entries := mergeZonesEntriesVersionsCh(zonesEntryChs, maxKeys, z.zones[0].drivesPerSet)
|
||||
if len(entries.FilesVersions) == 0 {
|
||||
return loi, nil
|
||||
}
|
||||
|
||||
+42
-24
@@ -79,17 +79,36 @@ func (er erasureObjects) Shutdown(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// byDiskTotal is a collection satisfying sort.Interface.
|
||||
type byDiskTotal []DiskInfo
|
||||
type byDiskTotal []madmin.Disk
|
||||
|
||||
func (d byDiskTotal) Len() int { return len(d) }
|
||||
func (d byDiskTotal) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
|
||||
func (d byDiskTotal) Less(i, j int) bool {
|
||||
return d[i].Total < d[j].Total
|
||||
return d[i].TotalSpace < d[j].TotalSpace
|
||||
}
|
||||
|
||||
func diskErrToDriveState(err error) (state string) {
|
||||
state = madmin.DriveStateUnknown
|
||||
switch err {
|
||||
case errDiskNotFound:
|
||||
state = madmin.DriveStateOffline
|
||||
case errCorruptedFormat:
|
||||
state = madmin.DriveStateCorrupt
|
||||
case errUnformattedDisk:
|
||||
state = madmin.DriveStateUnformatted
|
||||
case errDiskAccessDenied:
|
||||
state = madmin.DriveStatePermission
|
||||
case errFaultyDisk:
|
||||
state = madmin.DriveStateFaulty
|
||||
case nil:
|
||||
state = madmin.DriveStateOk
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getDisksInfo - fetch disks info across all other storage API.
|
||||
func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []DiskInfo, errs []error, onlineDisks, offlineDisks madmin.BackendDisks) {
|
||||
disksInfo = make([]DiskInfo, len(disks))
|
||||
func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []madmin.Disk, errs []error, onlineDisks, offlineDisks madmin.BackendDisks) {
|
||||
disksInfo = make([]madmin.Disk, len(disks))
|
||||
onlineDisks = make(madmin.BackendDisks)
|
||||
offlineDisks = make(madmin.BackendDisks)
|
||||
|
||||
@@ -107,6 +126,10 @@ func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []DiskInfo,
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
if disks[index] == OfflineDisk {
|
||||
disksInfo[index] = madmin.Disk{
|
||||
State: diskErrToDriveState(errDiskNotFound),
|
||||
Endpoint: endpoints[index],
|
||||
}
|
||||
// Storage disk is empty, perhaps ignored disk or not available.
|
||||
return errDiskNotFound
|
||||
}
|
||||
@@ -117,10 +140,21 @@ func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []DiskInfo,
|
||||
ctx := logger.SetReqInfo(GlobalContext, reqInfo)
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
disksInfo[index] = info
|
||||
return nil
|
||||
di := madmin.Disk{
|
||||
Endpoint: endpoints[index],
|
||||
DrivePath: info.MountPath,
|
||||
TotalSpace: info.Total,
|
||||
UsedSpace: info.Used,
|
||||
AvailableSpace: info.Free,
|
||||
UUID: info.ID,
|
||||
State: diskErrToDriveState(err),
|
||||
}
|
||||
if info.Total > 0 {
|
||||
di.Utilization = float64(info.Used / info.Total * 100)
|
||||
}
|
||||
disksInfo[index] = di
|
||||
return err
|
||||
}, index)
|
||||
}
|
||||
|
||||
@@ -146,24 +180,8 @@ func getStorageInfo(disks []StorageAPI, endpoints []string) (StorageInfo, []erro
|
||||
// Sort so that the first element is the smallest.
|
||||
sort.Sort(byDiskTotal(disksInfo))
|
||||
|
||||
// Combine all disks to get total usage
|
||||
usedList := make([]uint64, len(disksInfo))
|
||||
totalList := make([]uint64, len(disksInfo))
|
||||
availableList := make([]uint64, len(disksInfo))
|
||||
mountPaths := make([]string, len(disksInfo))
|
||||
|
||||
for i, di := range disksInfo {
|
||||
usedList[i] = di.Used
|
||||
totalList[i] = di.Total
|
||||
availableList[i] = di.Free
|
||||
mountPaths[i] = di.MountPath
|
||||
}
|
||||
|
||||
storageInfo := StorageInfo{
|
||||
Used: usedList,
|
||||
Total: totalList,
|
||||
Available: availableList,
|
||||
MountPaths: mountPaths,
|
||||
Disks: disksInfo,
|
||||
}
|
||||
|
||||
storageInfo.Backend.Type = BackendErasure
|
||||
|
||||
@@ -709,6 +709,9 @@ func saveFormatErasureAll(ctx context.Context, storageDisks []StorageAPI, format
|
||||
for index := range storageDisks {
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
if formats[index] == nil {
|
||||
return errDiskNotFound
|
||||
}
|
||||
return saveFormatErasure(storageDisks[index], formats[index], formats[index].Erasure.This)
|
||||
}, index)
|
||||
}
|
||||
|
||||
+12
-14
@@ -33,8 +33,8 @@ import (
|
||||
"time"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v6/pkg/tags"
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
@@ -53,9 +53,6 @@ var defaultEtag = "00000000000000000000000000000000-1"
|
||||
type FSObjects struct {
|
||||
GatewayUnsupported
|
||||
|
||||
// Disk usage metrics
|
||||
totalUsed uint64 // ref: https://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||
|
||||
// The count of concurrent calls on FSObjects API
|
||||
activeIOCount int64
|
||||
// The active IO count ceiling for crawling to work
|
||||
@@ -215,15 +212,15 @@ func (fs *FSObjects) StorageInfo(ctx context.Context, _ bool) (StorageInfo, []er
|
||||
return StorageInfo{}, []error{err}
|
||||
}
|
||||
used := di.Total - di.Free
|
||||
if !fs.diskMount {
|
||||
used = atomic.LoadUint64(&fs.totalUsed)
|
||||
}
|
||||
|
||||
storageInfo := StorageInfo{
|
||||
Used: []uint64{used},
|
||||
Total: []uint64{di.Total},
|
||||
Available: []uint64{di.Free},
|
||||
MountPaths: []string{fs.fsPath},
|
||||
Disks: []madmin.Disk{
|
||||
{
|
||||
TotalSpace: di.Total,
|
||||
UsedSpace: used,
|
||||
AvailableSpace: di.Free,
|
||||
DrivePath: fs.fsPath,
|
||||
},
|
||||
},
|
||||
}
|
||||
storageInfo.Backend.Type = BackendFS
|
||||
return storageInfo, nil
|
||||
@@ -304,6 +301,7 @@ func (fs *FSObjects) CrawlAndGetDataUsage(ctx context.Context, bf *bloomFilter,
|
||||
logger.LogIf(ctx, totalCache.save(ctx, fs, dataUsageCacheName))
|
||||
cloned := totalCache.clone()
|
||||
updates <- cloned.dui(dataUsageRoot, buckets)
|
||||
enforceFIFOQuotaBucket(ctx, fs, b.Name, cloned.bucketUsageInfo(b.Name))
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -754,7 +752,7 @@ func (fs *FSObjects) GetObjectNInfo(ctx context.Context, bucket, object string,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return objReaderFn(reader, h, opts.CheckCopyPrecondFn, closeFn)
|
||||
return objReaderFn(reader, h, opts.CheckPrecondFn, closeFn)
|
||||
}
|
||||
|
||||
// GetObject - reads an object from the disk.
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
xnet "github.com/minio/minio/pkg/net"
|
||||
|
||||
minio "github.com/minio/minio-go/v6"
|
||||
minio "github.com/minio/minio-go/v7"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
+1
-1
@@ -267,7 +267,7 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
||||
}
|
||||
|
||||
httpServer := xhttp.NewServer([]string{globalCLIContext.Addr},
|
||||
criticalErrorHandler{router}, getCert)
|
||||
criticalErrorHandler{corsHandler(router)}, getCert)
|
||||
httpServer.BaseContext = func(listener net.Listener) context.Context {
|
||||
return GlobalContext
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/tags"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
bucketsse "github.com/minio/minio/pkg/bucket/encryption"
|
||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
|
||||
@@ -41,7 +41,7 @@ import (
|
||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/cli"
|
||||
miniogopolicy "github.com/minio/minio-go/v6/pkg/policy"
|
||||
miniogopolicy "github.com/minio/minio-go/v7/pkg/policy"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
@@ -462,7 +462,7 @@ func azureToObjectError(err error, params ...string) error {
|
||||
|
||||
func azureCodesToObjectError(err error, serviceCode string, statusCode int, bucket string, object string) error {
|
||||
switch serviceCode {
|
||||
case "ContainerNotFound":
|
||||
case "ContainerNotFound", "ContainerBeingDeleted":
|
||||
err = minio.BucketNotFound{Bucket: bucket}
|
||||
case "ContainerAlreadyExists":
|
||||
err = minio.BucketExists{Bucket: bucket}
|
||||
@@ -908,7 +908,7 @@ func (a *azureObjects) PutObject(ctx context.Context, bucket, object string, r *
|
||||
// CopyObject - Copies a blob from source container to destination container.
|
||||
// Uses Azure equivalent `BlobURL.StartCopyFromURL`.
|
||||
func (a *azureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, srcInfo minio.ObjectInfo, srcOpts, dstOpts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
|
||||
if srcOpts.CheckCopyPrecondFn != nil && srcOpts.CheckCopyPrecondFn(srcInfo, "") {
|
||||
if srcOpts.CheckPrecondFn != nil && srcOpts.CheckPrecondFn(srcInfo) {
|
||||
return minio.ObjectInfo{}, minio.PreConditionFailed{}
|
||||
}
|
||||
srcBlob := a.client.NewContainerURL(srcBucket).NewBlobURL(srcObject)
|
||||
|
||||
@@ -38,7 +38,7 @@ import (
|
||||
"cloud.google.com/go/storage"
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/cli"
|
||||
miniogopolicy "github.com/minio/minio-go/v6/pkg/policy"
|
||||
miniogopolicy "github.com/minio/minio-go/v7/pkg/policy"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
@@ -940,7 +940,7 @@ func (l *gcsGateway) PutObject(ctx context.Context, bucket string, key string, r
|
||||
// CopyObject - Copies a blob from source container to destination container.
|
||||
func (l *gcsGateway) CopyObject(ctx context.Context, srcBucket string, srcObject string, destBucket string, destObject string,
|
||||
srcInfo minio.ObjectInfo, srcOpts, dstOpts minio.ObjectOptions) (minio.ObjectInfo, error) {
|
||||
if srcOpts.CheckCopyPrecondFn != nil && srcOpts.CheckCopyPrecondFn(srcInfo, "") {
|
||||
if srcOpts.CheckPrecondFn != nil && srcOpts.CheckPrecondFn(srcInfo) {
|
||||
return minio.ObjectInfo{}, minio.PreConditionFailed{}
|
||||
}
|
||||
src := l.client.Bucket(srcBucket).Object(srcObject)
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
"cloud.google.com/go/storage"
|
||||
"google.golang.org/api/googleapi"
|
||||
|
||||
miniogo "github.com/minio/minio-go/v6"
|
||||
miniogo "github.com/minio/minio-go/v7"
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ package hdfs
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
|
||||
|
||||
@@ -34,11 +34,12 @@ import (
|
||||
"github.com/colinmarc/hdfs/v2"
|
||||
"github.com/colinmarc/hdfs/v2/hadoopconf"
|
||||
"github.com/minio/cli"
|
||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
minio "github.com/minio/minio/cmd"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/env"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
xnet "github.com/minio/minio/pkg/net"
|
||||
krb "gopkg.in/jcmturner/gokrb5.v7/client"
|
||||
"gopkg.in/jcmturner/gokrb5.v7/config"
|
||||
@@ -158,6 +159,7 @@ func (g *HDFS) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, error
|
||||
opts.DatanodeDialFunc = dialFunc
|
||||
|
||||
// Not addresses found, load it from command line.
|
||||
var commonPath string
|
||||
if len(opts.Addresses) == 0 {
|
||||
var addresses []string
|
||||
for _, s := range g.args {
|
||||
@@ -165,6 +167,15 @@ func (g *HDFS) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.Scheme != "hdfs" {
|
||||
return nil, fmt.Errorf("unsupported scheme %s, only supports hdfs://", u)
|
||||
}
|
||||
if commonPath != "" && commonPath != u.Path {
|
||||
return nil, fmt.Errorf("all namenode paths should be same %s", g.args)
|
||||
}
|
||||
if commonPath == "" {
|
||||
commonPath = u.Path
|
||||
}
|
||||
addresses = append(addresses, u.Host)
|
||||
}
|
||||
opts.Addresses = addresses
|
||||
@@ -172,13 +183,13 @@ func (g *HDFS) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, error
|
||||
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to lookup local user: %s", err)
|
||||
return nil, fmt.Errorf("unable to lookup local user: %s", err)
|
||||
}
|
||||
|
||||
if opts.KerberosClient != nil {
|
||||
opts.KerberosClient, err = getKerberosClient()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to initialize kerberos client: %s", err)
|
||||
return nil, fmt.Errorf("unable to initialize kerberos client: %s", err)
|
||||
}
|
||||
} else {
|
||||
opts.User = env.Get("HADOOP_USER_NAME", u.Username)
|
||||
@@ -186,14 +197,14 @@ func (g *HDFS) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, error
|
||||
|
||||
clnt, err := hdfs.NewClient(opts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to initialize hdfsClient")
|
||||
}
|
||||
|
||||
if err = clnt.MkdirAll(minio.PathJoin(commonPath, hdfsSeparator, minioMetaTmpBucket), os.FileMode(0755)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = clnt.MkdirAll(minio.PathJoin(hdfsSeparator, minioMetaTmpBucket), os.FileMode(0755)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &hdfsObjects{clnt: clnt, listPool: minio.NewTreeWalkPool(time.Minute * 30)}, nil
|
||||
return &hdfsObjects{clnt: clnt, subPath: commonPath, listPool: minio.NewTreeWalkPool(time.Minute * 30)}, nil
|
||||
}
|
||||
|
||||
// Production - hdfs gateway is production ready.
|
||||
@@ -210,7 +221,9 @@ func (n *hdfsObjects) StorageInfo(ctx context.Context, _ bool) (si minio.Storage
|
||||
if err != nil {
|
||||
return minio.StorageInfo{}, []error{err}
|
||||
}
|
||||
si.Used = []uint64{fsInfo.Used}
|
||||
si.Disks = []madmin.Disk{{
|
||||
UsedSpace: fsInfo.Used,
|
||||
}}
|
||||
si.Backend.Type = minio.BackendGateway
|
||||
si.Backend.GatewayOnline = true
|
||||
return si, nil
|
||||
@@ -220,6 +233,7 @@ func (n *hdfsObjects) StorageInfo(ctx context.Context, _ bool) (si minio.Storage
|
||||
type hdfsObjects struct {
|
||||
minio.GatewayUnsupported
|
||||
clnt *hdfs.Client
|
||||
subPath string
|
||||
listPool *minio.TreeWalkPool
|
||||
}
|
||||
|
||||
@@ -273,14 +287,18 @@ func hdfsIsValidBucketName(bucket string) bool {
|
||||
return s3utils.CheckValidBucketNameStrict(bucket) == nil
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) hdfsPathJoin(args ...string) string {
|
||||
return minio.PathJoin(append([]string{n.subPath, hdfsSeparator}, args...)...)
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) DeleteBucket(ctx context.Context, bucket string, forceDelete bool) error {
|
||||
if !hdfsIsValidBucketName(bucket) {
|
||||
return minio.BucketNameInvalid{Bucket: bucket}
|
||||
}
|
||||
if forceDelete {
|
||||
return hdfsToObjectErr(ctx, n.clnt.RemoveAll(minio.PathJoin(hdfsSeparator, bucket)), bucket)
|
||||
return hdfsToObjectErr(ctx, n.clnt.RemoveAll(n.hdfsPathJoin(bucket)), bucket)
|
||||
}
|
||||
return hdfsToObjectErr(ctx, n.clnt.Remove(minio.PathJoin(hdfsSeparator, bucket)), bucket)
|
||||
return hdfsToObjectErr(ctx, n.clnt.Remove(n.hdfsPathJoin(bucket)), bucket)
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) MakeBucketWithLocation(ctx context.Context, bucket string, opts minio.BucketOptions) error {
|
||||
@@ -291,11 +309,11 @@ func (n *hdfsObjects) MakeBucketWithLocation(ctx context.Context, bucket string,
|
||||
if !hdfsIsValidBucketName(bucket) {
|
||||
return minio.BucketNameInvalid{Bucket: bucket}
|
||||
}
|
||||
return hdfsToObjectErr(ctx, n.clnt.Mkdir(minio.PathJoin(hdfsSeparator, bucket), os.FileMode(0755)), bucket)
|
||||
return hdfsToObjectErr(ctx, n.clnt.Mkdir(n.hdfsPathJoin(bucket), os.FileMode(0755)), bucket)
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) GetBucketInfo(ctx context.Context, bucket string) (bi minio.BucketInfo, err error) {
|
||||
fi, err := n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket))
|
||||
fi, err := n.clnt.Stat(n.hdfsPathJoin(bucket))
|
||||
if err != nil {
|
||||
return bi, hdfsToObjectErr(ctx, err, bucket)
|
||||
}
|
||||
@@ -333,7 +351,7 @@ func (n *hdfsObjects) ListBuckets(ctx context.Context) (buckets []minio.BucketIn
|
||||
func (n *hdfsObjects) listDirFactory() minio.ListDirFunc {
|
||||
// listDir - lists all the entries at a given prefix and given entry in the prefix.
|
||||
listDir := func(bucket, prefixDir, prefixEntry string) (emptyDir bool, entries []string) {
|
||||
f, err := n.clnt.Open(minio.PathJoin(hdfsSeparator, bucket, prefixDir))
|
||||
f, err := n.clnt.Open(n.hdfsPathJoin(bucket, prefixDir))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = nil
|
||||
@@ -366,12 +384,12 @@ func (n *hdfsObjects) listDirFactory() minio.ListDirFunc {
|
||||
|
||||
// ListObjects lists all blobs in HDFS bucket filtered by prefix.
|
||||
func (n *hdfsObjects) ListObjects(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (loi minio.ListObjectsInfo, err error) {
|
||||
if _, err := n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket)); err != nil {
|
||||
if _, err := n.clnt.Stat(n.hdfsPathJoin(bucket)); err != nil {
|
||||
return loi, hdfsToObjectErr(ctx, err, bucket)
|
||||
}
|
||||
|
||||
getObjectInfo := func(ctx context.Context, bucket, entry string) (minio.ObjectInfo, error) {
|
||||
fi, err := n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket, entry))
|
||||
fi, err := n.clnt.Stat(n.hdfsPathJoin(bucket, entry))
|
||||
if err != nil {
|
||||
return minio.ObjectInfo{}, hdfsToObjectErr(ctx, err, bucket, entry)
|
||||
}
|
||||
@@ -440,7 +458,7 @@ func (n *hdfsObjects) ListObjectsV2(ctx context.Context, bucket, prefix, continu
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) DeleteObject(ctx context.Context, bucket, object string, opts minio.ObjectOptions) (minio.ObjectInfo, error) {
|
||||
err := hdfsToObjectErr(ctx, n.deleteObject(minio.PathJoin(hdfsSeparator, bucket), minio.PathJoin(hdfsSeparator, bucket, object)), bucket, object)
|
||||
err := hdfsToObjectErr(ctx, n.deleteObject(n.hdfsPathJoin(bucket), n.hdfsPathJoin(bucket, object)), bucket, object)
|
||||
return minio.ObjectInfo{
|
||||
Bucket: bucket,
|
||||
Name: object,
|
||||
@@ -487,7 +505,7 @@ func (n *hdfsObjects) GetObjectNInfo(ctx context.Context, bucket, object string,
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBucket, dstObject string, srcInfo minio.ObjectInfo, srcOpts, dstOpts minio.ObjectOptions) (minio.ObjectInfo, error) {
|
||||
cpSrcDstSame := minio.IsStringEqual(minio.PathJoin(hdfsSeparator, srcBucket, srcObject), minio.PathJoin(hdfsSeparator, dstBucket, dstObject))
|
||||
cpSrcDstSame := minio.IsStringEqual(n.hdfsPathJoin(srcBucket, srcObject), n.hdfsPathJoin(dstBucket, dstObject))
|
||||
if cpSrcDstSame {
|
||||
return n.GetObjectInfo(ctx, srcBucket, srcObject, minio.ObjectOptions{})
|
||||
}
|
||||
@@ -499,10 +517,10 @@ func (n *hdfsObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstB
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) GetObject(ctx context.Context, bucket, key string, startOffset, length int64, writer io.Writer, etag string, opts minio.ObjectOptions) error {
|
||||
if _, err := n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket)); err != nil {
|
||||
if _, err := n.clnt.Stat(n.hdfsPathJoin(bucket)); err != nil {
|
||||
return hdfsToObjectErr(ctx, err, bucket)
|
||||
}
|
||||
rd, err := n.clnt.Open(minio.PathJoin(hdfsSeparator, bucket, key))
|
||||
rd, err := n.clnt.Open(n.hdfsPathJoin(bucket, key))
|
||||
if err != nil {
|
||||
return hdfsToObjectErr(ctx, err, bucket, key)
|
||||
}
|
||||
@@ -518,7 +536,7 @@ func (n *hdfsObjects) GetObject(ctx context.Context, bucket, key string, startOf
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) isObjectDir(ctx context.Context, bucket, object string) bool {
|
||||
f, err := n.clnt.Open(minio.PathJoin(hdfsSeparator, bucket, object))
|
||||
f, err := n.clnt.Open(n.hdfsPathJoin(bucket, object))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
@@ -538,7 +556,7 @@ func (n *hdfsObjects) isObjectDir(ctx context.Context, bucket, object string) bo
|
||||
|
||||
// GetObjectInfo reads object info and replies back ObjectInfo.
|
||||
func (n *hdfsObjects) GetObjectInfo(ctx context.Context, bucket, object string, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
|
||||
_, err = n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket))
|
||||
_, err = n.clnt.Stat(n.hdfsPathJoin(bucket))
|
||||
if err != nil {
|
||||
return objInfo, hdfsToObjectErr(ctx, err, bucket)
|
||||
}
|
||||
@@ -546,7 +564,7 @@ func (n *hdfsObjects) GetObjectInfo(ctx context.Context, bucket, object string,
|
||||
return objInfo, hdfsToObjectErr(ctx, os.ErrNotExist, bucket, object)
|
||||
}
|
||||
|
||||
fi, err := n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket, object))
|
||||
fi, err := n.clnt.Stat(n.hdfsPathJoin(bucket, object))
|
||||
if err != nil {
|
||||
return objInfo, hdfsToObjectErr(ctx, err, bucket, object)
|
||||
}
|
||||
@@ -561,27 +579,27 @@ func (n *hdfsObjects) GetObjectInfo(ctx context.Context, bucket, object string,
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) PutObject(ctx context.Context, bucket string, object string, r *minio.PutObjReader, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
|
||||
_, err = n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket))
|
||||
_, err = n.clnt.Stat(n.hdfsPathJoin(bucket))
|
||||
if err != nil {
|
||||
return objInfo, hdfsToObjectErr(ctx, err, bucket)
|
||||
}
|
||||
|
||||
name := minio.PathJoin(hdfsSeparator, bucket, object)
|
||||
name := n.hdfsPathJoin(bucket, object)
|
||||
|
||||
// If its a directory create a prefix {
|
||||
if strings.HasSuffix(object, hdfsSeparator) && r.Size() == 0 {
|
||||
if err = n.clnt.MkdirAll(name, os.FileMode(0755)); err != nil {
|
||||
n.deleteObject(minio.PathJoin(hdfsSeparator, bucket), name)
|
||||
n.deleteObject(n.hdfsPathJoin(bucket), name)
|
||||
return objInfo, hdfsToObjectErr(ctx, err, bucket, object)
|
||||
}
|
||||
} else {
|
||||
tmpname := minio.PathJoin(hdfsSeparator, minioMetaTmpBucket, minio.MustGetUUID())
|
||||
tmpname := n.hdfsPathJoin(minioMetaTmpBucket, minio.MustGetUUID())
|
||||
var w *hdfs.FileWriter
|
||||
w, err = n.clnt.Create(tmpname)
|
||||
if err != nil {
|
||||
return objInfo, hdfsToObjectErr(ctx, err, bucket, object)
|
||||
}
|
||||
defer n.deleteObject(minio.PathJoin(hdfsSeparator, minioMetaTmpBucket), tmpname)
|
||||
defer n.deleteObject(n.hdfsPathJoin(minioMetaTmpBucket), tmpname)
|
||||
if _, err = io.Copy(w, r); err != nil {
|
||||
w.Close()
|
||||
return objInfo, hdfsToObjectErr(ctx, err, bucket, object)
|
||||
@@ -590,7 +608,7 @@ func (n *hdfsObjects) PutObject(ctx context.Context, bucket string, object strin
|
||||
if dir != "" {
|
||||
if err = n.clnt.MkdirAll(dir, os.FileMode(0755)); err != nil {
|
||||
w.Close()
|
||||
n.deleteObject(minio.PathJoin(hdfsSeparator, bucket), dir)
|
||||
n.deleteObject(n.hdfsPathJoin(bucket), dir)
|
||||
return objInfo, hdfsToObjectErr(ctx, err, bucket, object)
|
||||
}
|
||||
}
|
||||
@@ -615,13 +633,13 @@ func (n *hdfsObjects) PutObject(ctx context.Context, bucket string, object strin
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) NewMultipartUpload(ctx context.Context, bucket string, object string, opts minio.ObjectOptions) (uploadID string, err error) {
|
||||
_, err = n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket))
|
||||
_, err = n.clnt.Stat(n.hdfsPathJoin(bucket))
|
||||
if err != nil {
|
||||
return uploadID, hdfsToObjectErr(ctx, err, bucket)
|
||||
}
|
||||
|
||||
uploadID = minio.MustGetUUID()
|
||||
if err = n.clnt.CreateEmptyFile(minio.PathJoin(hdfsSeparator, minioMetaTmpBucket, uploadID)); err != nil {
|
||||
if err = n.clnt.CreateEmptyFile(n.hdfsPathJoin(minioMetaTmpBucket, uploadID)); err != nil {
|
||||
return uploadID, hdfsToObjectErr(ctx, err, bucket)
|
||||
}
|
||||
|
||||
@@ -629,7 +647,7 @@ func (n *hdfsObjects) NewMultipartUpload(ctx context.Context, bucket string, obj
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) ListMultipartUploads(ctx context.Context, bucket string, prefix string, keyMarker string, uploadIDMarker string, delimiter string, maxUploads int) (lmi minio.ListMultipartsInfo, err error) {
|
||||
_, err = n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket))
|
||||
_, err = n.clnt.Stat(n.hdfsPathJoin(bucket))
|
||||
if err != nil {
|
||||
return lmi, hdfsToObjectErr(ctx, err, bucket)
|
||||
}
|
||||
@@ -639,7 +657,7 @@ func (n *hdfsObjects) ListMultipartUploads(ctx context.Context, bucket string, p
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) checkUploadIDExists(ctx context.Context, bucket, object, uploadID string) (err error) {
|
||||
_, err = n.clnt.Stat(minio.PathJoin(hdfsSeparator, minioMetaTmpBucket, uploadID))
|
||||
_, err = n.clnt.Stat(n.hdfsPathJoin(minioMetaTmpBucket, uploadID))
|
||||
if err != nil {
|
||||
return hdfsToObjectErr(ctx, err, bucket, object, uploadID)
|
||||
}
|
||||
@@ -648,7 +666,7 @@ func (n *hdfsObjects) checkUploadIDExists(ctx context.Context, bucket, object, u
|
||||
|
||||
// GetMultipartInfo returns multipart info of the uploadId of the object
|
||||
func (n *hdfsObjects) GetMultipartInfo(ctx context.Context, bucket, object, uploadID string, opts minio.ObjectOptions) (result minio.MultipartInfo, err error) {
|
||||
_, err = n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket))
|
||||
_, err = n.clnt.Stat(n.hdfsPathJoin(bucket))
|
||||
if err != nil {
|
||||
return result, hdfsToObjectErr(ctx, err, bucket)
|
||||
}
|
||||
@@ -664,7 +682,7 @@ func (n *hdfsObjects) GetMultipartInfo(ctx context.Context, bucket, object, uplo
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) ListObjectParts(ctx context.Context, bucket, object, uploadID string, partNumberMarker int, maxParts int, opts minio.ObjectOptions) (result minio.ListPartsInfo, err error) {
|
||||
_, err = n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket))
|
||||
_, err = n.clnt.Stat(n.hdfsPathJoin(bucket))
|
||||
if err != nil {
|
||||
return result, hdfsToObjectErr(ctx, err, bucket)
|
||||
}
|
||||
@@ -683,13 +701,13 @@ func (n *hdfsObjects) CopyObjectPart(ctx context.Context, srcBucket, srcObject,
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) PutObjectPart(ctx context.Context, bucket, object, uploadID string, partID int, r *minio.PutObjReader, opts minio.ObjectOptions) (info minio.PartInfo, err error) {
|
||||
_, err = n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket))
|
||||
_, err = n.clnt.Stat(n.hdfsPathJoin(bucket))
|
||||
if err != nil {
|
||||
return info, hdfsToObjectErr(ctx, err, bucket)
|
||||
}
|
||||
|
||||
var w *hdfs.FileWriter
|
||||
w, err = n.clnt.Append(minio.PathJoin(hdfsSeparator, minioMetaTmpBucket, uploadID))
|
||||
w, err = n.clnt.Append(n.hdfsPathJoin(minioMetaTmpBucket, uploadID))
|
||||
if err != nil {
|
||||
return info, hdfsToObjectErr(ctx, err, bucket, object, uploadID)
|
||||
}
|
||||
@@ -708,7 +726,7 @@ func (n *hdfsObjects) PutObjectPart(ctx context.Context, bucket, object, uploadI
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) CompleteMultipartUpload(ctx context.Context, bucket, object, uploadID string, parts []minio.CompletePart, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
|
||||
_, err = n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket))
|
||||
_, err = n.clnt.Stat(n.hdfsPathJoin(bucket))
|
||||
if err != nil {
|
||||
return objInfo, hdfsToObjectErr(ctx, err, bucket)
|
||||
}
|
||||
@@ -717,7 +735,7 @@ func (n *hdfsObjects) CompleteMultipartUpload(ctx context.Context, bucket, objec
|
||||
return objInfo, err
|
||||
}
|
||||
|
||||
name := minio.PathJoin(hdfsSeparator, bucket, object)
|
||||
name := n.hdfsPathJoin(bucket, object)
|
||||
dir := path.Dir(name)
|
||||
if dir != "" {
|
||||
if err = n.clnt.MkdirAll(dir, os.FileMode(0755)); err != nil {
|
||||
@@ -725,19 +743,19 @@ func (n *hdfsObjects) CompleteMultipartUpload(ctx context.Context, bucket, objec
|
||||
}
|
||||
}
|
||||
|
||||
err = n.clnt.Rename(minio.PathJoin(hdfsSeparator, minioMetaTmpBucket, uploadID), name)
|
||||
err = n.clnt.Rename(n.hdfsPathJoin(minioMetaTmpBucket, uploadID), name)
|
||||
// Object already exists is an error on HDFS
|
||||
// remove it and then create it again.
|
||||
if os.IsExist(err) {
|
||||
if err = n.clnt.Remove(name); err != nil {
|
||||
if dir != "" {
|
||||
n.deleteObject(minio.PathJoin(hdfsSeparator, bucket), dir)
|
||||
n.deleteObject(n.hdfsPathJoin(bucket), dir)
|
||||
}
|
||||
return objInfo, hdfsToObjectErr(ctx, err, bucket, object)
|
||||
}
|
||||
if err = n.clnt.Rename(minio.PathJoin(hdfsSeparator, minioMetaTmpBucket, uploadID), name); err != nil {
|
||||
if err = n.clnt.Rename(n.hdfsPathJoin(minioMetaTmpBucket, uploadID), name); err != nil {
|
||||
if dir != "" {
|
||||
n.deleteObject(minio.PathJoin(hdfsSeparator, bucket), dir)
|
||||
n.deleteObject(n.hdfsPathJoin(bucket), dir)
|
||||
}
|
||||
return objInfo, hdfsToObjectErr(ctx, err, bucket, object)
|
||||
}
|
||||
@@ -762,11 +780,11 @@ func (n *hdfsObjects) CompleteMultipartUpload(ctx context.Context, bucket, objec
|
||||
}
|
||||
|
||||
func (n *hdfsObjects) AbortMultipartUpload(ctx context.Context, bucket, object, uploadID string) (err error) {
|
||||
_, err = n.clnt.Stat(minio.PathJoin(hdfsSeparator, bucket))
|
||||
_, err = n.clnt.Stat(n.hdfsPathJoin(bucket))
|
||||
if err != nil {
|
||||
return hdfsToObjectErr(ctx, err, bucket)
|
||||
}
|
||||
return hdfsToObjectErr(ctx, n.clnt.Remove(minio.PathJoin(hdfsSeparator, minioMetaTmpBucket, uploadID)), bucket, object, uploadID)
|
||||
return hdfsToObjectErr(ctx, n.clnt.Remove(n.hdfsPathJoin(minioMetaTmpBucket, uploadID)), bucket, object, uploadID)
|
||||
}
|
||||
|
||||
// IsReady returns whether the layer is ready to take requests.
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/encrypt"
|
||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||
minio "github.com/minio/minio/cmd"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
@@ -330,7 +330,7 @@ func (l *s3EncObjects) GetObjectNInfo(ctx context.Context, bucket, object string
|
||||
// Setup cleanup function to cause the above go-routine to
|
||||
// exit in case of partial read
|
||||
pipeCloser := func() { pr.Close() }
|
||||
return fn(pr, h, o.CheckCopyPrecondFn, pipeCloser)
|
||||
return fn(pr, h, o.CheckPrecondFn, pipeCloser)
|
||||
}
|
||||
|
||||
// GetObjectInfo reads object info and replies back ObjectInfo
|
||||
@@ -785,7 +785,7 @@ func (l *s3EncObjects) DeleteBucket(ctx context.Context, bucket string, forceDel
|
||||
for k := range expParts {
|
||||
l.s3Objects.DeleteObject(ctx, bucket, k, minio.ObjectOptions{})
|
||||
}
|
||||
err := l.Client.RemoveBucket(bucket)
|
||||
err := l.Client.RemoveBucket(ctx, bucket)
|
||||
if err != nil {
|
||||
return minio.ErrorRespToObjectError(err, bucket)
|
||||
}
|
||||
|
||||
@@ -27,13 +27,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/minio/cli"
|
||||
miniogo "github.com/minio/minio-go/v6"
|
||||
"github.com/minio/minio-go/v6/pkg/credentials"
|
||||
"github.com/minio/minio-go/v6/pkg/tags"
|
||||
miniogo "github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
minio "github.com/minio/minio/cmd"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/encrypt"
|
||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
@@ -165,7 +165,7 @@ var defaultAWSCredProviders = []credentials.Provider{
|
||||
}
|
||||
|
||||
// newS3 - Initializes a new client by auto probing S3 server signature.
|
||||
func newS3(urlStr string) (*miniogo.Core, error) {
|
||||
func newS3(urlStr string, tripper http.RoundTripper) (*miniogo.Core, error) {
|
||||
if urlStr == "" {
|
||||
urlStr = "https://s3.amazonaws.com"
|
||||
}
|
||||
@@ -191,14 +191,15 @@ func newS3(urlStr string) (*miniogo.Core, error) {
|
||||
creds = credentials.NewChainCredentials(defaultProviders)
|
||||
}
|
||||
|
||||
options := miniogo.Options{
|
||||
options := &miniogo.Options{
|
||||
Creds: creds,
|
||||
Secure: secure,
|
||||
Region: s3utils.GetRegionFromURL(*u),
|
||||
BucketLookup: miniogo.BucketLookupAuto,
|
||||
Transport: tripper,
|
||||
}
|
||||
|
||||
clnt, err := miniogo.NewWithOptions(endpoint, &options)
|
||||
clnt, err := miniogo.New(endpoint, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -208,13 +209,6 @@ func newS3(urlStr string) (*miniogo.Core, error) {
|
||||
|
||||
// NewGatewayLayer returns s3 ObjectLayer.
|
||||
func (g *S3) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, error) {
|
||||
// creds are ignored here, since S3 gateway implements chaining
|
||||
// all credentials.
|
||||
clnt, err := newS3(g.host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
metrics := minio.NewMetrics()
|
||||
|
||||
t := &minio.MetricsTransport{
|
||||
@@ -222,13 +216,17 @@ func (g *S3) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, error)
|
||||
Metrics: metrics,
|
||||
}
|
||||
|
||||
// Set custom transport
|
||||
clnt.SetCustomTransport(t)
|
||||
// creds are ignored here, since S3 gateway implements chaining
|
||||
// all credentials.
|
||||
clnt, err := newS3(g.host, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
probeBucketName := randString(60, rand.NewSource(time.Now().UnixNano()), "probe-bucket-sign-")
|
||||
|
||||
// Check if the provided keys are valid.
|
||||
if _, err = clnt.BucketExists(probeBucketName); err != nil {
|
||||
if _, err = clnt.BucketExists(context.Background(), probeBucketName); err != nil {
|
||||
if miniogo.ToErrorResponse(err).Code != "AccessDenied" {
|
||||
return nil, err
|
||||
}
|
||||
@@ -302,7 +300,7 @@ func (l *s3Objects) MakeBucketWithLocation(ctx context.Context, bucket string, o
|
||||
if s3utils.CheckValidBucketName(bucket) != nil {
|
||||
return minio.BucketNameInvalid{Bucket: bucket}
|
||||
}
|
||||
err := l.Client.MakeBucket(bucket, opts.Location)
|
||||
err := l.Client.MakeBucket(ctx, bucket, miniogo.MakeBucketOptions{Region: opts.Location})
|
||||
if err != nil {
|
||||
return minio.ErrorRespToObjectError(err, bucket)
|
||||
}
|
||||
@@ -311,12 +309,12 @@ func (l *s3Objects) MakeBucketWithLocation(ctx context.Context, bucket string, o
|
||||
|
||||
// GetBucketInfo gets bucket metadata..
|
||||
func (l *s3Objects) GetBucketInfo(ctx context.Context, bucket string) (bi minio.BucketInfo, e error) {
|
||||
buckets, err := l.Client.ListBuckets()
|
||||
buckets, err := l.Client.ListBuckets(ctx)
|
||||
if err != nil {
|
||||
// Listbuckets may be disallowed, proceed to check if
|
||||
// bucket indeed exists, if yes return success.
|
||||
var ok bool
|
||||
if ok, err = l.Client.BucketExists(bucket); err != nil {
|
||||
if ok, err = l.Client.BucketExists(ctx, bucket); err != nil {
|
||||
return bi, minio.ErrorRespToObjectError(err, bucket)
|
||||
}
|
||||
if !ok {
|
||||
@@ -344,7 +342,7 @@ func (l *s3Objects) GetBucketInfo(ctx context.Context, bucket string) (bi minio.
|
||||
|
||||
// ListBuckets lists all S3 buckets
|
||||
func (l *s3Objects) ListBuckets(ctx context.Context) ([]minio.BucketInfo, error) {
|
||||
buckets, err := l.Client.ListBuckets()
|
||||
buckets, err := l.Client.ListBuckets(ctx)
|
||||
if err != nil {
|
||||
return nil, minio.ErrorRespToObjectError(err)
|
||||
}
|
||||
@@ -362,7 +360,7 @@ func (l *s3Objects) ListBuckets(ctx context.Context) ([]minio.BucketInfo, error)
|
||||
|
||||
// DeleteBucket deletes a bucket on S3
|
||||
func (l *s3Objects) DeleteBucket(ctx context.Context, bucket string, forceDelete bool) error {
|
||||
err := l.Client.RemoveBucket(bucket)
|
||||
err := l.Client.RemoveBucket(ctx, bucket)
|
||||
if err != nil {
|
||||
return minio.ErrorRespToObjectError(err, bucket)
|
||||
}
|
||||
@@ -381,7 +379,7 @@ func (l *s3Objects) ListObjects(ctx context.Context, bucket string, prefix strin
|
||||
|
||||
// ListObjectsV2 lists all blobs in S3 bucket filtered by prefix
|
||||
func (l *s3Objects) ListObjectsV2(ctx context.Context, bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (loi minio.ListObjectsV2Info, e error) {
|
||||
result, err := l.Client.ListObjectsV2(bucket, prefix, continuationToken, fetchOwner, delimiter, maxKeys, startAfter)
|
||||
result, err := l.Client.ListObjectsV2(bucket, prefix, continuationToken, fetchOwner, delimiter, maxKeys)
|
||||
if err != nil {
|
||||
return loi, minio.ErrorRespToObjectError(err, bucket)
|
||||
}
|
||||
@@ -433,7 +431,7 @@ func (l *s3Objects) GetObject(ctx context.Context, bucket string, key string, st
|
||||
return minio.ErrorRespToObjectError(err, bucket, key)
|
||||
}
|
||||
}
|
||||
object, _, _, err := l.Client.GetObject(bucket, key, opts)
|
||||
object, _, _, err := l.Client.GetObject(ctx, bucket, key, opts)
|
||||
if err != nil {
|
||||
return minio.ErrorRespToObjectError(err, bucket, key)
|
||||
}
|
||||
@@ -446,10 +444,8 @@ func (l *s3Objects) GetObject(ctx context.Context, bucket string, key string, st
|
||||
|
||||
// GetObjectInfo reads object info and replies back ObjectInfo
|
||||
func (l *s3Objects) GetObjectInfo(ctx context.Context, bucket string, object string, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
|
||||
oi, err := l.Client.StatObject(bucket, object, miniogo.StatObjectOptions{
|
||||
GetObjectOptions: miniogo.GetObjectOptions{
|
||||
ServerSideEncryption: opts.ServerSideEncryption,
|
||||
},
|
||||
oi, err := l.Client.StatObject(ctx, bucket, object, miniogo.StatObjectOptions{
|
||||
ServerSideEncryption: opts.ServerSideEncryption,
|
||||
})
|
||||
if err != nil {
|
||||
return minio.ObjectInfo{}, minio.ErrorRespToObjectError(err, bucket, object)
|
||||
@@ -475,21 +471,26 @@ func (l *s3Objects) PutObject(ctx context.Context, bucket string, object string,
|
||||
ServerSideEncryption: opts.ServerSideEncryption,
|
||||
UserTags: tagMap,
|
||||
}
|
||||
oi, err := l.Client.PutObject(bucket, object, data, data.Size(), data.MD5Base64String(), data.SHA256HexString(), putOpts)
|
||||
|
||||
ui, err := l.Client.PutObject(ctx, bucket, object, data, data.Size(), data.MD5Base64String(), data.SHA256HexString(), putOpts)
|
||||
if err != nil {
|
||||
return objInfo, minio.ErrorRespToObjectError(err, bucket, object)
|
||||
}
|
||||
|
||||
// On success, populate the key & metadata so they are present in the notification
|
||||
oi.Key = object
|
||||
oi.Metadata = minio.ToMinioClientObjectInfoMetadata(opts.UserDefined)
|
||||
oi := miniogo.ObjectInfo{
|
||||
ETag: ui.ETag,
|
||||
Size: ui.Size,
|
||||
Key: object,
|
||||
Metadata: minio.ToMinioClientObjectInfoMetadata(opts.UserDefined),
|
||||
}
|
||||
|
||||
return minio.FromMinioClientObjectInfo(bucket, oi), nil
|
||||
}
|
||||
|
||||
// CopyObject copies an object from source bucket to a destination bucket.
|
||||
func (l *s3Objects) CopyObject(ctx context.Context, srcBucket string, srcObject string, dstBucket string, dstObject string, srcInfo minio.ObjectInfo, srcOpts, dstOpts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
|
||||
if srcOpts.CheckCopyPrecondFn != nil && srcOpts.CheckCopyPrecondFn(srcInfo, "") {
|
||||
if srcOpts.CheckPrecondFn != nil && srcOpts.CheckPrecondFn(srcInfo) {
|
||||
return minio.ObjectInfo{}, minio.PreConditionFailed{}
|
||||
}
|
||||
// Set this header such that following CopyObject() always sets the right metadata on the destination.
|
||||
@@ -511,7 +512,7 @@ func (l *s3Objects) CopyObject(ctx context.Context, srcBucket string, srcObject
|
||||
srcInfo.UserDefined[k] = v[0]
|
||||
}
|
||||
|
||||
if _, err = l.Client.CopyObject(srcBucket, srcObject, dstBucket, dstObject, srcInfo.UserDefined); err != nil {
|
||||
if _, err = l.Client.CopyObject(ctx, srcBucket, srcObject, dstBucket, dstObject, srcInfo.UserDefined); err != nil {
|
||||
return objInfo, minio.ErrorRespToObjectError(err, srcBucket, srcObject)
|
||||
}
|
||||
return l.GetObjectInfo(ctx, dstBucket, dstObject, dstOpts)
|
||||
@@ -519,7 +520,7 @@ func (l *s3Objects) CopyObject(ctx context.Context, srcBucket string, srcObject
|
||||
|
||||
// DeleteObject deletes a blob in bucket
|
||||
func (l *s3Objects) DeleteObject(ctx context.Context, bucket string, object string, opts minio.ObjectOptions) (minio.ObjectInfo, error) {
|
||||
err := l.Client.RemoveObject(bucket, object)
|
||||
err := l.Client.RemoveObject(ctx, bucket, object, miniogo.RemoveObjectOptions{})
|
||||
if err != nil {
|
||||
return minio.ObjectInfo{}, minio.ErrorRespToObjectError(err, bucket, object)
|
||||
}
|
||||
@@ -546,7 +547,7 @@ func (l *s3Objects) DeleteObjects(ctx context.Context, bucket string, objects []
|
||||
|
||||
// ListMultipartUploads lists all multipart uploads.
|
||||
func (l *s3Objects) ListMultipartUploads(ctx context.Context, bucket string, prefix string, keyMarker string, uploadIDMarker string, delimiter string, maxUploads int) (lmi minio.ListMultipartsInfo, e error) {
|
||||
result, err := l.Client.ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads)
|
||||
result, err := l.Client.ListMultipartUploads(ctx, bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads)
|
||||
if err != nil {
|
||||
return lmi, err
|
||||
}
|
||||
@@ -571,7 +572,7 @@ func (l *s3Objects) NewMultipartUpload(ctx context.Context, bucket string, objec
|
||||
ServerSideEncryption: o.ServerSideEncryption,
|
||||
UserTags: tagMap,
|
||||
}
|
||||
uploadID, err = l.Client.NewMultipartUpload(bucket, object, opts)
|
||||
uploadID, err = l.Client.NewMultipartUpload(ctx, bucket, object, opts)
|
||||
if err != nil {
|
||||
return uploadID, minio.ErrorRespToObjectError(err, bucket, object)
|
||||
}
|
||||
@@ -581,7 +582,7 @@ func (l *s3Objects) NewMultipartUpload(ctx context.Context, bucket string, objec
|
||||
// PutObjectPart puts a part of object in bucket
|
||||
func (l *s3Objects) PutObjectPart(ctx context.Context, bucket string, object string, uploadID string, partID int, r *minio.PutObjReader, opts minio.ObjectOptions) (pi minio.PartInfo, e error) {
|
||||
data := r.Reader
|
||||
info, err := l.Client.PutObjectPart(bucket, object, uploadID, partID, data, data.Size(), data.MD5Base64String(), data.SHA256HexString(), opts.ServerSideEncryption)
|
||||
info, err := l.Client.PutObjectPart(ctx, bucket, object, uploadID, partID, data, data.Size(), data.MD5Base64String(), data.SHA256HexString(), opts.ServerSideEncryption)
|
||||
if err != nil {
|
||||
return pi, minio.ErrorRespToObjectError(err, bucket, object)
|
||||
}
|
||||
@@ -593,7 +594,7 @@ func (l *s3Objects) PutObjectPart(ctx context.Context, bucket string, object str
|
||||
// existing object or a part of it.
|
||||
func (l *s3Objects) CopyObjectPart(ctx context.Context, srcBucket, srcObject, destBucket, destObject, uploadID string,
|
||||
partID int, startOffset, length int64, srcInfo minio.ObjectInfo, srcOpts, dstOpts minio.ObjectOptions) (p minio.PartInfo, err error) {
|
||||
if srcOpts.CheckCopyPrecondFn != nil && srcOpts.CheckCopyPrecondFn(srcInfo, "") {
|
||||
if srcOpts.CheckPrecondFn != nil && srcOpts.CheckPrecondFn(srcInfo) {
|
||||
return minio.PartInfo{}, minio.PreConditionFailed{}
|
||||
}
|
||||
srcInfo.UserDefined = map[string]string{
|
||||
@@ -611,7 +612,7 @@ func (l *s3Objects) CopyObjectPart(ctx context.Context, srcBucket, srcObject, de
|
||||
srcInfo.UserDefined[k] = v[0]
|
||||
}
|
||||
|
||||
completePart, err := l.Client.CopyObjectPart(srcBucket, srcObject, destBucket, destObject,
|
||||
completePart, err := l.Client.CopyObjectPart(ctx, srcBucket, srcObject, destBucket, destObject,
|
||||
uploadID, partID, startOffset, length, srcInfo.UserDefined)
|
||||
if err != nil {
|
||||
return p, minio.ErrorRespToObjectError(err, srcBucket, srcObject)
|
||||
@@ -659,13 +660,13 @@ func (l *s3Objects) ListObjectParts(ctx context.Context, bucket string, object s
|
||||
|
||||
// AbortMultipartUpload aborts a ongoing multipart upload
|
||||
func (l *s3Objects) AbortMultipartUpload(ctx context.Context, bucket string, object string, uploadID string) error {
|
||||
err := l.Client.AbortMultipartUpload(bucket, object, uploadID)
|
||||
err := l.Client.AbortMultipartUpload(ctx, bucket, object, uploadID)
|
||||
return minio.ErrorRespToObjectError(err, bucket, object)
|
||||
}
|
||||
|
||||
// CompleteMultipartUpload completes ongoing multipart upload and finalizes object
|
||||
func (l *s3Objects) CompleteMultipartUpload(ctx context.Context, bucket string, object string, uploadID string, uploadedParts []minio.CompletePart, opts minio.ObjectOptions) (oi minio.ObjectInfo, e error) {
|
||||
etag, err := l.Client.CompleteMultipartUpload(bucket, object, uploadID, minio.ToMinioClientCompleteParts(uploadedParts))
|
||||
etag, err := l.Client.CompleteMultipartUpload(ctx, bucket, object, uploadID, minio.ToMinioClientCompleteParts(uploadedParts))
|
||||
if err != nil {
|
||||
return oi, minio.ErrorRespToObjectError(err, bucket, object)
|
||||
}
|
||||
@@ -682,7 +683,7 @@ func (l *s3Objects) SetBucketPolicy(ctx context.Context, bucket string, bucketPo
|
||||
return minio.ErrorRespToObjectError(err, bucket)
|
||||
}
|
||||
|
||||
if err := l.Client.SetBucketPolicy(bucket, string(data)); err != nil {
|
||||
if err := l.Client.SetBucketPolicy(ctx, bucket, string(data)); err != nil {
|
||||
return minio.ErrorRespToObjectError(err, bucket)
|
||||
}
|
||||
|
||||
@@ -691,7 +692,7 @@ func (l *s3Objects) SetBucketPolicy(ctx context.Context, bucket string, bucketPo
|
||||
|
||||
// GetBucketPolicy will get policy on bucket
|
||||
func (l *s3Objects) GetBucketPolicy(ctx context.Context, bucket string) (*policy.Policy, error) {
|
||||
data, err := l.Client.GetBucketPolicy(bucket)
|
||||
data, err := l.Client.GetBucketPolicy(ctx, bucket)
|
||||
if err != nil {
|
||||
return nil, minio.ErrorRespToObjectError(err, bucket)
|
||||
}
|
||||
@@ -702,7 +703,7 @@ func (l *s3Objects) GetBucketPolicy(ctx context.Context, bucket string) (*policy
|
||||
|
||||
// DeleteBucketPolicy deletes all policies on bucket
|
||||
func (l *s3Objects) DeleteBucketPolicy(ctx context.Context, bucket string) error {
|
||||
if err := l.Client.SetBucketPolicy(bucket, ""); err != nil {
|
||||
if err := l.Client.SetBucketPolicy(ctx, bucket, ""); err != nil {
|
||||
return minio.ErrorRespToObjectError(err, bucket, "")
|
||||
}
|
||||
return nil
|
||||
@@ -711,21 +712,17 @@ func (l *s3Objects) DeleteBucketPolicy(ctx context.Context, bucket string) error
|
||||
// GetObjectTags gets the tags set on the object
|
||||
func (l *s3Objects) GetObjectTags(ctx context.Context, bucket string, object string, opts minio.ObjectOptions) (*tags.Tags, error) {
|
||||
var err error
|
||||
var tagObj *tags.Tags
|
||||
var tagStr string
|
||||
|
||||
if _, err = l.GetObjectInfo(ctx, bucket, object, opts); err != nil {
|
||||
return nil, minio.ErrorRespToObjectError(err, bucket, object)
|
||||
}
|
||||
|
||||
if tagStr, err = l.Client.GetObjectTagging(bucket, object); err != nil {
|
||||
t, err := l.Client.GetObjectTagging(ctx, bucket, object, miniogo.GetObjectTaggingOptions{})
|
||||
if err != nil {
|
||||
return nil, minio.ErrorRespToObjectError(err, bucket, object)
|
||||
}
|
||||
|
||||
if tagObj, err = tags.ParseObjectXML(strings.NewReader(tagStr)); err != nil {
|
||||
return nil, minio.ErrorRespToObjectError(err, bucket, object)
|
||||
}
|
||||
return tagObj, err
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// PutObjectTags attaches the tags to the object
|
||||
@@ -734,7 +731,7 @@ func (l *s3Objects) PutObjectTags(ctx context.Context, bucket, object string, ta
|
||||
if err != nil {
|
||||
return minio.ErrorRespToObjectError(err, bucket, object)
|
||||
}
|
||||
if err = l.Client.PutObjectTagging(bucket, object, tagObj.ToMap()); err != nil {
|
||||
if err = l.Client.PutObjectTagging(ctx, bucket, object, tagObj, miniogo.PutObjectTaggingOptions{}); err != nil {
|
||||
return minio.ErrorRespToObjectError(err, bucket, object)
|
||||
}
|
||||
return nil
|
||||
@@ -742,7 +739,7 @@ func (l *s3Objects) PutObjectTags(ctx context.Context, bucket, object string, ta
|
||||
|
||||
// DeleteObjectTags removes the tags attached to the object
|
||||
func (l *s3Objects) DeleteObjectTags(ctx context.Context, bucket, object string, opts minio.ObjectOptions) error {
|
||||
if err := l.Client.RemoveObjectTagging(bucket, object); err != nil {
|
||||
if err := l.Client.RemoveObjectTagging(ctx, bucket, object, miniogo.RemoveObjectTaggingOptions{}); err != nil {
|
||||
return minio.ErrorRespToObjectError(err, bucket, object)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
miniogo "github.com/minio/minio-go/v6"
|
||||
miniogo "github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/minio/cmd/config/etcd/dns"
|
||||
|
||||
@@ -103,6 +103,12 @@ func healErasureSet(ctx context.Context, setIndex int, xlObj *erasureObjects, dr
|
||||
}
|
||||
}
|
||||
|
||||
buckets = append(buckets, BucketInfo{
|
||||
Name: pathJoin(minioMetaBucket, minioConfigPrefix),
|
||||
}, BucketInfo{
|
||||
Name: pathJoin(minioMetaBucket, bucketConfigPrefix),
|
||||
}) // add metadata .minio.sys/ bucket prefixes to heal
|
||||
|
||||
// Heal all buckets with all objects
|
||||
for _, bucket := range buckets {
|
||||
// Heal current bucket
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/minio/minio/cmd/config/cache"
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
)
|
||||
|
||||
var serverPort uint32 = 60000
|
||||
|
||||
+15
-4
@@ -28,8 +28,10 @@ import (
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/pkg/certs"
|
||||
"github.com/minio/minio/pkg/env"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -163,7 +165,7 @@ func (srv *Server) Shutdown() error {
|
||||
// (CBC-SHA ciphers can be enabled again if required)
|
||||
// - RSA key exchange ciphers: Disabled because of dangerous PKCS1-v1.5 RSA
|
||||
// padding scheme. See Bleichenbacher attacks.
|
||||
var defaultCipherSuites = []uint16{
|
||||
var secureCipherSuites = []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
@@ -175,15 +177,19 @@ var defaultCipherSuites = []uint16{
|
||||
// Go only provides constant-time implementations of Curve25519 and NIST P-256 curve.
|
||||
var secureCurves = []tls.CurveID{tls.X25519, tls.CurveP256}
|
||||
|
||||
const (
|
||||
enableSecureCiphersEnv = "MINIO_API_SECURE_CIPHERS"
|
||||
)
|
||||
|
||||
// NewServer - creates new HTTP server using given arguments.
|
||||
func NewServer(addrs []string, handler http.Handler, getCert certs.GetCertificateFunc) *Server {
|
||||
secureCiphers := env.Get(enableSecureCiphersEnv, config.EnableOn) == config.EnableOn
|
||||
|
||||
var tlsConfig *tls.Config
|
||||
if getCert != nil {
|
||||
tlsConfig = &tls.Config{
|
||||
// TLS hardening
|
||||
PreferServerCipherSuites: true,
|
||||
CipherSuites: defaultCipherSuites,
|
||||
CurvePreferences: secureCurves,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
// Do not edit the next line, protos priority is kept
|
||||
// on purpose in this manner for HTTP 2.0, we would
|
||||
@@ -197,6 +203,11 @@ func NewServer(addrs []string, handler http.Handler, getCert certs.GetCertificat
|
||||
tlsConfig.GetCertificate = getCert
|
||||
}
|
||||
|
||||
if secureCiphers && tlsConfig != nil {
|
||||
tlsConfig.CipherSuites = secureCipherSuites
|
||||
tlsConfig.CurvePreferences = secureCurves
|
||||
}
|
||||
|
||||
httpServer := &Server{
|
||||
Addrs: addrs,
|
||||
ShutdownTimeout: DefaultShutdownTimeout,
|
||||
|
||||
@@ -25,9 +25,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
jwtgo "github.com/dgrijalva/jwt-go"
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
@@ -120,7 +121,7 @@ func (ies *IAMEtcdStore) loadIAMConfig(item interface{}, path string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if globalConfigEncrypted {
|
||||
if globalConfigEncrypted && !utf8.Valid(pdata) {
|
||||
pdata, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(pdata))
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
jwtgo "github.com/dgrijalva/jwt-go"
|
||||
|
||||
@@ -223,7 +224,7 @@ func (iamOS *IAMObjectStore) loadIAMConfig(item interface{}, path string) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if globalConfigEncrypted {
|
||||
if globalConfigEncrypted && !utf8.Valid(data) {
|
||||
data, err = madmin.DecryptData(globalActiveCred.String(), bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -324,7 +325,7 @@ func (iamOS *IAMObjectStore) loadUsers(ctx context.Context, userType IAMUserType
|
||||
}
|
||||
|
||||
userName := item.Item
|
||||
if err := iamOS.loadUser(userName, userType, m); err != errNoSuchUser {
|
||||
if err := iamOS.loadUser(userName, userType, m); err != nil && err != errNoSuchUser {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
|
||||
"github.com/minio/minio/cmd/http"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/cmd/rest"
|
||||
"github.com/minio/minio/pkg/dsync"
|
||||
)
|
||||
@@ -155,13 +154,12 @@ func newlockRESTClient(endpoint Endpoint) *lockRESTClient {
|
||||
}
|
||||
|
||||
trFn := newCustomHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)
|
||||
restClient, err := rest.NewClient(serverURL, trFn, newAuthToken)
|
||||
if err != nil {
|
||||
logger.Fatal(err, "Unable to create lock rest client")
|
||||
}
|
||||
restClient := rest.NewClient(serverURL, trFn, newAuthToken)
|
||||
restClient.HealthCheckFn = func() bool {
|
||||
ctx, cancel := context.WithTimeout(GlobalContext, restClient.HealthCheckTimeout)
|
||||
respBody, err := restClient.CallWithContext(ctx, lockRESTMethodHealth, nil, nil, -1)
|
||||
// Instantiate a new rest client for healthcheck
|
||||
// to avoid recursive healthCheckFn()
|
||||
respBody, err := rest.NewClient(serverURL, trFn, newAuthToken).CallWithContext(ctx, lockRESTMethodHealth, nil, nil, -1)
|
||||
xhttp.DrainBody(respBody)
|
||||
cancel()
|
||||
var ne *rest.NetworkError
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/minio/highwayhash"
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/cmd/logger/message/log"
|
||||
)
|
||||
|
||||
|
||||
+7
-10
@@ -464,10 +464,7 @@ func storageMetricsPrometheus(ch chan<- prometheus.Metric) {
|
||||
float64(totalDisks.Sum()),
|
||||
)
|
||||
|
||||
for i := 0; i < len(storageInfo.Total); i++ {
|
||||
mountPath, total, free := storageInfo.MountPaths[i], storageInfo.Total[i],
|
||||
storageInfo.Available[i]
|
||||
|
||||
for _, disk := range storageInfo.Disks {
|
||||
// Total disk usage by the disk
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
prometheus.NewDesc(
|
||||
@@ -475,8 +472,8 @@ func storageMetricsPrometheus(ch chan<- prometheus.Metric) {
|
||||
"Total disk storage used on the disk",
|
||||
[]string{"disk"}, nil),
|
||||
prometheus.GaugeValue,
|
||||
float64(total-free),
|
||||
mountPath,
|
||||
float64(disk.UsedSpace),
|
||||
disk.DrivePath,
|
||||
)
|
||||
|
||||
// Total available space in the disk
|
||||
@@ -486,8 +483,8 @@ func storageMetricsPrometheus(ch chan<- prometheus.Metric) {
|
||||
"Total available space left on the disk",
|
||||
[]string{"disk"}, nil),
|
||||
prometheus.GaugeValue,
|
||||
float64(free),
|
||||
mountPath,
|
||||
float64(disk.AvailableSpace),
|
||||
disk.DrivePath,
|
||||
)
|
||||
|
||||
// Total storage space of the disk
|
||||
@@ -497,8 +494,8 @@ func storageMetricsPrometheus(ch chan<- prometheus.Metric) {
|
||||
"Total space on the disk",
|
||||
[]string{"disk"}, nil),
|
||||
prometheus.GaugeValue,
|
||||
float64(total),
|
||||
mountPath,
|
||||
float64(disk.TotalSpace),
|
||||
disk.DrivePath,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
xnet "github.com/minio/minio/pkg/net"
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
)
|
||||
|
||||
func TestMustSplitHostPort(t *testing.T) {
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
|
||||
@@ -43,13 +43,7 @@ const (
|
||||
|
||||
// StorageInfo - represents total capacity of underlying storage.
|
||||
type StorageInfo struct {
|
||||
Used []uint64 // Used total used per disk.
|
||||
|
||||
Total []uint64 // Total disk space per disk.
|
||||
|
||||
Available []uint64 // Total disk space available per disk.
|
||||
|
||||
MountPaths []string // Disk mountpoints
|
||||
Disks []madmin.Disk
|
||||
|
||||
// Backend type.
|
||||
Backend struct {
|
||||
@@ -66,9 +60,6 @@ type StorageInfo struct {
|
||||
StandardSCParity int // Parity disks for currently configured Standard storage class.
|
||||
RRSCData int // Data disks for currently configured Reduced Redundancy storage class.
|
||||
RRSCParity int // Parity disks for currently configured Reduced Redundancy storage class.
|
||||
|
||||
// List of all disk status, this is only meaningful if BackendType is Erasure.
|
||||
Sets [][]madmin.DriveInfo
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
)
|
||||
|
||||
|
||||
+11
-11
@@ -22,14 +22,14 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/encrypt"
|
||||
"github.com/minio/minio-go/v6/pkg/tags"
|
||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
// CheckCopyPreconditionFn returns true if copy precondition check failed.
|
||||
type CheckCopyPreconditionFn func(o ObjectInfo, encETag string) bool
|
||||
// CheckPreconditionFn returns true if precondition check failed.
|
||||
type CheckPreconditionFn func(o ObjectInfo) bool
|
||||
|
||||
// GetObjectInfoFn is the signature of GetObjectInfo function.
|
||||
type GetObjectInfoFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error)
|
||||
@@ -37,13 +37,13 @@ type GetObjectInfoFn func(ctx context.Context, bucket, object string, opts Objec
|
||||
// ObjectOptions represents object options for ObjectLayer object operations
|
||||
type ObjectOptions struct {
|
||||
ServerSideEncryption encrypt.ServerSide
|
||||
Versioned bool // indicates if the bucket is versioned
|
||||
WalkVersions bool // indicates if the we are interested in walking versions
|
||||
VersionID string // Specifies the versionID which needs to be overwritten or read
|
||||
MTime time.Time // Is only set in POST/PUT operations
|
||||
UserDefined map[string]string // only set in case of POST/PUT operations
|
||||
PartNumber int // only useful in case of GetObject/HeadObject
|
||||
CheckCopyPrecondFn CheckCopyPreconditionFn // only set during CopyObject preconditional valuation
|
||||
Versioned bool // indicates if the bucket is versioned
|
||||
WalkVersions bool // indicates if the we are interested in walking versions
|
||||
VersionID string // Specifies the versionID which needs to be overwritten or read
|
||||
MTime time.Time // Is only set in POST/PUT operations
|
||||
UserDefined map[string]string // only set in case of POST/PUT operations
|
||||
PartNumber int // only useful in case of GetObject/HeadObject
|
||||
CheckPrecondFn CheckPreconditionFn // only set during GetObject/HeadObject/CopyObjectPart preconditional valuation
|
||||
}
|
||||
|
||||
// BucketOptions represents bucket options for ObjectLayer bucket operations
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/minio/minio-go/v6/pkg/encrypt"
|
||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
|
||||
+51
-42
@@ -36,7 +36,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/klauspost/compress/s2"
|
||||
"github.com/klauspost/readahead"
|
||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
"github.com/minio/minio/cmd/config/compress"
|
||||
"github.com/minio/minio/cmd/config/etcd/dns"
|
||||
"github.com/minio/minio/cmd/config/storageclass"
|
||||
@@ -346,18 +346,36 @@ func isMinioReservedBucket(bucketName string) bool {
|
||||
|
||||
// returns a slice of hosts by reading a slice of DNS records
|
||||
func getHostsSlice(records []dns.SrvRecord) []string {
|
||||
var hosts []string
|
||||
for _, r := range records {
|
||||
hosts = append(hosts, net.JoinHostPort(r.Host, string(r.Port)))
|
||||
hosts := make([]string, len(records))
|
||||
for i, r := range records {
|
||||
hosts[i] = net.JoinHostPort(r.Host, string(r.Port))
|
||||
}
|
||||
return hosts
|
||||
}
|
||||
|
||||
// returns a host (and corresponding port) from a slice of DNS records
|
||||
func getHostFromSrv(records []dns.SrvRecord) string {
|
||||
rand.Seed(time.Now().Unix())
|
||||
srvRecord := records[rand.Intn(len(records))]
|
||||
return net.JoinHostPort(srvRecord.Host, string(srvRecord.Port))
|
||||
var rng = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
|
||||
|
||||
// returns an online host (and corresponding port) from a slice of DNS records
|
||||
func getHostFromSrv(records []dns.SrvRecord) (host string) {
|
||||
hosts := getHostsSlice(records)
|
||||
|
||||
var d net.Dialer
|
||||
var retry int
|
||||
for retry < len(hosts) {
|
||||
ctx, cancel := context.WithTimeout(GlobalContext, 300*time.Millisecond)
|
||||
|
||||
host = hosts[rng.Intn(len(hosts))]
|
||||
conn, err := d.DialContext(ctx, "tcp", host)
|
||||
cancel()
|
||||
if err != nil {
|
||||
retry++
|
||||
continue
|
||||
}
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
|
||||
return host
|
||||
}
|
||||
|
||||
// IsCompressed returns true if the object is marked as compressed.
|
||||
@@ -509,14 +527,12 @@ type GetObjectReader struct {
|
||||
// NewGetObjectReaderFromReader sets up a GetObjectReader with a given
|
||||
// reader. This ignores any object properties.
|
||||
func NewGetObjectReaderFromReader(r io.Reader, oi ObjectInfo, opts ObjectOptions, cleanupFns ...func()) (*GetObjectReader, error) {
|
||||
if opts.CheckCopyPrecondFn != nil {
|
||||
if ok := opts.CheckCopyPrecondFn(oi, ""); ok {
|
||||
// Call the cleanup funcs
|
||||
for i := len(cleanupFns) - 1; i >= 0; i-- {
|
||||
cleanupFns[i]()
|
||||
}
|
||||
return nil, PreConditionFailed{}
|
||||
if opts.CheckPrecondFn != nil && opts.CheckPrecondFn(oi) {
|
||||
// Call the cleanup funcs
|
||||
for i := len(cleanupFns) - 1; i >= 0; i-- {
|
||||
cleanupFns[i]()
|
||||
}
|
||||
return nil, PreConditionFailed{}
|
||||
}
|
||||
return &GetObjectReader{
|
||||
ObjInfo: oi,
|
||||
@@ -530,7 +546,7 @@ func NewGetObjectReaderFromReader(r io.Reader, oi ObjectInfo, opts ObjectOptions
|
||||
// GetObjectReader and an error. Request headers are passed to provide
|
||||
// encryption parameters. cleanupFns allow cleanup funcs to be
|
||||
// registered for calling after usage of the reader.
|
||||
type ObjReaderFn func(inputReader io.Reader, h http.Header, pcfn CheckCopyPreconditionFn, cleanupFns ...func()) (r *GetObjectReader, err error)
|
||||
type ObjReaderFn func(inputReader io.Reader, h http.Header, pcfn CheckPreconditionFn, cleanupFns ...func()) (r *GetObjectReader, err error)
|
||||
|
||||
// NewGetObjectReader creates a new GetObjectReader. The cleanUpFns
|
||||
// are called on Close() in reverse order as passed here. NOTE: It is
|
||||
@@ -581,7 +597,7 @@ func NewGetObjectReader(rs *HTTPRangeSpec, oi ObjectInfo, opts ObjectOptions, cl
|
||||
// a reader that returns the desired range of
|
||||
// encrypted bytes. The header parameter is used to
|
||||
// provide encryption parameters.
|
||||
fn = func(inputReader io.Reader, h http.Header, pcfn CheckCopyPreconditionFn, cFns ...func()) (r *GetObjectReader, err error) {
|
||||
fn = func(inputReader io.Reader, h http.Header, pcfn CheckPreconditionFn, cFns ...func()) (r *GetObjectReader, err error) {
|
||||
copySource := h.Get(crypto.SSECopyAlgorithm) != ""
|
||||
|
||||
cFns = append(cleanUpFns, cFns...)
|
||||
@@ -596,17 +612,14 @@ func NewGetObjectReader(rs *HTTPRangeSpec, oi ObjectInfo, opts ObjectOptions, cl
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
encETag := oi.ETag
|
||||
oi.ETag = getDecryptedETag(h, oi, copySource) // Decrypt the ETag before top layer consumes this value.
|
||||
|
||||
if opts.CheckCopyPrecondFn != nil {
|
||||
if ok := opts.CheckCopyPrecondFn(oi, encETag); ok {
|
||||
// Call the cleanup funcs
|
||||
for i := len(cFns) - 1; i >= 0; i-- {
|
||||
cFns[i]()
|
||||
}
|
||||
return nil, PreConditionFailed{}
|
||||
if opts.CheckPrecondFn != nil && opts.CheckPrecondFn(oi) {
|
||||
// Call the cleanup funcs
|
||||
for i := len(cFns) - 1; i >= 0; i-- {
|
||||
cFns[i]()
|
||||
}
|
||||
return nil, PreConditionFailed{}
|
||||
}
|
||||
|
||||
// Apply the skipLen and limit on the
|
||||
@@ -650,16 +663,14 @@ func NewGetObjectReader(rs *HTTPRangeSpec, oi ObjectInfo, opts ObjectOptions, cl
|
||||
return nil, 0, 0, errInvalidRange
|
||||
}
|
||||
}
|
||||
fn = func(inputReader io.Reader, _ http.Header, pcfn CheckCopyPreconditionFn, cFns ...func()) (r *GetObjectReader, err error) {
|
||||
fn = func(inputReader io.Reader, _ http.Header, pcfn CheckPreconditionFn, cFns ...func()) (r *GetObjectReader, err error) {
|
||||
cFns = append(cleanUpFns, cFns...)
|
||||
if opts.CheckCopyPrecondFn != nil {
|
||||
if ok := opts.CheckCopyPrecondFn(oi, ""); ok {
|
||||
// Call the cleanup funcs
|
||||
for i := len(cFns) - 1; i >= 0; i-- {
|
||||
cFns[i]()
|
||||
}
|
||||
return nil, PreConditionFailed{}
|
||||
if opts.CheckPrecondFn != nil && opts.CheckPrecondFn(oi) {
|
||||
// Call the cleanup funcs
|
||||
for i := len(cFns) - 1; i >= 0; i-- {
|
||||
cFns[i]()
|
||||
}
|
||||
return nil, PreConditionFailed{}
|
||||
}
|
||||
// Decompression reader.
|
||||
s2Reader := s2.NewReader(inputReader)
|
||||
@@ -700,16 +711,14 @@ func NewGetObjectReader(rs *HTTPRangeSpec, oi ObjectInfo, opts ObjectOptions, cl
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
fn = func(inputReader io.Reader, _ http.Header, pcfn CheckCopyPreconditionFn, cFns ...func()) (r *GetObjectReader, err error) {
|
||||
fn = func(inputReader io.Reader, _ http.Header, pcfn CheckPreconditionFn, cFns ...func()) (r *GetObjectReader, err error) {
|
||||
cFns = append(cleanUpFns, cFns...)
|
||||
if opts.CheckCopyPrecondFn != nil {
|
||||
if ok := opts.CheckCopyPrecondFn(oi, ""); ok {
|
||||
// Call the cleanup funcs
|
||||
for i := len(cFns) - 1; i >= 0; i-- {
|
||||
cFns[i]()
|
||||
}
|
||||
return nil, PreConditionFailed{}
|
||||
if opts.CheckPrecondFn != nil && opts.CheckPrecondFn(oi) {
|
||||
// Call the cleanup funcs
|
||||
for i := len(cFns) - 1; i >= 0; i-- {
|
||||
cFns[i]()
|
||||
}
|
||||
return nil, PreConditionFailed{}
|
||||
}
|
||||
r = &GetObjectReader{
|
||||
ObjInfo: oi,
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/pkg/bucket/lifecycle"
|
||||
"github.com/minio/minio/pkg/event"
|
||||
@@ -41,8 +40,8 @@ var (
|
||||
// x-amz-copy-source-if-unmodified-since
|
||||
// x-amz-copy-source-if-match
|
||||
// x-amz-copy-source-if-none-match
|
||||
func checkCopyObjectPartPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Request, objInfo ObjectInfo, encETag string) bool {
|
||||
return checkCopyObjectPreconditions(ctx, w, r, objInfo, encETag)
|
||||
func checkCopyObjectPartPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Request, objInfo ObjectInfo) bool {
|
||||
return checkCopyObjectPreconditions(ctx, w, r, objInfo)
|
||||
}
|
||||
|
||||
// Validates the preconditions for CopyObject, returns true if CopyObject operation should not proceed.
|
||||
@@ -51,14 +50,11 @@ func checkCopyObjectPartPreconditions(ctx context.Context, w http.ResponseWriter
|
||||
// x-amz-copy-source-if-unmodified-since
|
||||
// x-amz-copy-source-if-match
|
||||
// x-amz-copy-source-if-none-match
|
||||
func checkCopyObjectPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Request, objInfo ObjectInfo, encETag string) bool {
|
||||
func checkCopyObjectPreconditions(ctx context.Context, w http.ResponseWriter, r *http.Request, objInfo ObjectInfo) bool {
|
||||
// Return false for methods other than GET and HEAD.
|
||||
if r.Method != http.MethodPut {
|
||||
return false
|
||||
}
|
||||
if encETag == "" {
|
||||
encETag = objInfo.ETag
|
||||
}
|
||||
// If the object doesn't have a modtime (IsZero), or the modtime
|
||||
// is obviously garbage (Unix time == 0), then ignore modtimes
|
||||
// and don't process the If-Modified-Since header.
|
||||
@@ -106,17 +102,11 @@ func checkCopyObjectPreconditions(ctx context.Context, w http.ResponseWriter, r
|
||||
}
|
||||
}
|
||||
|
||||
shouldDecryptEtag := crypto.SSECopy.IsRequested(r.Header) && !crypto.IsMultiPart(objInfo.UserDefined)
|
||||
|
||||
// x-amz-copy-source-if-match : Return the object only if its entity tag (ETag) is the
|
||||
// same as the one specified; otherwise return a 412 (precondition failed).
|
||||
ifMatchETagHeader := r.Header.Get(xhttp.AmzCopySourceIfMatch)
|
||||
if ifMatchETagHeader != "" {
|
||||
etag := objInfo.ETag
|
||||
if shouldDecryptEtag {
|
||||
etag = encETag[len(encETag)-32:]
|
||||
}
|
||||
if objInfo.ETag != "" && !isETagEqual(etag, ifMatchETagHeader) {
|
||||
if !isETagEqual(objInfo.ETag, ifMatchETagHeader) {
|
||||
// If the object ETag does not match with the specified ETag.
|
||||
writeHeaders()
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPreconditionFailed), r.URL, guessIsBrowserReq(r))
|
||||
@@ -128,11 +118,7 @@ func checkCopyObjectPreconditions(ctx context.Context, w http.ResponseWriter, r
|
||||
// one specified otherwise, return a 304 (not modified).
|
||||
ifNoneMatchETagHeader := r.Header.Get(xhttp.AmzCopySourceIfNoneMatch)
|
||||
if ifNoneMatchETagHeader != "" {
|
||||
etag := objInfo.ETag
|
||||
if shouldDecryptEtag {
|
||||
etag = encETag[len(encETag)-32:]
|
||||
}
|
||||
if objInfo.ETag != "" && isETagEqual(etag, ifNoneMatchETagHeader) {
|
||||
if isETagEqual(objInfo.ETag, ifNoneMatchETagHeader) {
|
||||
// If the object ETag matches with the specified ETag.
|
||||
writeHeaders()
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPreconditionFailed), r.URL, guessIsBrowserReq(r))
|
||||
|
||||
+139
-86
@@ -33,9 +33,10 @@ import (
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
miniogo "github.com/minio/minio-go/v6"
|
||||
"github.com/minio/minio-go/v6/pkg/encrypt"
|
||||
"github.com/minio/minio-go/v6/pkg/tags"
|
||||
miniogo "github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/cmd/config/etcd/dns"
|
||||
"github.com/minio/minio/cmd/config/storageclass"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
@@ -212,7 +213,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
|
||||
objInfo.UserDefined = objectlock.FilterObjectLockMetadata(objInfo.UserDefined, getRetPerms != ErrNone, legalHoldPerms != ErrNone)
|
||||
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err = DecryptObjectInfo(&objInfo, r.Header); err != nil {
|
||||
if _, err = DecryptObjectInfo(&objInfo, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
@@ -370,23 +371,43 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
|
||||
// Get request range.
|
||||
var rs *HTTPRangeSpec
|
||||
var rangeErr error
|
||||
rangeHeader := r.Header.Get(xhttp.Range)
|
||||
if rangeHeader != "" {
|
||||
if rs, err = parseRequestRangeSpec(rangeHeader); err != nil {
|
||||
// Handle only errInvalidRange. Ignore other
|
||||
// parse error and treat it as regular Get
|
||||
// request like Amazon S3.
|
||||
if err == errInvalidRange {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRange), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
rs, rangeErr = parseRequestRangeSpec(rangeHeader)
|
||||
}
|
||||
|
||||
logger.LogIf(ctx, err, logger.Application)
|
||||
// Validate pre-conditions if any.
|
||||
opts.CheckPrecondFn = func(oi ObjectInfo) bool {
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err := DecryptObjectInfo(&oi, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if checkPreconditions(ctx, w, r, oi, opts) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Handle only errInvalidRange. Ignore other
|
||||
// parse error and treat it as regular Get
|
||||
// request like Amazon S3.
|
||||
if rangeErr == errInvalidRange {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRange), r.URL, guessIsBrowserReq(r))
|
||||
return true
|
||||
}
|
||||
if rangeErr != nil {
|
||||
logger.LogIf(ctx, rangeErr, logger.Application)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
gr, err := getObjectNInfo(ctx, bucket, object, rs, r.Header, readLock, opts)
|
||||
if err != nil {
|
||||
if isErrPreconditionFailed(err) {
|
||||
return
|
||||
}
|
||||
if globalBucketVersioningSys.Enabled(bucket) && gr != nil {
|
||||
// Versioning enabled quite possibly object is deleted might be delete-marker
|
||||
// if present set the headers, no idea why AWS S3 sets these headers.
|
||||
@@ -408,18 +429,6 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
// filter object lock metadata if permission does not permit
|
||||
objInfo.UserDefined = objectlock.FilterObjectLockMetadata(objInfo.UserDefined, getRetPerms != ErrNone, legalHoldPerms != ErrNone)
|
||||
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err = DecryptObjectInfo(&objInfo, r.Header); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Validate pre-conditions if any.
|
||||
if checkPreconditions(ctx, w, r, objInfo, opts) {
|
||||
return
|
||||
}
|
||||
|
||||
// Set encryption response headers
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if crypto.IsEncrypted(objInfo.UserDefined) {
|
||||
@@ -454,7 +463,8 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
|
||||
// Write object content to response body
|
||||
if _, err = io.Copy(httpWriter, gr); err != nil {
|
||||
if !httpWriter.HasWritten() && !statusCodeWritten { // write error response only if no data or headers has been written to client yet
|
||||
if !httpWriter.HasWritten() && !statusCodeWritten {
|
||||
// write error response only if no data or headers has been written to client yet
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
}
|
||||
return
|
||||
@@ -550,23 +560,6 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
// Get request range.
|
||||
var rs *HTTPRangeSpec
|
||||
rangeHeader := r.Header.Get(xhttp.Range)
|
||||
if rangeHeader != "" {
|
||||
if rs, err = parseRequestRangeSpec(rangeHeader); err != nil {
|
||||
// Handle only errInvalidRange. Ignore other
|
||||
// parse error and treat it as regular Get
|
||||
// request like Amazon S3.
|
||||
if err == errInvalidRange {
|
||||
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrInvalidRange))
|
||||
return
|
||||
}
|
||||
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
objInfo, err := getObjectInfo(ctx, bucket, object, opts)
|
||||
if err != nil {
|
||||
if globalBucketVersioningSys.Enabled(bucket) {
|
||||
@@ -589,14 +582,40 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
objInfo.UserDefined = objectlock.FilterObjectLockMetadata(objInfo.UserDefined, getRetPerms != ErrNone, legalHoldPerms != ErrNone)
|
||||
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err = DecryptObjectInfo(&objInfo, r.Header); err != nil {
|
||||
if _, err = DecryptObjectInfo(&objInfo, r); err != nil {
|
||||
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Validate pre-conditions if any.
|
||||
if checkPreconditions(ctx, w, r, objInfo, opts) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get request range.
|
||||
var rs *HTTPRangeSpec
|
||||
rangeHeader := r.Header.Get(xhttp.Range)
|
||||
if rangeHeader != "" {
|
||||
if rs, err = parseRequestRangeSpec(rangeHeader); err != nil {
|
||||
// Handle only errInvalidRange. Ignore other
|
||||
// parse error and treat it as regular Get
|
||||
// request like Amazon S3.
|
||||
if err == errInvalidRange {
|
||||
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrInvalidRange))
|
||||
return
|
||||
}
|
||||
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Set encryption response headers
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err = DecryptObjectInfo(&objInfo, r); err != nil {
|
||||
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||
return
|
||||
}
|
||||
if crypto.IsEncrypted(objInfo.UserDefined) {
|
||||
switch {
|
||||
case crypto.S3.IsEncrypted(objInfo.UserDefined):
|
||||
@@ -613,11 +632,6 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
}
|
||||
|
||||
// Validate pre-conditions if any.
|
||||
if checkPreconditions(ctx, w, r, objInfo, opts) {
|
||||
return
|
||||
}
|
||||
|
||||
// Set standard object headers.
|
||||
if err = setObjectHeaders(w, objInfo, rs); err != nil {
|
||||
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||
@@ -700,25 +714,29 @@ func getCpObjMetadataFromHeader(ctx context.Context, r *http.Request, userMeta m
|
||||
}
|
||||
|
||||
// getRemoteInstanceTransport contains a singleton roundtripper.
|
||||
var getRemoteInstanceTransport http.RoundTripper
|
||||
var getRemoteInstanceTransportLongTO http.RoundTripper
|
||||
var getRemoteInstanceTransport *http.Transport
|
||||
var getRemoteInstanceTransportLongTO *http.Transport
|
||||
var getRemoteInstanceTransportOnce sync.Once
|
||||
|
||||
// Returns a minio-go Client configured to access remote host described by destDNSRecord
|
||||
// Applicable only in a federated deployment
|
||||
var getRemoteInstanceClient = func(r *http.Request, host string) (*miniogo.Core, error) {
|
||||
cred := getReqAccessCred(r, globalServerRegion)
|
||||
// In a federated deployment, all the instances share config files
|
||||
// and hence expected to have same credentials.
|
||||
core, err := miniogo.NewCore(host, cred.AccessKey, cred.SecretKey, globalIsSSL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
getRemoteInstanceTransportOnce.Do(func() {
|
||||
getRemoteInstanceTransport = NewGatewayHTTPTransport()
|
||||
getRemoteInstanceTransportLongTO = newGatewayHTTPTransport(time.Hour)
|
||||
})
|
||||
core.SetCustomTransport(getRemoteInstanceTransport)
|
||||
|
||||
cred := getReqAccessCred(r, globalServerRegion)
|
||||
// In a federated deployment, all the instances share config files
|
||||
// and hence expected to have same credentials.
|
||||
core, err := miniogo.NewCore(host, &miniogo.Options{
|
||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, ""),
|
||||
Secure: globalIsSSL,
|
||||
Transport: getRemoteInstanceTransport,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return core, nil
|
||||
}
|
||||
|
||||
@@ -726,18 +744,22 @@ var getRemoteInstanceClient = func(r *http.Request, host string) (*miniogo.Core,
|
||||
// Applicable only in a federated deployment.
|
||||
// The transport does not contain any timeout except for dialing.
|
||||
func getRemoteInstanceClientLongTimeout(r *http.Request, host string) (*miniogo.Core, error) {
|
||||
cred := getReqAccessCred(r, globalServerRegion)
|
||||
// In a federated deployment, all the instances share config files
|
||||
// and hence expected to have same credentials.
|
||||
core, err := miniogo.NewCore(host, cred.AccessKey, cred.SecretKey, globalIsSSL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
getRemoteInstanceTransportOnce.Do(func() {
|
||||
getRemoteInstanceTransport = NewGatewayHTTPTransport()
|
||||
getRemoteInstanceTransportLongTO = newGatewayHTTPTransport(time.Hour)
|
||||
})
|
||||
core.SetCustomTransport(getRemoteInstanceTransportLongTO)
|
||||
|
||||
cred := getReqAccessCred(r, globalServerRegion)
|
||||
// In a federated deployment, all the instances share config files
|
||||
// and hence expected to have same credentials.
|
||||
core, err := miniogo.NewCore(host, &miniogo.Options{
|
||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, ""),
|
||||
Secure: globalIsSSL,
|
||||
Transport: getRemoteInstanceTransportLongTO,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return core, nil
|
||||
}
|
||||
|
||||
@@ -891,16 +913,25 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
cpSrcDstSame := isStringEqual(pathJoin(srcBucket, srcObject), pathJoin(dstBucket, dstObject))
|
||||
|
||||
getObjectNInfo := objectAPI.GetObjectNInfo
|
||||
if api.CacheAPI() != nil {
|
||||
getObjectNInfo = api.CacheAPI().GetObjectNInfo
|
||||
}
|
||||
|
||||
var lock = noLock
|
||||
if !cpSrcDstSame {
|
||||
lock = readLock
|
||||
}
|
||||
checkCopyPrecondFn := func(o ObjectInfo, encETag string) bool {
|
||||
return checkCopyObjectPreconditions(ctx, w, r, o, encETag)
|
||||
checkCopyPrecondFn := func(o ObjectInfo) bool {
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err := DecryptObjectInfo(&o, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return true
|
||||
}
|
||||
}
|
||||
return checkCopyObjectPreconditions(ctx, w, r, o)
|
||||
}
|
||||
getOpts.CheckCopyPrecondFn = checkCopyPrecondFn
|
||||
srcOpts.CheckCopyPrecondFn = checkCopyPrecondFn
|
||||
getOpts.CheckPrecondFn = checkCopyPrecondFn
|
||||
|
||||
var rs *HTTPRangeSpec
|
||||
gr, err := getObjectNInfo(ctx, srcBucket, srcObject, rs, r.Header, lock, getOpts)
|
||||
if err != nil {
|
||||
@@ -1202,7 +1233,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
ServerSideEncryption: dstOpts.ServerSideEncryption,
|
||||
UserTags: tag.ToMap(),
|
||||
}
|
||||
remoteObjInfo, rerr := core.PutObjectWithContext(ctx, dstBucket, dstObject, srcInfo.Reader,
|
||||
remoteObjInfo, rerr := core.PutObject(ctx, dstBucket, dstObject, srcInfo.Reader,
|
||||
srcInfo.Size, "", "", opts)
|
||||
if rerr != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, rerr), r.URL, guessIsBrowserReq(r))
|
||||
@@ -1783,22 +1814,31 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
|
||||
// Get request range.
|
||||
var rs *HTTPRangeSpec
|
||||
rangeHeader := r.Header.Get(xhttp.AmzCopySourceRange)
|
||||
if rangeHeader != "" {
|
||||
var parseRangeErr error
|
||||
if rs, parseRangeErr = parseCopyPartRangeSpec(rangeHeader); parseRangeErr != nil {
|
||||
logger.GetReqInfo(ctx).AppendTags("rangeHeader", rangeHeader)
|
||||
var parseRangeErr error
|
||||
if rangeHeader := r.Header.Get(xhttp.AmzCopySourceRange); rangeHeader != "" {
|
||||
rs, parseRangeErr = parseCopyPartRangeSpec(rangeHeader)
|
||||
}
|
||||
|
||||
checkCopyPartPrecondFn := func(o ObjectInfo) bool {
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err := DecryptObjectInfo(&o, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return true
|
||||
}
|
||||
}
|
||||
if checkCopyObjectPartPreconditions(ctx, w, r, o) {
|
||||
return true
|
||||
}
|
||||
if parseRangeErr != nil {
|
||||
logger.LogIf(ctx, parseRangeErr)
|
||||
writeCopyPartErr(ctx, w, parseRangeErr, r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
|
||||
// Range header mismatch is pre-condition like failure
|
||||
// so return true to indicate Range precondition failed.
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
checkCopyPartPrecondFn := func(o ObjectInfo, encETag string) bool {
|
||||
return checkCopyObjectPartPreconditions(ctx, w, r, o, encETag)
|
||||
}
|
||||
getOpts.CheckCopyPrecondFn = checkCopyPartPrecondFn
|
||||
srcOpts.CheckCopyPrecondFn = checkCopyPartPrecondFn
|
||||
getOpts.CheckPrecondFn = checkCopyPartPrecondFn
|
||||
|
||||
gr, err := getObjectNInfo(ctx, srcBucket, srcObject, rs, r.Header, readLock, getOpts)
|
||||
if err != nil {
|
||||
@@ -1865,7 +1905,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
return
|
||||
}
|
||||
|
||||
partInfo, err := core.PutObjectPartWithContext(ctx, dstBucket, dstObject, uploadID, partID,
|
||||
partInfo, err := core.PutObjectPart(ctx, dstBucket, dstObject, uploadID, partID,
|
||||
srcInfo.Reader, srcInfo.Size, "", "", dstOpts.ServerSideEncryption)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
@@ -3009,6 +3049,7 @@ func (api objectAPIHandlers) GetObjectTaggingHandler(w http.ResponseWriter, r *h
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
// Allow getObjectTagging if policy action is set.
|
||||
if s3Error := checkRequestAuthType(ctx, r, policy.GetObjectTaggingAction, bucket, object); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
|
||||
@@ -3028,6 +3069,10 @@ func (api objectAPIHandlers) GetObjectTaggingHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
if opts.VersionID != "" {
|
||||
w.Header()[xhttp.AmzVersionID] = []string{opts.VersionID}
|
||||
}
|
||||
|
||||
writeSuccessResponseXML(w, encodeResponse(tags))
|
||||
}
|
||||
|
||||
@@ -3079,6 +3124,10 @@ func (api objectAPIHandlers) PutObjectTaggingHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
if opts.VersionID != "" {
|
||||
w.Header()[xhttp.AmzVersionID] = []string{opts.VersionID}
|
||||
}
|
||||
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
|
||||
@@ -3118,10 +3167,14 @@ func (api objectAPIHandlers) DeleteObjectTaggingHandler(w http.ResponseWriter, r
|
||||
}
|
||||
|
||||
// Delete object tags
|
||||
if err = objAPI.DeleteObjectTags(ctx, bucket, object, opts); err != nil && err != errConfigNotFound {
|
||||
if err = objAPI.DeleteObjectTags(ctx, bucket, object, opts); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
|
||||
if opts.VersionID != "" {
|
||||
w.Header()[xhttp.AmzVersionID] = []string{opts.VersionID}
|
||||
}
|
||||
|
||||
writeSuccessNoContent(w)
|
||||
}
|
||||
|
||||
+7
-13
@@ -846,19 +846,14 @@ func newPeerRestClients(endpoints EndpointZones) []*peerRESTClient {
|
||||
peerHosts := getRemoteHosts(endpoints)
|
||||
restClients := make([]*peerRESTClient, len(peerHosts))
|
||||
for i, host := range peerHosts {
|
||||
client, err := newPeerRESTClient(host)
|
||||
if err != nil {
|
||||
logger.LogIf(GlobalContext, err)
|
||||
continue
|
||||
}
|
||||
restClients[i] = client
|
||||
restClients[i] = newPeerRESTClient(host)
|
||||
}
|
||||
|
||||
return restClients
|
||||
}
|
||||
|
||||
// Returns a peer rest client.
|
||||
func newPeerRESTClient(peer *xnet.Host) (*peerRESTClient, error) {
|
||||
func newPeerRESTClient(peer *xnet.Host) *peerRESTClient {
|
||||
scheme := "http"
|
||||
if globalIsSSL {
|
||||
scheme = "https"
|
||||
@@ -879,20 +874,19 @@ func newPeerRESTClient(peer *xnet.Host) (*peerRESTClient, error) {
|
||||
}
|
||||
|
||||
trFn := newCustomHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)
|
||||
restClient, err := rest.NewClient(serverURL, trFn, newAuthToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
restClient := rest.NewClient(serverURL, trFn, newAuthToken)
|
||||
|
||||
// Construct a new health function.
|
||||
restClient.HealthCheckFn = func() bool {
|
||||
ctx, cancel := context.WithTimeout(GlobalContext, restClient.HealthCheckTimeout)
|
||||
respBody, err := restClient.CallWithContext(ctx, peerRESTMethodHealth, nil, nil, -1)
|
||||
// Instantiate a new rest client for healthcheck
|
||||
// to avoid recursive healthCheckFn()
|
||||
respBody, err := rest.NewClient(serverURL, trFn, newAuthToken).CallWithContext(ctx, peerRESTMethodHealth, nil, nil, -1)
|
||||
xhttp.DrainBody(respBody)
|
||||
cancel()
|
||||
var ne *rest.NetworkError
|
||||
return !errors.Is(err, context.DeadlineExceeded) && !errors.As(err, &ne)
|
||||
}
|
||||
|
||||
return &peerRESTClient{host: peer, restClient: restClient}, nil
|
||||
return &peerRESTClient{host: peer, restClient: restClient}
|
||||
}
|
||||
|
||||
+2
-2
@@ -20,8 +20,8 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
miniogopolicy "github.com/minio/minio-go/v6/pkg/policy"
|
||||
"github.com/minio/minio-go/v6/pkg/set"
|
||||
miniogopolicy "github.com/minio/minio-go/v7/pkg/policy"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/bucket/policy/condition"
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
minio "github.com/minio/minio-go/v6"
|
||||
minio "github.com/minio/minio-go/v7"
|
||||
)
|
||||
|
||||
// Test Post Policy parsing and checking conditions
|
||||
|
||||
+38
-12
@@ -30,32 +30,44 @@ import (
|
||||
"github.com/minio/minio/cmd/config"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/cmd/rest"
|
||||
"github.com/minio/minio/pkg/sync/errgroup"
|
||||
)
|
||||
|
||||
var printEndpointError = func() func(Endpoint, error) {
|
||||
var printEndpointError = func() func(Endpoint, error, bool) {
|
||||
var mutex sync.Mutex
|
||||
printOnce := make(map[Endpoint]map[string]bool)
|
||||
printOnce := make(map[Endpoint]map[string]int)
|
||||
|
||||
return func(endpoint Endpoint, err error) {
|
||||
return func(endpoint Endpoint, err error, once bool) {
|
||||
reqInfo := (&logger.ReqInfo{}).AppendTags("endpoint", endpoint.String())
|
||||
ctx := logger.SetReqInfo(GlobalContext, reqInfo)
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
m, ok := printOnce[endpoint]
|
||||
if !ok {
|
||||
m = make(map[string]bool)
|
||||
m[err.Error()] = true
|
||||
m = make(map[string]int)
|
||||
m[err.Error()]++
|
||||
printOnce[endpoint] = m
|
||||
logger.LogAlwaysIf(ctx, err)
|
||||
if once {
|
||||
logger.LogAlwaysIf(ctx, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Once is set and we are here means error was already
|
||||
// printed once.
|
||||
if once {
|
||||
return
|
||||
}
|
||||
if m[err.Error()] {
|
||||
return
|
||||
// once not set, check if same error occurred 3 times in
|
||||
// a row, then make sure we print it to call attention.
|
||||
if m[err.Error()] > 2 {
|
||||
logger.LogAlwaysIf(ctx, fmt.Errorf("Following error has been printed %d times.. %w", m[err.Error()], err))
|
||||
// Reduce the count to introduce further delay in printing
|
||||
// but let it again print after the 2th attempt
|
||||
m[err.Error()]--
|
||||
m[err.Error()]--
|
||||
}
|
||||
m[err.Error()] = true
|
||||
logger.LogAlwaysIf(ctx, err)
|
||||
m[err.Error()]++
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -183,7 +195,21 @@ func IsServerResolvable(endpoint Endpoint) error {
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: newCustomHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)(),
|
||||
Transport:
|
||||
// For more details about various values used here refer
|
||||
// https://golang.org/pkg/net/http/#Transport documentation
|
||||
&http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: xhttp.NewCustomDialContext(3 * time.Second),
|
||||
ResponseHeaderTimeout: 5 * time.Second,
|
||||
TLSHandshakeTimeout: 5 * time.Second,
|
||||
ExpectContinueTimeout: 5 * time.Second,
|
||||
TLSClientConfig: tlsConfig,
|
||||
// Go net/http automatically unzip if content-type is
|
||||
// gzip disable this feature, as we are always interested
|
||||
// in raw stream.
|
||||
DisableCompression: true,
|
||||
},
|
||||
}
|
||||
defer httpClient.CloseIdleConnections()
|
||||
|
||||
|
||||
+2
-2
@@ -158,7 +158,7 @@ func (c *Client) Close() {
|
||||
}
|
||||
|
||||
// NewClient - returns new REST client.
|
||||
func NewClient(url *url.URL, newCustomTransport func() *http.Transport, newAuthToken func(aud string) string) (*Client, error) {
|
||||
func NewClient(url *url.URL, newCustomTransport func() *http.Transport, newAuthToken func(aud string) string) *Client {
|
||||
// Transport is exactly same as Go default in https://golang.org/pkg/net/http/#RoundTripper
|
||||
// except custom DialContext and TLSClientConfig.
|
||||
tr := newCustomTransport()
|
||||
@@ -172,7 +172,7 @@ func NewClient(url *url.URL, newCustomTransport func() *http.Transport, newAuthT
|
||||
MaxErrResponseSize: 4096,
|
||||
HealthCheckInterval: 200 * time.Millisecond,
|
||||
HealthCheckTimeout: time.Second,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsOnline returns whether the client is likely to be online.
|
||||
|
||||
+1
-2
@@ -360,7 +360,6 @@ func startBackgroundOps(ctx context.Context, objAPI ObjectLayer) {
|
||||
}
|
||||
|
||||
initDataCrawler(ctx, objAPI)
|
||||
initQuotaEnforcement(ctx, objAPI)
|
||||
}
|
||||
|
||||
// serverMain handler called for 'minio server' command.
|
||||
@@ -466,7 +465,7 @@ func serverMain(ctx *cli.Context) {
|
||||
}
|
||||
}()
|
||||
|
||||
httpServer := xhttp.NewServer([]string{globalMinioAddr}, criticalErrorHandler{handler}, getCert)
|
||||
httpServer := xhttp.NewServer([]string{globalMinioAddr}, criticalErrorHandler{corsHandler(handler)}, getCert)
|
||||
httpServer.ErrorLog = log.New(pw, "", 0)
|
||||
httpServer.BaseContext = func(listener net.Listener) context.Context {
|
||||
return GlobalContext
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
@@ -80,6 +80,9 @@ type FileInfo struct {
|
||||
// DataDir of the file
|
||||
DataDir string
|
||||
|
||||
// Indicates if this object is still in V1 format.
|
||||
XLV1 bool
|
||||
|
||||
// Date and time when the file was last modified, if Deleted
|
||||
// is 'true' this value represents when while was deleted.
|
||||
ModTime time.Time
|
||||
|
||||
@@ -190,7 +190,13 @@ func (client *storageRESTClient) DiskInfo() (info DiskInfo, err error) {
|
||||
}
|
||||
defer http.DrainBody(respBody)
|
||||
err = gob.NewDecoder(respBody).Decode(&info)
|
||||
return info, err
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
if info.Error != "" {
|
||||
return info, toStorageErr(errors.New(info.Error))
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// MakeVolBulk - create multiple volumes in a bulk operation.
|
||||
@@ -329,7 +335,7 @@ func (client *storageRESTClient) CheckParts(volume, path string, fi FileInfo) er
|
||||
return err
|
||||
}
|
||||
|
||||
respBody, err := client.call(storageRESTMethodWriteMetadata, values, &reader, -1)
|
||||
respBody, err := client.call(storageRESTMethodCheckParts, values, &reader, -1)
|
||||
defer http.DrainBody(respBody)
|
||||
return err
|
||||
}
|
||||
@@ -660,15 +666,13 @@ func newStorageRESTClient(endpoint Endpoint) *storageRESTClient {
|
||||
}
|
||||
|
||||
trFn := newCustomHTTPTransport(tlsConfig, rest.DefaultRESTTimeout)
|
||||
restClient, err := rest.NewClient(serverURL, trFn, newAuthToken)
|
||||
if err != nil {
|
||||
logger.Fatal(err, "Unable to initialize remote REST disks")
|
||||
}
|
||||
|
||||
restClient := rest.NewClient(serverURL, trFn, newAuthToken)
|
||||
restClient.HealthCheckInterval = 500 * time.Millisecond
|
||||
restClient.HealthCheckFn = func() bool {
|
||||
ctx, cancel := context.WithTimeout(GlobalContext, restClient.HealthCheckTimeout)
|
||||
respBody, err := restClient.CallWithContext(ctx, storageRESTMethodHealth, nil, nil, -1)
|
||||
// Instantiate a new rest client for healthcheck
|
||||
// to avoid recursive healthCheckFn()
|
||||
respBody, err := rest.NewClient(serverURL, trFn, newAuthToken).CallWithContext(ctx, storageRESTMethodHealth, nil, nil, -1)
|
||||
xhttp.DrainBody(respBody)
|
||||
cancel()
|
||||
return !errors.Is(err, context.DeadlineExceeded) && toStorageErr(err) != errDiskNotFound
|
||||
|
||||
@@ -134,8 +134,7 @@ func (s *storageRESTServer) DiskInfoHandler(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
info, err := s.storage.DiskInfo()
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
info.Error = err.Error()
|
||||
}
|
||||
defer w.(http.Flusher).Flush()
|
||||
gob.NewEncoder(w).Encode(info)
|
||||
|
||||
@@ -36,16 +36,10 @@ import (
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func testStorageAPIDiskInfo(t *testing.T, storage StorageAPI) {
|
||||
tmpGlobalServerConfig := globalServerConfig
|
||||
defer func() {
|
||||
globalServerConfig = tmpGlobalServerConfig
|
||||
}()
|
||||
globalServerConfig = newServerConfig()
|
||||
|
||||
testCases := []struct {
|
||||
expectErr bool
|
||||
}{
|
||||
{false},
|
||||
{true},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
@@ -55,16 +49,13 @@ func testStorageAPIDiskInfo(t *testing.T, storage StorageAPI) {
|
||||
if expectErr != testCase.expectErr {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
|
||||
}
|
||||
if err != errUnformattedDisk {
|
||||
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, errUnformattedDisk, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testStorageAPIMakeVol(t *testing.T, storage StorageAPI) {
|
||||
tmpGlobalServerConfig := globalServerConfig
|
||||
defer func() {
|
||||
globalServerConfig = tmpGlobalServerConfig
|
||||
}()
|
||||
globalServerConfig = newServerConfig()
|
||||
|
||||
testCases := []struct {
|
||||
volumeName string
|
||||
expectErr bool
|
||||
@@ -85,12 +76,6 @@ func testStorageAPIMakeVol(t *testing.T, storage StorageAPI) {
|
||||
}
|
||||
|
||||
func testStorageAPIListVols(t *testing.T, storage StorageAPI) {
|
||||
tmpGlobalServerConfig := globalServerConfig
|
||||
defer func() {
|
||||
globalServerConfig = tmpGlobalServerConfig
|
||||
}()
|
||||
globalServerConfig = newServerConfig()
|
||||
|
||||
testCases := []struct {
|
||||
volumeNames []string
|
||||
expectedResult []VolInfo
|
||||
@@ -124,12 +109,6 @@ func testStorageAPIListVols(t *testing.T, storage StorageAPI) {
|
||||
}
|
||||
|
||||
func testStorageAPIStatVol(t *testing.T, storage StorageAPI) {
|
||||
tmpGlobalServerConfig := globalServerConfig
|
||||
defer func() {
|
||||
globalServerConfig = tmpGlobalServerConfig
|
||||
}()
|
||||
globalServerConfig = newServerConfig()
|
||||
|
||||
err := storage.MakeVol("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
@@ -161,12 +140,6 @@ func testStorageAPIStatVol(t *testing.T, storage StorageAPI) {
|
||||
}
|
||||
|
||||
func testStorageAPIDeleteVol(t *testing.T, storage StorageAPI) {
|
||||
tmpGlobalServerConfig := globalServerConfig
|
||||
defer func() {
|
||||
globalServerConfig = tmpGlobalServerConfig
|
||||
}()
|
||||
globalServerConfig = newServerConfig()
|
||||
|
||||
err := storage.MakeVol("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
@@ -192,12 +165,6 @@ func testStorageAPIDeleteVol(t *testing.T, storage StorageAPI) {
|
||||
}
|
||||
|
||||
func testStorageAPICheckFile(t *testing.T, storage StorageAPI) {
|
||||
tmpGlobalServerConfig := globalServerConfig
|
||||
defer func() {
|
||||
globalServerConfig = tmpGlobalServerConfig
|
||||
}()
|
||||
globalServerConfig = newServerConfig()
|
||||
|
||||
err := storage.MakeVol("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
@@ -228,12 +195,6 @@ func testStorageAPICheckFile(t *testing.T, storage StorageAPI) {
|
||||
}
|
||||
|
||||
func testStorageAPIListDir(t *testing.T, storage StorageAPI) {
|
||||
tmpGlobalServerConfig := globalServerConfig
|
||||
defer func() {
|
||||
globalServerConfig = tmpGlobalServerConfig
|
||||
}()
|
||||
globalServerConfig = newServerConfig()
|
||||
|
||||
err := storage.MakeVol("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
@@ -271,12 +232,6 @@ func testStorageAPIListDir(t *testing.T, storage StorageAPI) {
|
||||
}
|
||||
|
||||
func testStorageAPIReadAll(t *testing.T, storage StorageAPI) {
|
||||
tmpGlobalServerConfig := globalServerConfig
|
||||
defer func() {
|
||||
globalServerConfig = tmpGlobalServerConfig
|
||||
}()
|
||||
globalServerConfig = newServerConfig()
|
||||
|
||||
err := storage.MakeVol("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
@@ -314,12 +269,6 @@ func testStorageAPIReadAll(t *testing.T, storage StorageAPI) {
|
||||
}
|
||||
|
||||
func testStorageAPIReadFile(t *testing.T, storage StorageAPI) {
|
||||
tmpGlobalServerConfig := globalServerConfig
|
||||
defer func() {
|
||||
globalServerConfig = tmpGlobalServerConfig
|
||||
}()
|
||||
globalServerConfig = newServerConfig()
|
||||
|
||||
err := storage.MakeVol("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
@@ -361,12 +310,6 @@ func testStorageAPIReadFile(t *testing.T, storage StorageAPI) {
|
||||
}
|
||||
|
||||
func testStorageAPIAppendFile(t *testing.T, storage StorageAPI) {
|
||||
tmpGlobalServerConfig := globalServerConfig
|
||||
defer func() {
|
||||
globalServerConfig = tmpGlobalServerConfig
|
||||
}()
|
||||
globalServerConfig = newServerConfig()
|
||||
|
||||
err := storage.MakeVol("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
@@ -395,12 +338,6 @@ func testStorageAPIAppendFile(t *testing.T, storage StorageAPI) {
|
||||
}
|
||||
|
||||
func testStorageAPIDeleteFile(t *testing.T, storage StorageAPI) {
|
||||
tmpGlobalServerConfig := globalServerConfig
|
||||
defer func() {
|
||||
globalServerConfig = tmpGlobalServerConfig
|
||||
}()
|
||||
globalServerConfig = newServerConfig()
|
||||
|
||||
err := storage.MakeVol("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
@@ -434,12 +371,6 @@ func testStorageAPIDeleteFile(t *testing.T, storage StorageAPI) {
|
||||
}
|
||||
|
||||
func testStorageAPIRenameFile(t *testing.T, storage StorageAPI) {
|
||||
tmpGlobalServerConfig := globalServerConfig
|
||||
defer func() {
|
||||
globalServerConfig = tmpGlobalServerConfig
|
||||
}()
|
||||
globalServerConfig = newServerConfig()
|
||||
|
||||
err := storage.MakeVol("foo")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
@@ -518,9 +449,11 @@ func newStorageRESTHTTPServerClient(t *testing.T) (*httptest.Server, *storageRES
|
||||
Endpoints: Endpoints{endpoint},
|
||||
}})
|
||||
|
||||
restClient := newStorageRESTClient(endpoint)
|
||||
prevGlobalServerConfig := globalServerConfig
|
||||
globalServerConfig = newServerConfig()
|
||||
lookupConfigs(globalServerConfig)
|
||||
|
||||
restClient := newStorageRESTClient(endpoint)
|
||||
|
||||
return httpServer, restClient, prevGlobalServerConfig, endpointPath
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v6/pkg/signer"
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7/pkg/signer"
|
||||
"github.com/minio/minio/cmd/config"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
@@ -66,6 +66,13 @@ import (
|
||||
|
||||
// Tests should initNSLock only once.
|
||||
func init() {
|
||||
globalActiveCred = auth.Credentials{
|
||||
AccessKey: auth.DefaultAccessKey,
|
||||
SecretKey: auth.DefaultSecretKey,
|
||||
}
|
||||
|
||||
globalConfigEncrypted = true
|
||||
|
||||
// disable ENVs which interfere with tests.
|
||||
for _, env := range []string{
|
||||
crypto.EnvAutoEncryptionLegacy,
|
||||
@@ -463,13 +470,6 @@ func newTestConfig(bucketLocation string, obj ObjectLayer) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
globalActiveCred = auth.Credentials{
|
||||
AccessKey: auth.DefaultAccessKey,
|
||||
SecretKey: auth.DefaultSecretKey,
|
||||
}
|
||||
|
||||
globalConfigEncrypted = true
|
||||
|
||||
// Set a default region.
|
||||
config.SetRegion(globalServerConfig, bucketLocation)
|
||||
|
||||
|
||||
+16
-12
@@ -35,8 +35,10 @@ import (
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/rpc/v2/json2"
|
||||
"github.com/klauspost/compress/zip"
|
||||
miniogopolicy "github.com/minio/minio-go/v6/pkg/policy"
|
||||
"github.com/minio/minio-go/v6/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7"
|
||||
miniogo "github.com/minio/minio-go/v7"
|
||||
miniogopolicy "github.com/minio/minio-go/v7/pkg/policy"
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
"github.com/minio/minio/browser"
|
||||
"github.com/minio/minio/cmd/config/etcd/dns"
|
||||
"github.com/minio/minio/cmd/config/identity/openid"
|
||||
@@ -252,7 +254,7 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs,
|
||||
if err != nil {
|
||||
return toJSONError(ctx, err, args.BucketName)
|
||||
}
|
||||
if err = core.RemoveBucket(args.BucketName); err != nil {
|
||||
if err = core.RemoveBucket(ctx, args.BucketName); err != nil {
|
||||
return toJSONError(ctx, err, args.BucketName)
|
||||
}
|
||||
return nil
|
||||
@@ -636,18 +638,20 @@ func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs,
|
||||
if err != nil {
|
||||
return toJSONError(ctx, err, args.BucketName)
|
||||
}
|
||||
objectsCh := make(chan string)
|
||||
objectsCh := make(chan miniogo.ObjectInfo)
|
||||
|
||||
// Send object names that are needed to be removed to objectsCh
|
||||
go func() {
|
||||
defer close(objectsCh)
|
||||
|
||||
for _, objectName := range args.Objects {
|
||||
objectsCh <- objectName
|
||||
objectsCh <- miniogo.ObjectInfo{
|
||||
Key: objectName,
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for resp := range core.RemoveObjects(args.BucketName, objectsCh) {
|
||||
for resp := range core.RemoveObjects(ctx, args.BucketName, objectsCh, minio.RemoveObjectsOptions{}) {
|
||||
if resp.Err != nil {
|
||||
return toJSONError(ctx, resp.Err, args.BucketName, resp.ObjectName)
|
||||
}
|
||||
@@ -1265,7 +1269,7 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
|
||||
objInfo.UserDefined = objectlock.FilterObjectLockMetadata(objInfo.UserDefined, getRetPerms != ErrNone, legalHoldPerms != ErrNone)
|
||||
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err = DecryptObjectInfo(&objInfo, r.Header); err != nil {
|
||||
if _, err = DecryptObjectInfo(&objInfo, r); err != nil {
|
||||
writeWebErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
@@ -1618,7 +1622,7 @@ func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolic
|
||||
if rerr != nil {
|
||||
return toJSONError(ctx, rerr, args.BucketName)
|
||||
}
|
||||
policyStr, err := client.GetBucketPolicy(args.BucketName)
|
||||
policyStr, err := client.GetBucketPolicy(ctx, args.BucketName)
|
||||
if err != nil {
|
||||
return toJSONError(ctx, rerr, args.BucketName)
|
||||
}
|
||||
@@ -1716,7 +1720,7 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB
|
||||
return toJSONError(ctx, rerr, args.BucketName)
|
||||
}
|
||||
var policyStr string
|
||||
policyStr, err = core.Client.GetBucketPolicy(args.BucketName)
|
||||
policyStr, err = core.Client.GetBucketPolicy(ctx, args.BucketName)
|
||||
if err != nil {
|
||||
return toJSONError(ctx, err, args.BucketName)
|
||||
}
|
||||
@@ -1815,7 +1819,7 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic
|
||||
var policyStr string
|
||||
// Use the abstracted API instead of core, such that
|
||||
// NoSuchBucketPolicy errors are automatically handled.
|
||||
policyStr, err = core.Client.GetBucketPolicy(args.BucketName)
|
||||
policyStr, err = core.Client.GetBucketPolicy(ctx, args.BucketName)
|
||||
if err != nil {
|
||||
return toJSONError(ctx, err, args.BucketName)
|
||||
}
|
||||
@@ -1828,7 +1832,7 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic
|
||||
|
||||
policyInfo.Statements = miniogopolicy.SetPolicy(policyInfo.Statements, policyType, args.BucketName, args.Prefix)
|
||||
if len(policyInfo.Statements) == 0 {
|
||||
if err = core.SetBucketPolicy(args.BucketName, ""); err != nil {
|
||||
if err = core.SetBucketPolicy(ctx, args.BucketName, ""); err != nil {
|
||||
return toJSONError(ctx, err, args.BucketName)
|
||||
}
|
||||
return nil
|
||||
@@ -1845,7 +1849,7 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic
|
||||
return toJSONError(ctx, err, args.BucketName)
|
||||
}
|
||||
|
||||
if err = core.SetBucketPolicy(args.BucketName, string(policyData)); err != nil {
|
||||
if err = core.SetBucketPolicy(ctx, args.BucketName, string(policyData)); err != nil {
|
||||
return toJSONError(ctx, err, args.BucketName)
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ func (p *xlStorageDiskIDCheck) Hostname() string {
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) CrawlAndGetDataUsage(ctx context.Context, cache dataUsageCache) (dataUsageCache, error) {
|
||||
if p.isDiskStale() {
|
||||
return dataUsageCache{}, errDiskNotFound
|
||||
if err := p.checkDiskStale(); err != nil {
|
||||
return dataUsageCache{}, err
|
||||
}
|
||||
return p.storage.CrawlAndGetDataUsage(ctx, cache)
|
||||
}
|
||||
@@ -66,157 +66,182 @@ func (p *xlStorageDiskIDCheck) SetDiskID(id string) {
|
||||
p.diskID = id
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) isDiskStale() bool {
|
||||
func (p *xlStorageDiskIDCheck) checkDiskStale() error {
|
||||
if p.diskID == "" {
|
||||
// For empty disk-id we allow the call as the server might be coming up and trying to read format.json
|
||||
// or create format.json
|
||||
return false
|
||||
// For empty disk-id we allow the call as the server might be
|
||||
// coming up and trying to read format.json or create format.json
|
||||
return nil
|
||||
}
|
||||
storedDiskID, err := p.storage.GetDiskID()
|
||||
if err == nil && p.diskID == storedDiskID {
|
||||
return false
|
||||
if err != nil {
|
||||
// return any error generated while reading `format.json`
|
||||
return err
|
||||
}
|
||||
return true
|
||||
if err == nil && p.diskID == storedDiskID {
|
||||
return nil
|
||||
}
|
||||
// not the same disk we remember, take it offline.
|
||||
return errDiskNotFound
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) DiskInfo() (info DiskInfo, err error) {
|
||||
if p.isDiskStale() {
|
||||
return info, errDiskNotFound
|
||||
info, err = p.storage.DiskInfo()
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
return p.storage.DiskInfo()
|
||||
// check cached diskID against backend
|
||||
// only if its non-empty.
|
||||
if p.diskID != "" {
|
||||
if p.diskID != info.ID {
|
||||
return info, errDiskNotFound
|
||||
}
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) MakeVolBulk(volumes ...string) (err error) {
|
||||
if p.isDiskStale() {
|
||||
return errDiskNotFound
|
||||
if err = p.checkDiskStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
return p.storage.MakeVolBulk(volumes...)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) MakeVol(volume string) (err error) {
|
||||
if p.isDiskStale() {
|
||||
return errDiskNotFound
|
||||
if err = p.checkDiskStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
return p.storage.MakeVol(volume)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) ListVols() ([]VolInfo, error) {
|
||||
if p.isDiskStale() {
|
||||
return nil, errDiskNotFound
|
||||
if err := p.checkDiskStale(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.storage.ListVols()
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) StatVol(volume string) (vol VolInfo, err error) {
|
||||
if p.isDiskStale() {
|
||||
return vol, errDiskNotFound
|
||||
if err = p.checkDiskStale(); err != nil {
|
||||
return vol, err
|
||||
}
|
||||
return p.storage.StatVol(volume)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) DeleteVol(volume string, forceDelete bool) (err error) {
|
||||
if p.isDiskStale() {
|
||||
return errDiskNotFound
|
||||
if err = p.checkDiskStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
return p.storage.DeleteVol(volume, forceDelete)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) WalkVersions(volume, dirPath string, marker string, recursive bool, endWalkCh <-chan struct{}) (chan FileInfoVersions, error) {
|
||||
if p.isDiskStale() {
|
||||
return nil, errDiskNotFound
|
||||
if err := p.checkDiskStale(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p.storage.WalkVersions(volume, dirPath, marker, recursive, endWalkCh)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) Walk(volume, dirPath string, marker string, recursive bool, endWalkCh <-chan struct{}) (chan FileInfo, error) {
|
||||
if p.isDiskStale() {
|
||||
return nil, errDiskNotFound
|
||||
if err := p.checkDiskStale(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.storage.Walk(volume, dirPath, marker, recursive, endWalkCh)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) WalkSplunk(volume, dirPath string, marker string, endWalkCh <-chan struct{}) (chan FileInfo, error) {
|
||||
if p.isDiskStale() {
|
||||
return nil, errDiskNotFound
|
||||
if err := p.checkDiskStale(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.storage.WalkSplunk(volume, dirPath, marker, endWalkCh)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) ListDir(volume, dirPath string, count int) ([]string, error) {
|
||||
if p.isDiskStale() {
|
||||
return nil, errDiskNotFound
|
||||
if err := p.checkDiskStale(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.storage.ListDir(volume, dirPath, count)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) ReadFile(volume string, path string, offset int64, buf []byte, verifier *BitrotVerifier) (n int64, err error) {
|
||||
if p.isDiskStale() {
|
||||
return 0, errDiskNotFound
|
||||
if err := p.checkDiskStale(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return p.storage.ReadFile(volume, path, offset, buf, verifier)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) AppendFile(volume string, path string, buf []byte) (err error) {
|
||||
if p.isDiskStale() {
|
||||
return errDiskNotFound
|
||||
if err = p.checkDiskStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.storage.AppendFile(volume, path, buf)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) CreateFile(volume, path string, size int64, reader io.Reader) error {
|
||||
if p.isDiskStale() {
|
||||
return errDiskNotFound
|
||||
if err := p.checkDiskStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.storage.CreateFile(volume, path, size, reader)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) ReadFileStream(volume, path string, offset, length int64) (io.ReadCloser, error) {
|
||||
if p.isDiskStale() {
|
||||
return nil, errDiskNotFound
|
||||
if err := p.checkDiskStale(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.storage.ReadFileStream(volume, path, offset, length)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) RenameFile(srcVolume, srcPath, dstVolume, dstPath string) error {
|
||||
if p.isDiskStale() {
|
||||
return errDiskNotFound
|
||||
if err := p.checkDiskStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.storage.RenameFile(srcVolume, srcPath, dstVolume, dstPath)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) RenameData(srcVolume, srcPath, dataDir, dstVolume, dstPath string) error {
|
||||
if p.isDiskStale() {
|
||||
return errDiskNotFound
|
||||
if err := p.checkDiskStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.storage.RenameData(srcVolume, srcPath, dataDir, dstVolume, dstPath)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) CheckParts(volume string, path string, fi FileInfo) (err error) {
|
||||
if p.isDiskStale() {
|
||||
return errDiskNotFound
|
||||
if err = p.checkDiskStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.storage.CheckParts(volume, path, fi)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) CheckFile(volume string, path string) (err error) {
|
||||
if p.isDiskStale() {
|
||||
return errDiskNotFound
|
||||
if err = p.checkDiskStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.storage.CheckFile(volume, path)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) DeleteFile(volume string, path string) (err error) {
|
||||
if p.isDiskStale() {
|
||||
return errDiskNotFound
|
||||
if err = p.checkDiskStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.storage.DeleteFile(volume, path)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) DeleteVersions(volume string, versions []FileInfo) (errs []error) {
|
||||
if p.isDiskStale() {
|
||||
if err := p.checkDiskStale(); err != nil {
|
||||
errs = make([]error, len(versions))
|
||||
for i := range errs {
|
||||
errs[i] = errDiskNotFound
|
||||
errs[i] = err
|
||||
}
|
||||
return errs
|
||||
}
|
||||
@@ -224,43 +249,49 @@ func (p *xlStorageDiskIDCheck) DeleteVersions(volume string, versions []FileInfo
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) VerifyFile(volume, path string, fi FileInfo) error {
|
||||
if p.isDiskStale() {
|
||||
return errDiskNotFound
|
||||
if err := p.checkDiskStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.storage.VerifyFile(volume, path, fi)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) WriteAll(volume string, path string, reader io.Reader) (err error) {
|
||||
if p.isDiskStale() {
|
||||
return errDiskNotFound
|
||||
if err = p.checkDiskStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.storage.WriteAll(volume, path, reader)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) DeleteVersion(volume, path string, fi FileInfo) (err error) {
|
||||
if p.isDiskStale() {
|
||||
return errDiskNotFound
|
||||
if err = p.checkDiskStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.storage.DeleteVersion(volume, path, fi)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) WriteMetadata(volume, path string, fi FileInfo) (err error) {
|
||||
if p.isDiskStale() {
|
||||
return errDiskNotFound
|
||||
if err = p.checkDiskStale(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.storage.WriteMetadata(volume, path, fi)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) ReadVersion(volume, path, versionID string) (fi FileInfo, err error) {
|
||||
if p.isDiskStale() {
|
||||
return fi, errDiskNotFound
|
||||
if err = p.checkDiskStale(); err != nil {
|
||||
return fi, err
|
||||
}
|
||||
|
||||
return p.storage.ReadVersion(volume, path, versionID)
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) ReadAll(volume string, path string) (buf []byte, err error) {
|
||||
if p.isDiskStale() {
|
||||
return nil, errDiskNotFound
|
||||
if err = p.checkDiskStale(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.storage.ReadAll(volume, path)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ func getFileInfoVersions(xlMetaBuf []byte, volume, path string) (FileInfoVersion
|
||||
}
|
||||
|
||||
fi.IsLatest = true // No versions so current version is latest.
|
||||
fi.XLV1 = true // indicates older version
|
||||
return FileInfoVersions{
|
||||
Volume: volume,
|
||||
Name: path,
|
||||
@@ -76,5 +77,6 @@ func getFileInfo(xlMetaBuf []byte, volume, path, versionID string) (FileInfo, er
|
||||
if err == errFileNotFound && versionID != "" {
|
||||
return fi, errFileVersionNotFound
|
||||
}
|
||||
fi.XLV1 = true // indicates older version
|
||||
return fi, err
|
||||
}
|
||||
|
||||
+39
-28
@@ -86,9 +86,6 @@ func isValidVolname(volname string) bool {
|
||||
|
||||
// xlStorage - implements StorageAPI interface.
|
||||
type xlStorage struct {
|
||||
// Disk usage metrics
|
||||
totalUsed uint64 // ref: https://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||
|
||||
maxActiveIOCount int32
|
||||
activeIOCount int32
|
||||
|
||||
@@ -408,7 +405,10 @@ func (s *xlStorage) CrawlAndGetDataUsage(ctx context.Context, cache dataUsageCac
|
||||
|
||||
var totalSize int64
|
||||
for _, version := range fivs.Versions {
|
||||
size := item.applyActions(ctx, objAPI, actionMeta{numVersions: len(fivs.Versions), oi: version.ToObjectInfo(item.bucket, item.objectPath())})
|
||||
size := item.applyActions(ctx, objAPI, actionMeta{
|
||||
numVersions: len(fivs.Versions),
|
||||
oi: version.ToObjectInfo(item.bucket, item.objectPath()),
|
||||
})
|
||||
if !version.Deleted {
|
||||
totalSize += size
|
||||
}
|
||||
@@ -422,12 +422,6 @@ func (s *xlStorage) CrawlAndGetDataUsage(ctx context.Context, cache dataUsageCac
|
||||
}
|
||||
|
||||
dataUsageInfo.Info.LastUpdate = time.Now()
|
||||
total := dataUsageInfo.sizeRecursive(dataUsageInfo.Info.Name)
|
||||
if total == nil {
|
||||
total = &dataUsageEntry{}
|
||||
}
|
||||
atomic.StoreUint64(&s.totalUsed, uint64(total.Size))
|
||||
|
||||
return dataUsageInfo, nil
|
||||
}
|
||||
|
||||
@@ -438,8 +432,10 @@ type DiskInfo struct {
|
||||
Free uint64
|
||||
Used uint64
|
||||
RootDisk bool
|
||||
Endpoint string
|
||||
MountPath string
|
||||
Error string // reports any error returned by underlying disk
|
||||
ID string
|
||||
Error string // carries the error over the network
|
||||
}
|
||||
|
||||
// DiskInfo provides current information about disk space usage,
|
||||
@@ -455,23 +451,22 @@ func (s *xlStorage) DiskInfo() (info DiskInfo, err error) {
|
||||
return info, err
|
||||
}
|
||||
|
||||
used := di.Total - di.Free
|
||||
if !s.diskMount {
|
||||
used = atomic.LoadUint64(&s.totalUsed)
|
||||
}
|
||||
|
||||
rootDisk, err := disk.IsRootDisk(s.diskPath)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
return DiskInfo{
|
||||
info = DiskInfo{
|
||||
Total: di.Total,
|
||||
Free: di.Free,
|
||||
Used: used,
|
||||
Used: di.Total - di.Free,
|
||||
RootDisk: rootDisk,
|
||||
MountPath: s.diskPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
diskID, err := s.GetDiskID()
|
||||
info.ID = diskID
|
||||
return info, err
|
||||
}
|
||||
|
||||
// getVolDir - will convert incoming volume names to
|
||||
@@ -512,7 +507,17 @@ func (s *xlStorage) GetDiskID() (string, error) {
|
||||
if err != nil {
|
||||
// If the disk is still not initialized.
|
||||
if os.IsNotExist(err) {
|
||||
return "", errUnformattedDisk
|
||||
_, err = os.Stat(s.diskPath)
|
||||
if err == nil {
|
||||
// Disk is present by missing `format.json`
|
||||
return "", errUnformattedDisk
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return "", errDiskNotFound
|
||||
} else if os.IsPermission(err) {
|
||||
return "", errDiskAccessDenied
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return "", errCorruptedFormat
|
||||
}
|
||||
@@ -1100,12 +1105,7 @@ func (s *xlStorage) DeleteVersion(volume, path string, fi FileInfo) error {
|
||||
if !isXL2V1Format(buf) {
|
||||
// Delete the meta file, if there are no more versions the
|
||||
// top level parent is automatically removed.
|
||||
filePath := pathJoin(volumeDir, path, xlStorageFormatFile)
|
||||
if err = checkPathLength(filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return deleteFile(volumeDir, filePath, false)
|
||||
return deleteFile(volumeDir, pathJoin(volumeDir, path), true)
|
||||
}
|
||||
|
||||
var xlMeta xlMetaV2
|
||||
@@ -1232,7 +1232,12 @@ func (s *xlStorage) renameLegacyMetadata(volume, path string) error {
|
||||
dstFilePath := pathJoin(filePath, xlStorageFormatFile)
|
||||
|
||||
// Renaming xl.json to xl.meta should be fully synced to disk.
|
||||
defer globalSync()
|
||||
defer func() {
|
||||
if err == nil {
|
||||
// Sync to disk only upon success.
|
||||
globalSync()
|
||||
}
|
||||
}()
|
||||
|
||||
if err = os.Rename(srcFilePath, dstFilePath); err != nil {
|
||||
switch {
|
||||
@@ -1770,6 +1775,9 @@ func (s *xlStorage) CheckParts(volume, path string, fi FileInfo) error {
|
||||
|
||||
for _, part := range fi.Parts {
|
||||
partPath := pathJoin(path, fi.DataDir, fmt.Sprintf("part.%d", part.Number))
|
||||
if fi.XLV1 {
|
||||
partPath = pathJoin(path, fmt.Sprintf("part.%d", part.Number))
|
||||
}
|
||||
filePath := pathJoin(volumeDir, partPath)
|
||||
if err = checkPathLength(filePath); err != nil {
|
||||
return err
|
||||
@@ -2111,7 +2119,7 @@ func (s *xlStorage) RenameData(srcVolume, srcPath, dataDir, dstVolume, dstPath s
|
||||
return osErrToFileErr(err)
|
||||
}
|
||||
|
||||
// Sync all the directory operations.
|
||||
// Sync all the previous directory operations.
|
||||
globalSync()
|
||||
|
||||
for _, entry := range entries {
|
||||
@@ -2375,6 +2383,9 @@ func (s *xlStorage) VerifyFile(volume, path string, fi FileInfo) (err error) {
|
||||
for _, part := range fi.Parts {
|
||||
checksumInfo := erasure.GetChecksumInfo(part.Number)
|
||||
partPath := pathJoin(volumeDir, path, fi.DataDir, fmt.Sprintf("part.%d", part.Number))
|
||||
if fi.XLV1 {
|
||||
partPath = pathJoin(volumeDir, path, fmt.Sprintf("part.%d", part.Number))
|
||||
}
|
||||
if err := s.bitrotVerify(partPath,
|
||||
erasure.ShardFileSize(part.Size),
|
||||
checksumInfo.Algorithm,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bucket Quota Configuration Quickstart Guide [](https://slack.min.io) [](https://hub.docker.com/r/minio/minio/)
|
||||
|
||||

|
||||

|
||||
|
||||
Buckets can be configured to have one of two types of quota configuration - FIFO and Hard quota.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ Once the header is validated, we proceed to the actual data structure of the `xl
|
||||
- LegacyObjectType (preserves existing deployments and older xl.json format)
|
||||
- DeleteMarker (a versionId to capture the DELETE sequences implemented primarily for AWS spec compatibility)
|
||||
|
||||
A sample msgpack-JSON `xl.meta`, you can debug the content inside `xl.meta` using [xl-meta-to-json.go](./xl-meta-to-json.go) program.
|
||||
A sample msgpack-JSON `xl.meta`, you can debug the content inside `xl.meta` using [xl-meta-to-json.go](https://github.com/minio/minio/blob/master/docs/bucket/versioning/xl-meta-to-json.go) program.
|
||||
```json
|
||||
{
|
||||
"Versions": [
|
||||
|
||||
@@ -10,25 +10,25 @@ Only MinIO generates version IDs, and they can't be edited. Version IDs are simp
|
||||
|
||||
When you PUT an object in a versioning-enabled bucket, the noncurrent version is not overwritten. The following figure shows that when a new version of `spark.csv` is PUT into a bucket that already contains an object with the same name, the original object (ID = `ede336f2`) remains in the bucket, MinIO generates a new version (ID = `fae684da`), and adds the newer version to the bucket.
|
||||
|
||||

|
||||

|
||||
|
||||
This means accidental overwrites or deletes of objects are protected, allows previous version of on object to be retrieved.
|
||||
|
||||
When you DELETE an object, all versions remain in the bucket and MinIO adds a delete marker, as shown below:
|
||||
|
||||

|
||||

|
||||
|
||||
Now the delete marker becomes the current version of the object. GET requests by default always retrieve the latest stored version. So performing a simple GET object request when the current version is a delete marker would return `404` `The specified key does not exist` as shown below:
|
||||
|
||||

|
||||

|
||||
|
||||
GET requests by specifying a verison ID as shown below, you can retrieve the specific object version `fae684da`.
|
||||
|
||||

|
||||

|
||||
|
||||
To permanently delete an object you need to specify the version you want to delete, only the user with appropriate permissions can permanently delete a version. As shown below DELETE request called with a specific version id permenantly deletes an object from a bucket. Delete marker is not added for DELETE requests with version id.
|
||||
|
||||

|
||||

|
||||
|
||||
## Concepts
|
||||
- All Buckets on MinIO are always in one of the following states: unversioned (the default) and all other existing deployments, versioning-enabled, or versioning-suspended.
|
||||
@@ -36,3 +36,131 @@ To permanently delete an object you need to specify the version you want to dele
|
||||
- Existing or newer buckets can be created with versioning enabled and eventually can be suspended as well. Existing versions of objects stay as is and can still be accessed using the version ID.
|
||||
- All versions, including delete-markers should be deleted before deleting a bucket.
|
||||
- **Versioning feature is only available in erasure coded and distributed erasure coded setups**.
|
||||
|
||||
## How to configure versioning on a bucket
|
||||
Each bucket created has a versioning configuration associated with it. By default bucket is unversioned as shown below
|
||||
```
|
||||
<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
</VersioningConfiguration>
|
||||
```
|
||||
|
||||
To enable versioning, you send a request to MinIO with a versioning configuration with Status set to `Enabled`.
|
||||
```
|
||||
<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Status>Enabled</Status>
|
||||
</VersioningConfiguration>
|
||||
```
|
||||
|
||||
Similarly to suspend versioning set the configuration with Status set to `Suspended`.
|
||||
```
|
||||
<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Status>Suspended</Status>
|
||||
</VersioningConfiguration>
|
||||
```
|
||||
|
||||
Only users with explicit permissions or the root credential can configure the versioning state of any bucket.
|
||||
|
||||
## Examples of enabling bucket versioning using MinIO Java SDK
|
||||
|
||||
### EnableVersioning() API
|
||||
|
||||
```
|
||||
import io.minio.EnableVersioningArgs;
|
||||
import io.minio.MinioClient;
|
||||
import io.minio.errors.MinioException;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class EnableVersioning {
|
||||
/** MinioClient.enableVersioning() example. */
|
||||
public static void main(String[] args)
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeyException {
|
||||
try {
|
||||
/* play.min.io for test and development. */
|
||||
MinioClient minioClient =
|
||||
MinioClient.builder()
|
||||
.endpoint("https://play.min.io")
|
||||
.credentials("Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG")
|
||||
.build();
|
||||
|
||||
/* Amazon S3: */
|
||||
// MinioClient minioClient =
|
||||
// MinioClient.builder()
|
||||
// .endpoint("https://s3.amazonaws.com")
|
||||
// .credentials("YOUR-ACCESSKEY", "YOUR-SECRETACCESSKEY")
|
||||
// .build();
|
||||
|
||||
// Enable versioning on 'my-bucketname'.
|
||||
minioClient.enableVersioning(EnableVersioningArgs.builder().bucket("my-bucketname").build());
|
||||
|
||||
System.out.println("Bucket versioning is enabled successfully");
|
||||
|
||||
} catch (MinioException e) {
|
||||
System.out.println("Error occurred: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### isVersioningEnabled() API
|
||||
|
||||
```
|
||||
public class IsVersioningEnabled {
|
||||
/** MinioClient.isVersioningEnabled() example. */
|
||||
public static void main(String[] args)
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeyException {
|
||||
try {
|
||||
/* play.min.io for test and development. */
|
||||
MinioClient minioClient =
|
||||
MinioClient.builder()
|
||||
.endpoint("https://play.min.io")
|
||||
.credentials("Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG")
|
||||
.build();
|
||||
|
||||
/* Amazon S3: */
|
||||
// MinioClient minioClient =
|
||||
// MinioClient.builder()
|
||||
// .endpoint("https://s3.amazonaws.com")
|
||||
// .credentials("YOUR-ACCESSKEY", "YOUR-SECRETACCESSKEY")
|
||||
// .build();
|
||||
|
||||
// Create bucket 'my-bucketname' if it doesn`t exist.
|
||||
if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket("my-bucketname").build())) {
|
||||
minioClient.makeBucket(MakeBucketArgs.builder().bucket("my-bucketname").build());
|
||||
System.out.println("my-bucketname is created successfully");
|
||||
}
|
||||
|
||||
boolean isVersioningEnabled =
|
||||
minioClient.isVersioningEnabled(
|
||||
IsVersioningEnabledArgs.builder().bucket("my-bucketname").build());
|
||||
if (isVersioningEnabled) {
|
||||
System.out.println("Bucket versioning is enabled");
|
||||
} else {
|
||||
System.out.println("Bucket versioning is disabled");
|
||||
}
|
||||
// Enable versioning on 'my-bucketname'.
|
||||
minioClient.enableVersioning(EnableVersioningArgs.builder().bucket("my-bucketname").build());
|
||||
System.out.println("Bucket versioning is enabled successfully");
|
||||
|
||||
isVersioningEnabled =
|
||||
minioClient.isVersioningEnabled(
|
||||
IsVersioningEnabledArgs.builder().bucket("my-bucketname").build());
|
||||
if (isVersioningEnabled) {
|
||||
System.out.println("Bucket versioning is enabled");
|
||||
} else {
|
||||
System.out.println("Bucket versioning is disabled");
|
||||
}
|
||||
|
||||
} catch (MinioException e) {
|
||||
System.out.println("Error occurred: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Explore Further
|
||||
- [Use `minio-java` SDK with MinIO Server](https://docs.minio.io/docs/java-client-quickstart-guide.html)
|
||||
- [Object Lock and Immutablity Guide](https://docs.minio.io/docs/minio-bucket-object-lock-guide.html)
|
||||
- [MinIO Admin Complete Guide](https://docs.min.io/docs/minio-admin-complete-guide.html)
|
||||
- [The MinIO documentation website](https://docs.min.io)
|
||||
|
||||
@@ -9,55 +9,67 @@ __Prerequisites__
|
||||
- The S3 bucket, Access Key and Secret Key have to be created before and outside of Veeam.
|
||||
- Configure the minio client for the Veeam MinIO endpoint - https://docs.min.io/docs/minio-client-quickstart-guide.html
|
||||
|
||||
|
||||
|
||||
## Setting up an S3 compatible object store for Veeam Backup and Replication
|
||||
### Create a bucket for Veeam backups
|
||||
Create a bucket for Veeam Backup, e.g., `mc mb myminio/veeambackup`
|
||||
Create a bucket for Veeam Backup, e.g.,
|
||||
|
||||
```
|
||||
mc mb myminio/veeambackup
|
||||
```
|
||||
|
||||
> NOTE: For Veeam Backup with Immutability, create the bucket with object lock enabled, e.g.,
|
||||
> ```
|
||||
> mc mb -l myminio/veeambackup
|
||||
> ```.
|
||||
> Object locking requires erasure coding enabled on the minio server. For more information see https://docs.minio.io/docs/minio-erasure-code-quickstart-guide.html.
|
||||
|
||||
### Add MinIO as an object store for Veeam
|
||||
Follow the steps from the Veeam documentation for adding MinIO as an object store - https://helpcenter.veeam.com/docs/backup/vsphere/adding_s3c_object_storage.html?ver=100
|
||||
|
||||
For Veeam Backup with Immutability, choose the amount of days you want to make backups immutable for <br />
|
||||

|
||||
|
||||
### Creating the Scale-out Backup Repository
|
||||
|
||||
1. Under the Backup Infrastructure view, click on Scale-out Repositories and click the Add Scale-out Repository button on the ribbon.
|
||||
- Under the Backup Infrastructure view, click on Scale-out Repositories and click the Add Scale-out Repository button on the ribbon.
|
||||
|
||||
2. Follow the on screen wizard
|
||||
- Follow the on screen wizard
|
||||
|
||||
3. On the Capacity Tier screen, check the box to Extend scale-out backup repository capacity with object storage checkbox and select the object storage. If you want to be able to test backup data immediately after a job is run, under the object storage selection, check the "Copy" box and uncheck the "Move" box.
|
||||
|
||||
- On the Capacity Tier screen, check the box to Extend scale-out backup repository capacity with object storage checkbox and select the object storage. If you want to be able to test backup data immediately after a job is run, under the object storage selection, check the "Copy" box and uncheck the "Move" box.
|
||||
|
||||
### Create a backup job
|
||||
#### Backup Virtual Machines with Veeam Backup and Replication
|
||||
|
||||
1. Under Home > Jobs > Backup in Navigation Pane, click on Backup Job button in the ribbon and choose Virtual Machine. Follow the on screen wizard.
|
||||
- Under Home > Jobs > Backup in Navigation Pane, click on Backup Job button in the ribbon and choose Virtual Machine. Follow the on screen wizard.
|
||||
|
||||
2. On the Storage screen, choose the Scale-out Backup Repository that was configured previously.
|
||||
- On the Storage screen, choose the Scale-out Backup Repository that was configured previously.
|
||||
|
||||
4. Continue with the backup job creation. On the Summary screen, check the Run the Job when I click Finish checkbox and click the Finish button.
|
||||
The backup job will start immediately. This will create an Active Full backup of the VMs within the backup job.
|
||||
- Continue with the backup job creation. On the Summary screen, check the Run the Job when I click Finish checkbox and click the Finish button. The backup job will start immediately. This will create an Active Full backup of the VMs within the backup job.
|
||||
|
||||
- Since we selected Copy mode when creating the SOBR, the backup will be copied to the capacity tier as soon as it is created on the performance tier.
|
||||
|
||||
- For Veeam Backup with Immutability, you can choose a number of restore points or days to make backups immutable.
|
||||

|
||||
|
||||
5. Since we selected Copy mode when creating the SOBR, the backup will be copied to the capacity tier as soon as it is created on the performance tier.
|
||||
|
||||
#### Backup Office 365 with VBO
|
||||
1. Create a new bucket for VBO backups
|
||||
`mc mb myminio/vbo`
|
||||
- Create a new bucket for VBO backups
|
||||
```
|
||||
mc mb -l myminio/vbo
|
||||
```
|
||||
|
||||
2. Under Backup Infrastructure, right click on Object Storage Repositories and choose "Add object storage"
|
||||
- Under Backup Infrastructure, right click on Object Storage Repositories and choose "Add object storage"
|
||||
|
||||

|
||||

|
||||
|
||||
3. Follow through the wizard as above for Veeam Backup and Replication as the steps are the same between both products
|
||||
- Follow through the wizard as above for Veeam Backup and Replication as the steps are the same between both products
|
||||
|
||||
4. Under Backup Infrastructure -> Backup Repositories, right click and "Add Backup Repository"
|
||||
- Under Backup Infrastructure -> Backup Repositories, right click and "Add Backup Repository"
|
||||
|
||||
5. Follow the wizard. Under the "Object Storage Backup Repository" section, choose the MinIO object storage you created above
|
||||
- Follow the wizard. Under the "Object Storage Backup Repository" section, choose the MinIO object storage you created above
|
||||
|
||||

|
||||
|
||||
6. When you create your backup job, choose the backup repository you created above.
|
||||

|
||||
|
||||
- When you create your backup job, choose the backup repository you created above.
|
||||
|
||||
## Test the setup
|
||||
The next time the backup job runs, you can use the `mc admin trace myminio` command and verify traffic is flowing to the MinIO nodes. For Veeam Backup and Replication you will need to wait for the backup to complete to the performance tier before it migrates data to the capacity tier (i.e., MinIO).
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -103,7 +103,7 @@ Here `prometheus.yml` is the name of configuration file. You can now see MinIO m
|
||||
|
||||
### 6. Configure Grafana
|
||||
|
||||
After Prometheus is configured, you can use Grafana to visualize MinIO metrics. Refer the [document here to setup Grafana with MinIO metrics](./grafana/README.md).
|
||||
After Prometheus is configured, you can use Grafana to visualize MinIO metrics. Refer the [document here to setup Grafana with MinIO prometheus metrics](https://github.com/minio/minio/blob/master/docs/metrics/prometheus/grafana/README.md).
|
||||
|
||||
## List of metrics exposed by MinIO
|
||||
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
[Grafana](https://grafana.com/) allows you to query, visualize, alert on and understand your metrics no matter where they are stored. Create, explore, and share dashboards with your team and foster a data driven culture.
|
||||
|
||||
This document explains how to setup Grafana, to visualize MinIO metrics.
|
||||
This document explains how to setup Grafana, to visualize MinIO metrics.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Prometheus and MinIO configured as explained in [document here](../README.md).
|
||||
- Prometheus and MinIO configured as explained in [document here](https://github.com/minio/minio/blob/master/docs/metrics/prometheus/README.md).
|
||||
- Grafana installed as explained [here](https://grafana.com/grafana/download).
|
||||
|
||||
## MinIO Grafana Dashboard
|
||||
|
||||
To start visualizing MinIO metrics exposed by MinIO, you can use our Grafana dashboard. Import the dashboard by copying the `json` contents from the [file here](./Minio-Overview-1594305200170.json).
|
||||
To start visualizing MinIO metrics exposed by MinIO, you can use our Grafana dashboard. Import to dashboard the contents from the [file here](https://github.com/minio/minio/blob/master/docs/metrics/prometheus/grafana/Minio-Overview-1594305200170.json).
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -5,7 +5,7 @@ version: '3.7'
|
||||
# 9001 through 9004.
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2020-07-02T00-15-09Z
|
||||
image: minio/minio:RELEASE.2020-07-14T19-14-30Z
|
||||
volumes:
|
||||
- data1-1:/data1
|
||||
- data1-2:/data2
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2020-07-02T00-15-09Z
|
||||
image: minio/minio:RELEASE.2020-07-14T19-14-30Z
|
||||
volumes:
|
||||
- data2-1:/data1
|
||||
- data2-2:/data2
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2020-07-02T00-15-09Z
|
||||
image: minio/minio:RELEASE.2020-07-14T19-14-30Z
|
||||
volumes:
|
||||
- data3-1:/data1
|
||||
- data3-2:/data2
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2020-07-02T00-15-09Z
|
||||
image: minio/minio:RELEASE.2020-07-14T19-14-30Z
|
||||
volumes:
|
||||
- data4-1:/data1
|
||||
- data4-2:/data2
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2020-07-02T00-15-09Z
|
||||
image: minio/minio:RELEASE.2020-07-14T19-14-30Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -29,7 +29,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2020-07-02T00-15-09Z
|
||||
image: minio/minio:RELEASE.2020-07-14T19-14-30Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2020-07-02T00-15-09Z
|
||||
image: minio/minio:RELEASE.2020-07-14T19-14-30Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -83,7 +83,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2020-07-02T00-15-09Z
|
||||
image: minio/minio:RELEASE.2020-07-14T19-14-30Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user