| 
									
										
										
										
											2017-08-15 09:08:42 +08:00
										 |  |  | /* | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  |  * MinIO Cloud Storage, (C) 2016-2020 MinIO, Inc. | 
					
						
							| 
									
										
										
										
											2017-08-15 09:08:42 +08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package cmd | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2018-04-06 06:04:40 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2020-08-04 09:17:48 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"sort" | 
					
						
							| 
									
										
										
										
											2020-05-10 00:54:20 +08:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2017-08-15 09:08:42 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-06 06:04:40 +08:00
										 |  |  | 	"github.com/minio/minio/cmd/logger" | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	"github.com/minio/minio/pkg/bpool" | 
					
						
							| 
									
										
										
										
											2020-08-26 01:55:15 +08:00
										 |  |  | 	"github.com/minio/minio/pkg/color" | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	"github.com/minio/minio/pkg/dsync" | 
					
						
							|  |  |  | 	"github.com/minio/minio/pkg/madmin" | 
					
						
							|  |  |  | 	"github.com/minio/minio/pkg/sync/errgroup" | 
					
						
							| 
									
										
										
										
											2017-08-15 09:08:42 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | // OfflineDisk represents an unavailable disk.
 | 
					
						
							|  |  |  | var OfflineDisk StorageAPI // zero value is nil
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | // partialOperation is a successful upload/delete of an object
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | // but not written in all disks (having quorum)
 | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | type partialOperation struct { | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	bucket    string | 
					
						
							|  |  |  | 	object    string | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | 	versionID string | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	failedSet int | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // erasureObjects - Implements ER object layer.
 | 
					
						
							|  |  |  | type erasureObjects struct { | 
					
						
							|  |  |  | 	GatewayUnsupported | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// getDisks returns list of storageAPIs.
 | 
					
						
							|  |  |  | 	getDisks func() []StorageAPI | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// getLockers returns list of remote and local lockers.
 | 
					
						
							| 
									
										
										
										
											2020-09-26 10:21:52 +08:00
										 |  |  | 	getLockers func() ([]dsync.NetLocker, string) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// getEndpoints returns list of endpoint strings belonging this set.
 | 
					
						
							|  |  |  | 	// some may be local and some remote.
 | 
					
						
							|  |  |  | 	getEndpoints func() []string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Locker mutex map.
 | 
					
						
							|  |  |  | 	nsMutex *nsLockMap | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Byte pools used for temporary i/o buffers.
 | 
					
						
							|  |  |  | 	bp *bpool.BytePoolCap | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | 	mrfOpCh chan partialOperation | 
					
						
							| 
									
										
										
										
											2017-08-15 09:08:42 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | // NewNSLock - initialize a new namespace RWLocker instance.
 | 
					
						
							| 
									
										
										
										
											2020-11-05 00:25:42 +08:00
										 |  |  | func (er erasureObjects) NewNSLock(bucket string, objects ...string) RWLocker { | 
					
						
							|  |  |  | 	return er.nsMutex.NewNSLock(er.getLockers, bucket, objects...) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-06 04:31:12 +08:00
										 |  |  | // SetDriveCount returns the current drives per set.
 | 
					
						
							|  |  |  | func (er erasureObjects) SetDriveCount() int { | 
					
						
							|  |  |  | 	return len(er.getDisks()) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | // Shutdown function for object storage interface.
 | 
					
						
							|  |  |  | func (er erasureObjects) Shutdown(ctx context.Context) error { | 
					
						
							|  |  |  | 	// Add any object layer shutdown activities here.
 | 
					
						
							|  |  |  | 	closeStorageDisks(er.getDisks()) | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // byDiskTotal is a collection satisfying sort.Interface.
 | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | type byDiskTotal []madmin.Disk | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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 { | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | 	return d[i].TotalSpace < d[j].TotalSpace | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func diskErrToDriveState(err error) (state string) { | 
					
						
							|  |  |  | 	state = madmin.DriveStateUnknown | 
					
						
							| 
									
										
										
										
											2020-08-04 09:17:48 +08:00
										 |  |  | 	switch { | 
					
						
							|  |  |  | 	case errors.Is(err, errDiskNotFound): | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | 		state = madmin.DriveStateOffline | 
					
						
							| 
									
										
										
										
											2020-08-04 09:17:48 +08:00
										 |  |  | 	case errors.Is(err, errCorruptedFormat): | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | 		state = madmin.DriveStateCorrupt | 
					
						
							| 
									
										
										
										
											2020-08-04 09:17:48 +08:00
										 |  |  | 	case errors.Is(err, errUnformattedDisk): | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | 		state = madmin.DriveStateUnformatted | 
					
						
							| 
									
										
										
										
											2020-08-04 09:17:48 +08:00
										 |  |  | 	case errors.Is(err, errDiskAccessDenied): | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | 		state = madmin.DriveStatePermission | 
					
						
							| 
									
										
										
										
											2020-08-04 09:17:48 +08:00
										 |  |  | 	case errors.Is(err, errFaultyDisk): | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | 		state = madmin.DriveStateFaulty | 
					
						
							| 
									
										
										
										
											2020-08-04 09:17:48 +08:00
										 |  |  | 	case err == nil: | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | 		state = madmin.DriveStateOk | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // getDisksInfo - fetch disks info across all other storage API.
 | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | func getDisksInfo(disks []StorageAPI, endpoints []string) (disksInfo []madmin.Disk, errs []error, onlineDisks, offlineDisks madmin.BackendDisks) { | 
					
						
							|  |  |  | 	disksInfo = make([]madmin.Disk, len(disks)) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	onlineDisks = make(madmin.BackendDisks) | 
					
						
							|  |  |  | 	offlineDisks = make(madmin.BackendDisks) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | 	for _, ep := range endpoints { | 
					
						
							|  |  |  | 		if _, ok := offlineDisks[ep]; !ok { | 
					
						
							|  |  |  | 			offlineDisks[ep] = 0 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | 		if _, ok := onlineDisks[ep]; !ok { | 
					
						
							|  |  |  | 			onlineDisks[ep] = 0 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-08-15 09:08:42 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-05-10 00:54:20 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	g := errgroup.WithNErrs(len(disks)) | 
					
						
							|  |  |  | 	for index := range disks { | 
					
						
							|  |  |  | 		index := index | 
					
						
							|  |  |  | 		g.Go(func() error { | 
					
						
							|  |  |  | 			if disks[index] == OfflineDisk { | 
					
						
							| 
									
										
										
										
											2020-09-17 12:14:35 +08:00
										 |  |  | 				logger.LogIf(GlobalContext, fmt.Errorf("%s: %s", errDiskNotFound, endpoints[index])) | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | 				disksInfo[index] = madmin.Disk{ | 
					
						
							|  |  |  | 					State:    diskErrToDriveState(errDiskNotFound), | 
					
						
							|  |  |  | 					Endpoint: endpoints[index], | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 				// Storage disk is empty, perhaps ignored disk or not available.
 | 
					
						
							|  |  |  | 				return errDiskNotFound | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-09-05 00:45:06 +08:00
										 |  |  | 			info, err := disks[index].DiskInfo(context.TODO()) | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | 			di := madmin.Disk{ | 
					
						
							| 
									
										
										
										
											2020-07-17 05:43:48 +08:00
										 |  |  | 				Endpoint:       endpoints[index], | 
					
						
							|  |  |  | 				DrivePath:      info.MountPath, | 
					
						
							|  |  |  | 				TotalSpace:     info.Total, | 
					
						
							|  |  |  | 				UsedSpace:      info.Used, | 
					
						
							|  |  |  | 				AvailableSpace: info.Free, | 
					
						
							|  |  |  | 				UUID:           info.ID, | 
					
						
							| 
									
										
										
										
											2020-08-19 05:37:26 +08:00
										 |  |  | 				RootDisk:       info.RootDisk, | 
					
						
							| 
									
										
										
										
											2020-09-29 10:39:32 +08:00
										 |  |  | 				Healing:        info.Healing, | 
					
						
							| 
									
										
										
										
											2020-07-17 05:43:48 +08:00
										 |  |  | 				State:          diskErrToDriveState(err), | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			if info.Total > 0 { | 
					
						
							|  |  |  | 				di.Utilization = float64(info.Used / info.Total * 100) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			disksInfo[index] = di | 
					
						
							|  |  |  | 			return err | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		}, index) | 
					
						
							| 
									
										
										
										
											2020-05-10 00:54:20 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	errs = g.Wait() | 
					
						
							|  |  |  | 	// Wait for the routines.
 | 
					
						
							|  |  |  | 	for i, diskInfoErr := range errs { | 
					
						
							| 
									
										
										
										
											2020-08-13 10:17:41 +08:00
										 |  |  | 		ep := disksInfo[i].Endpoint | 
					
						
							| 
									
										
										
										
											2020-10-03 07:19:44 +08:00
										 |  |  | 		if diskInfoErr != nil && !errors.Is(diskInfoErr, errUnformattedDisk) { | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | 			offlineDisks[ep]++ | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | 		onlineDisks[ep]++ | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-19 05:37:26 +08:00
										 |  |  | 	rootDiskCount := 0 | 
					
						
							|  |  |  | 	for _, di := range disksInfo { | 
					
						
							|  |  |  | 		if di.RootDisk { | 
					
						
							|  |  |  | 			rootDiskCount++ | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-25 04:23:16 +08:00
										 |  |  | 	// Count offline disks as well to ensure consistent
 | 
					
						
							|  |  |  | 	// reportability of offline drives on local setups.
 | 
					
						
							|  |  |  | 	if len(disksInfo) == (rootDiskCount + offlineDisks.Sum()) { | 
					
						
							| 
									
										
										
										
											2020-08-19 05:37:26 +08:00
										 |  |  | 		// Success.
 | 
					
						
							|  |  |  | 		return disksInfo, errs, onlineDisks, offlineDisks | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Root disk should be considered offline
 | 
					
						
							|  |  |  | 	for i := range disksInfo { | 
					
						
							|  |  |  | 		ep := disksInfo[i].Endpoint | 
					
						
							|  |  |  | 		if disksInfo[i].RootDisk { | 
					
						
							|  |  |  | 			offlineDisks[ep]++ | 
					
						
							|  |  |  | 			onlineDisks[ep]-- | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	return disksInfo, errs, onlineDisks, offlineDisks | 
					
						
							| 
									
										
										
										
											2017-08-15 09:08:42 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | // Get an aggregated storage info across all disks.
 | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | func getStorageInfo(disks []StorageAPI, endpoints []string) (StorageInfo, []error) { | 
					
						
							|  |  |  | 	disksInfo, errs, onlineDisks, offlineDisks := getDisksInfo(disks, endpoints) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Sort so that the first element is the smallest.
 | 
					
						
							|  |  |  | 	sort.Sort(byDiskTotal(disksInfo)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	storageInfo := StorageInfo{ | 
					
						
							| 
									
										
										
										
											2020-07-14 00:51:07 +08:00
										 |  |  | 		Disks: disksInfo, | 
					
						
							| 
									
										
										
										
											2017-08-15 09:08:42 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	storageInfo.Backend.Type = BackendErasure | 
					
						
							|  |  |  | 	storageInfo.Backend.OnlineDisks = onlineDisks | 
					
						
							|  |  |  | 	storageInfo.Backend.OfflineDisks = offlineDisks | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return storageInfo, errs | 
					
						
							| 
									
										
										
										
											2017-08-15 09:08:42 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | // StorageInfo - returns underlying storage statistics.
 | 
					
						
							|  |  |  | func (er erasureObjects) StorageInfo(ctx context.Context, local bool) (StorageInfo, []error) { | 
					
						
							|  |  |  | 	disks := er.getDisks() | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | 	endpoints := er.getEndpoints() | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	if local { | 
					
						
							|  |  |  | 		var localDisks []StorageAPI | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | 		var localEndpoints []string | 
					
						
							|  |  |  | 		for i, disk := range disks { | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 			if disk != nil { | 
					
						
							|  |  |  | 				if disk.IsLocal() { | 
					
						
							|  |  |  | 					// Append this local disk since local flag is true
 | 
					
						
							|  |  |  | 					localDisks = append(localDisks, disk) | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | 					localEndpoints = append(localEndpoints, endpoints[i]) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		disks = localDisks | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | 		endpoints = localEndpoints | 
					
						
							| 
									
										
										
										
											2018-08-07 06:14:08 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-06-30 04:07:26 +08:00
										 |  |  | 	return getStorageInfo(disks, endpoints) | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CrawlAndGetDataUsage will start crawling buckets and send updated totals as they are traversed.
 | 
					
						
							|  |  |  | // Updates are sent on a regular basis and the caller *must* consume them.
 | 
					
						
							|  |  |  | func (er erasureObjects) crawlAndGetDataUsage(ctx context.Context, buckets []BucketInfo, bf *bloomFilter, updates chan<- dataUsageCache) error { | 
					
						
							| 
									
										
										
										
											2020-08-26 01:55:15 +08:00
										 |  |  | 	if len(buckets) == 0 { | 
					
						
							| 
									
										
										
										
											2020-09-29 10:39:32 +08:00
										 |  |  | 		logger.Info(color.Green("data-crawl:") + " No buckets found, skipping crawl") | 
					
						
							| 
									
										
										
										
											2020-08-26 01:55:15 +08:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 01:55:15 +08:00
										 |  |  | 	// Collect disks we can use.
 | 
					
						
							| 
									
										
										
										
											2020-10-15 03:12:10 +08:00
										 |  |  | 	disks := er.getOnlineDisks() | 
					
						
							| 
									
										
										
										
											2020-08-26 01:55:15 +08:00
										 |  |  | 	if len(disks) == 0 { | 
					
						
							| 
									
										
										
										
											2020-09-29 10:39:32 +08:00
										 |  |  | 		logger.Info(color.Green("data-crawl:") + " all disks are offline or being healed, skipping crawl") | 
					
						
							| 
									
										
										
										
											2020-05-10 00:54:20 +08:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Load bucket totals
 | 
					
						
							|  |  |  | 	oldCache := dataUsageCache{} | 
					
						
							| 
									
										
										
										
											2020-09-29 10:39:32 +08:00
										 |  |  | 	if err := oldCache.load(ctx, er, dataUsageCacheName); err != nil { | 
					
						
							| 
									
										
										
										
											2018-04-06 06:04:40 +08:00
										 |  |  | 		return err | 
					
						
							| 
									
										
										
										
											2017-08-15 09:08:42 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	// New cache..
 | 
					
						
							|  |  |  | 	cache := dataUsageCache{ | 
					
						
							|  |  |  | 		Info: dataUsageCacheInfo{ | 
					
						
							|  |  |  | 			Name:      dataUsageRoot, | 
					
						
							|  |  |  | 			NextCycle: oldCache.Info.NextCycle, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		Cache: make(map[string]dataUsageEntry, len(oldCache.Cache)), | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-06-17 23:54:41 +08:00
										 |  |  | 	bloom := bf.bytes() | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	// Put all buckets into channel.
 | 
					
						
							|  |  |  | 	bucketCh := make(chan BucketInfo, len(buckets)) | 
					
						
							|  |  |  | 	// Add new buckets first
 | 
					
						
							|  |  |  | 	for _, b := range buckets { | 
					
						
							|  |  |  | 		if oldCache.find(b.Name) == nil { | 
					
						
							|  |  |  | 			bucketCh <- b | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-06-17 23:54:41 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-25 04:47:01 +08:00
										 |  |  | 	// Add existing buckets.
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	for _, b := range buckets { | 
					
						
							|  |  |  | 		e := oldCache.find(b.Name) | 
					
						
							|  |  |  | 		if e != nil { | 
					
						
							|  |  |  | 			cache.replace(b.Name, dataUsageRoot, *e) | 
					
						
							| 
									
										
										
										
											2020-08-25 04:47:01 +08:00
										 |  |  | 			bucketCh <- b | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-05-01 07:27:31 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	close(bucketCh) | 
					
						
							|  |  |  | 	bucketResults := make(chan dataUsageEntryInfo, len(disks)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Start async collector/saver.
 | 
					
						
							|  |  |  | 	// This goroutine owns the cache.
 | 
					
						
							|  |  |  | 	var saverWg sync.WaitGroup | 
					
						
							|  |  |  | 	saverWg.Add(1) | 
					
						
							|  |  |  | 	go func() { | 
					
						
							|  |  |  | 		const updateTime = 30 * time.Second | 
					
						
							|  |  |  | 		t := time.NewTicker(updateTime) | 
					
						
							|  |  |  | 		defer t.Stop() | 
					
						
							|  |  |  | 		defer saverWg.Done() | 
					
						
							|  |  |  | 		var lastSave time.Time | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	saveLoop: | 
					
						
							|  |  |  | 		for { | 
					
						
							|  |  |  | 			select { | 
					
						
							|  |  |  | 			case <-ctx.Done(): | 
					
						
							|  |  |  | 				// Return without saving.
 | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			case <-t.C: | 
					
						
							|  |  |  | 				if cache.Info.LastUpdate.Equal(lastSave) { | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				logger.LogIf(ctx, cache.save(ctx, er, dataUsageCacheName)) | 
					
						
							|  |  |  | 				updates <- cache.clone() | 
					
						
							|  |  |  | 				lastSave = cache.Info.LastUpdate | 
					
						
							|  |  |  | 			case v, ok := <-bucketResults: | 
					
						
							|  |  |  | 				if !ok { | 
					
						
							|  |  |  | 					break saveLoop | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				cache.replace(v.Name, v.Parent, v.Entry) | 
					
						
							|  |  |  | 				cache.Info.LastUpdate = time.Now() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// Save final state...
 | 
					
						
							|  |  |  | 		cache.Info.NextCycle++ | 
					
						
							|  |  |  | 		cache.Info.LastUpdate = time.Now() | 
					
						
							|  |  |  | 		logger.LogIf(ctx, cache.save(ctx, er, dataUsageCacheName)) | 
					
						
							|  |  |  | 		updates <- cache | 
					
						
							|  |  |  | 	}() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Start one crawler per disk
 | 
					
						
							|  |  |  | 	var wg sync.WaitGroup | 
					
						
							|  |  |  | 	wg.Add(len(disks)) | 
					
						
							|  |  |  | 	for i := range disks { | 
					
						
							|  |  |  | 		go func(i int) { | 
					
						
							|  |  |  | 			defer wg.Done() | 
					
						
							|  |  |  | 			disk := disks[i] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for bucket := range bucketCh { | 
					
						
							|  |  |  | 				select { | 
					
						
							|  |  |  | 				case <-ctx.Done(): | 
					
						
							|  |  |  | 					return | 
					
						
							|  |  |  | 				default: | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// Load cache for bucket
 | 
					
						
							|  |  |  | 				cacheName := pathJoin(bucket.Name, dataUsageCacheName) | 
					
						
							|  |  |  | 				cache := dataUsageCache{} | 
					
						
							|  |  |  | 				logger.LogIf(ctx, cache.load(ctx, er, cacheName)) | 
					
						
							|  |  |  | 				if cache.Info.Name == "" { | 
					
						
							|  |  |  | 					cache.Info.Name = bucket.Name | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-06-17 23:54:41 +08:00
										 |  |  | 				cache.Info.BloomFilter = bloom | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 				if cache.Info.Name != bucket.Name { | 
					
						
							|  |  |  | 					logger.LogIf(ctx, fmt.Errorf("cache name mismatch: %s != %s", cache.Info.Name, bucket.Name)) | 
					
						
							|  |  |  | 					cache.Info = dataUsageCacheInfo{ | 
					
						
							|  |  |  | 						Name:       bucket.Name, | 
					
						
							|  |  |  | 						LastUpdate: time.Time{}, | 
					
						
							|  |  |  | 						NextCycle:  0, | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// Calc usage
 | 
					
						
							|  |  |  | 				before := cache.Info.LastUpdate | 
					
						
							| 
									
										
										
										
											2020-09-29 10:39:32 +08:00
										 |  |  | 				var err error | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 				cache, err = disk.CrawlAndGetDataUsage(ctx, cache) | 
					
						
							| 
									
										
										
										
											2020-06-17 23:54:41 +08:00
										 |  |  | 				cache.Info.BloomFilter = nil | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 				if err != nil { | 
					
						
							|  |  |  | 					logger.LogIf(ctx, err) | 
					
						
							|  |  |  | 					if cache.Info.LastUpdate.After(before) { | 
					
						
							|  |  |  | 						logger.LogIf(ctx, cache.save(ctx, er, cacheName)) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				var root dataUsageEntry | 
					
						
							|  |  |  | 				if r := cache.root(); r != nil { | 
					
						
							|  |  |  | 					root = cache.flatten(*r) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				bucketResults <- dataUsageEntryInfo{ | 
					
						
							|  |  |  | 					Name:   cache.Info.Name, | 
					
						
							|  |  |  | 					Parent: dataUsageRoot, | 
					
						
							|  |  |  | 					Entry:  root, | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				// Save cache
 | 
					
						
							|  |  |  | 				logger.LogIf(ctx, cache.save(ctx, er, cacheName)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}(i) | 
					
						
							| 
									
										
										
										
											2019-01-17 20:58:18 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-06-13 11:04:01 +08:00
										 |  |  | 	wg.Wait() | 
					
						
							|  |  |  | 	close(bucketResults) | 
					
						
							|  |  |  | 	saverWg.Wait() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } |