mirror of https://github.com/minio/minio.git
				
				
				
			
		
			
				
	
	
		
			342 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			342 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright (c) 2015-2022 MinIO, Inc.
 | |
| //
 | |
| // This file is part of MinIO Object Storage stack
 | |
| //
 | |
| // This program is free software: you can redistribute it and/or modify
 | |
| // it under the terms of the GNU Affero General Public License as published by
 | |
| // the Free Software Foundation, either version 3 of the License, or
 | |
| // (at your option) any later version.
 | |
| //
 | |
| // This program is distributed in the hope that it will be useful
 | |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| // GNU Affero General Public License for more details.
 | |
| //
 | |
| // You should have received a copy of the GNU Affero General Public License
 | |
| // along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | |
| 
 | |
| package cmd
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"encoding/gob"
 | |
| 	"errors"
 | |
| 	"net/http"
 | |
| 	"sort"
 | |
| 
 | |
| 	"github.com/minio/minio/internal/logger"
 | |
| 	"github.com/minio/mux"
 | |
| 	"github.com/minio/pkg/sync/errgroup"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	peerS3Version = "v1" // First implementation
 | |
| 
 | |
| 	peerS3VersionPrefix = SlashSeparator + peerS3Version
 | |
| 	peerS3Prefix        = minioReservedBucketPath + "/peer-s3"
 | |
| 	peerS3Path          = peerS3Prefix + peerS3VersionPrefix
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	peerS3MethodHealth        = "/health"
 | |
| 	peerS3MethodMakeBucket    = "/make-bucket"
 | |
| 	peerS3MethodGetBucketInfo = "/get-bucket-info"
 | |
| 	peerS3MethodDeleteBucket  = "/delete-bucket"
 | |
| 	peerS3MethodListBuckets   = "/list-buckets"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	peerS3Bucket            = "bucket"
 | |
| 	peerS3BucketDeleted     = "bucket-deleted"
 | |
| 	peerS3BucketForceCreate = "force-create"
 | |
| 	peerS3BucketForceDelete = "force-delete"
 | |
| )
 | |
| 
 | |
| type peerS3Server struct{}
 | |
| 
 | |
| func (s *peerS3Server) writeErrorResponse(w http.ResponseWriter, err error) {
 | |
| 	w.WriteHeader(http.StatusForbidden)
 | |
| 	w.Write([]byte(err.Error()))
 | |
| }
 | |
| 
 | |
| // IsValid - To authenticate and verify the time difference.
 | |
| func (s *peerS3Server) IsValid(w http.ResponseWriter, r *http.Request) bool {
 | |
| 	objAPI := newObjectLayerFn()
 | |
| 	if objAPI == nil {
 | |
| 		s.writeErrorResponse(w, errServerNotInitialized)
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	if err := storageServerRequestValidate(r); err != nil {
 | |
| 		s.writeErrorResponse(w, err)
 | |
| 		return false
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // HealthHandler - returns true of health
 | |
| func (s *peerS3Server) HealthHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	s.IsValid(w, r)
 | |
| }
 | |
| 
 | |
| func listBucketsLocal(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error) {
 | |
| 	globalLocalDrivesMu.RLock()
 | |
| 	globalLocalDrives := globalLocalDrives
 | |
| 	globalLocalDrivesMu.RUnlock()
 | |
| 
 | |
| 	quorum := (len(globalLocalDrives) / 2)
 | |
| 
 | |
| 	buckets = make([]BucketInfo, 0, 32)
 | |
| 	healBuckets := map[string]VolInfo{}
 | |
| 
 | |
| 	// lists all unique buckets across drives.
 | |
| 	if err := listAllBuckets(ctx, globalLocalDrives, healBuckets, quorum); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// include deleted buckets in listBuckets output
 | |
| 	deletedBuckets := map[string]VolInfo{}
 | |
| 
 | |
| 	if opts.Deleted {
 | |
| 		// lists all deleted buckets across drives.
 | |
| 		if err := listDeletedBuckets(ctx, globalLocalDrives, deletedBuckets, quorum); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, v := range healBuckets {
 | |
| 		bi := BucketInfo{
 | |
| 			Name:    v.Name,
 | |
| 			Created: v.Created,
 | |
| 		}
 | |
| 		if vi, ok := deletedBuckets[v.Name]; ok {
 | |
| 			bi.Deleted = vi.Created
 | |
| 		}
 | |
| 		buckets = append(buckets, bi)
 | |
| 	}
 | |
| 
 | |
| 	for _, v := range deletedBuckets {
 | |
| 		if _, ok := healBuckets[v.Name]; !ok {
 | |
| 			buckets = append(buckets, BucketInfo{
 | |
| 				Name:    v.Name,
 | |
| 				Deleted: v.Created,
 | |
| 			})
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	sort.Slice(buckets, func(i, j int) bool {
 | |
| 		return buckets[i].Name < buckets[j].Name
 | |
| 	})
 | |
| 
 | |
| 	return buckets, nil
 | |
| }
 | |
| 
 | |
| func getBucketInfoLocal(ctx context.Context, bucket string, opts BucketOptions) (BucketInfo, error) {
 | |
| 	globalLocalDrivesMu.RLock()
 | |
| 	globalLocalDrives := globalLocalDrives
 | |
| 	globalLocalDrivesMu.RUnlock()
 | |
| 
 | |
| 	g := errgroup.WithNErrs(len(globalLocalDrives)).WithConcurrency(32)
 | |
| 	bucketsInfo := make([]BucketInfo, len(globalLocalDrives))
 | |
| 
 | |
| 	// Make a volume entry on all underlying storage disks.
 | |
| 	for index := range globalLocalDrives {
 | |
| 		index := index
 | |
| 		g.Go(func() error {
 | |
| 			if globalLocalDrives[index] == nil {
 | |
| 				return errDiskNotFound
 | |
| 			}
 | |
| 			volInfo, err := globalLocalDrives[index].StatVol(ctx, bucket)
 | |
| 			if err != nil {
 | |
| 				if opts.Deleted {
 | |
| 					dvi, derr := globalLocalDrives[index].StatVol(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix, bucket))
 | |
| 					if derr != nil {
 | |
| 						return err
 | |
| 					}
 | |
| 					bucketsInfo[index] = BucketInfo{Name: bucket, Deleted: dvi.Created}
 | |
| 					return nil
 | |
| 				}
 | |
| 				return err
 | |
| 			}
 | |
| 
 | |
| 			bucketsInfo[index] = BucketInfo{Name: bucket, Created: volInfo.Created}
 | |
| 			return nil
 | |
| 		}, index)
 | |
| 	}
 | |
| 
 | |
| 	errs := g.Wait()
 | |
| 	if err := reduceReadQuorumErrs(ctx, errs, bucketOpIgnoredErrs, (len(globalLocalDrives) / 2)); err != nil {
 | |
| 		return BucketInfo{}, err
 | |
| 	}
 | |
| 
 | |
| 	var bucketInfo BucketInfo
 | |
| 	for i, err := range errs {
 | |
| 		if err == nil {
 | |
| 			bucketInfo = bucketsInfo[i]
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return bucketInfo, nil
 | |
| }
 | |
| 
 | |
| func deleteBucketLocal(ctx context.Context, bucket string, opts DeleteBucketOptions) error {
 | |
| 	globalLocalDrivesMu.RLock()
 | |
| 	globalLocalDrives := globalLocalDrives
 | |
| 	globalLocalDrivesMu.RUnlock()
 | |
| 
 | |
| 	g := errgroup.WithNErrs(len(globalLocalDrives)).WithConcurrency(32)
 | |
| 
 | |
| 	// Make a volume entry on all underlying storage disks.
 | |
| 	for index := range globalLocalDrives {
 | |
| 		index := index
 | |
| 		g.Go(func() error {
 | |
| 			if globalLocalDrives[index] == nil {
 | |
| 				return errDiskNotFound
 | |
| 			}
 | |
| 			return globalLocalDrives[index].DeleteVol(ctx, bucket, opts.Force)
 | |
| 		}, index)
 | |
| 	}
 | |
| 
 | |
| 	var recreate bool
 | |
| 	errs := g.Wait()
 | |
| 	for index, err := range errs {
 | |
| 		if errors.Is(err, errVolumeNotEmpty) {
 | |
| 			recreate = true
 | |
| 		}
 | |
| 		if err == nil && recreate {
 | |
| 			// ignore any errors
 | |
| 			globalLocalDrives[index].MakeVol(ctx, bucket)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, err := range errs {
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func makeBucketLocal(ctx context.Context, bucket string, opts MakeBucketOptions) error {
 | |
| 	globalLocalDrivesMu.RLock()
 | |
| 	globalLocalDrives := globalLocalDrives
 | |
| 	globalLocalDrivesMu.RUnlock()
 | |
| 
 | |
| 	g := errgroup.WithNErrs(len(globalLocalDrives)).WithConcurrency(32)
 | |
| 
 | |
| 	// Make a volume entry on all underlying storage disks.
 | |
| 	for index := range globalLocalDrives {
 | |
| 		index := index
 | |
| 		g.Go(func() error {
 | |
| 			if globalLocalDrives[index] == nil {
 | |
| 				return errDiskNotFound
 | |
| 			}
 | |
| 			err := globalLocalDrives[index].MakeVol(ctx, bucket)
 | |
| 			if opts.ForceCreate && errors.Is(err, errVolumeExists) {
 | |
| 				// No need to return error when force create was
 | |
| 				// requested.
 | |
| 				return nil
 | |
| 			}
 | |
| 			if err != nil && !errors.Is(err, errVolumeExists) {
 | |
| 				logger.LogIf(ctx, err)
 | |
| 			}
 | |
| 			return err
 | |
| 		}, index)
 | |
| 	}
 | |
| 
 | |
| 	errs := g.Wait()
 | |
| 	return reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, (len(globalLocalDrives)/2)+1)
 | |
| }
 | |
| 
 | |
| func (s *peerS3Server) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	if !s.IsValid(w, r) {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	bucketDeleted := r.Form.Get(peerS3BucketDeleted) == "true"
 | |
| 
 | |
| 	buckets, err := listBucketsLocal(r.Context(), BucketOptions{
 | |
| 		Deleted: bucketDeleted,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		s.writeErrorResponse(w, err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	logger.LogIf(r.Context(), gob.NewEncoder(w).Encode(buckets))
 | |
| }
 | |
| 
 | |
| // GetBucketInfoHandler implements peer BuckeInfo call, returns bucket create date.
 | |
| func (s *peerS3Server) GetBucketInfoHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	if !s.IsValid(w, r) {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	bucket := r.Form.Get(peerS3Bucket)
 | |
| 	bucketDeleted := r.Form.Get(peerS3BucketDeleted) == "true"
 | |
| 	bucketInfo, err := getBucketInfoLocal(r.Context(), bucket, BucketOptions{
 | |
| 		Deleted: bucketDeleted,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		s.writeErrorResponse(w, err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	logger.LogIf(r.Context(), gob.NewEncoder(w).Encode(bucketInfo))
 | |
| }
 | |
| 
 | |
| // DeleteBucketHandler implements peer delete bucket call.
 | |
| func (s *peerS3Server) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	if !s.IsValid(w, r) {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	bucket := r.Form.Get(peerS3Bucket)
 | |
| 	if isMinioMetaBucket(bucket) {
 | |
| 		s.writeErrorResponse(w, errInvalidArgument)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	forceDelete := r.Form.Get(peerS3BucketForceDelete) == "true"
 | |
| 
 | |
| 	err := deleteBucketLocal(r.Context(), bucket, DeleteBucketOptions{
 | |
| 		Force: forceDelete,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		s.writeErrorResponse(w, err)
 | |
| 		return
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // MakeBucketHandler implements peer create bucket call.
 | |
| func (s *peerS3Server) MakeBucketHandler(w http.ResponseWriter, r *http.Request) {
 | |
| 	if !s.IsValid(w, r) {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	bucket := r.Form.Get(peerS3Bucket)
 | |
| 	forceCreate := r.Form.Get(peerS3BucketForceCreate) == "true"
 | |
| 
 | |
| 	err := makeBucketLocal(r.Context(), bucket, MakeBucketOptions{
 | |
| 		ForceCreate: forceCreate,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		s.writeErrorResponse(w, err)
 | |
| 		return
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // registerPeerS3Handlers - register peer s3 router.
 | |
| func registerPeerS3Handlers(router *mux.Router) {
 | |
| 	server := &peerS3Server{}
 | |
| 	subrouter := router.PathPrefix(peerS3Prefix).Subrouter()
 | |
| 
 | |
| 	subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodHealth).HandlerFunc(httpTraceHdrs(server.HealthHandler))
 | |
| 	subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodMakeBucket).HandlerFunc(httpTraceHdrs(server.MakeBucketHandler))
 | |
| 	subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodDeleteBucket).HandlerFunc(httpTraceHdrs(server.DeleteBucketHandler))
 | |
| 	subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodGetBucketInfo).HandlerFunc(httpTraceHdrs(server.GetBucketInfoHandler))
 | |
| 	subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodListBuckets).HandlerFunc(httpTraceHdrs(server.ListBucketsHandler))
 | |
| }
 |