You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

226 lines
6.6 KiB

  1. /*
  2. * Minio Cloud Storage, (C) 2016 Minio, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package cmd
  17. import (
  18. "fmt"
  19. "sort"
  20. "github.com/minio/minio/pkg/disk"
  21. "github.com/minio/minio/pkg/objcache"
  22. )
  23. // XL constants.
  24. const (
  25. // Format config file carries backend format specific details.
  26. formatConfigFile = "format.json"
  27. // Format config tmp file carries backend format.
  28. formatConfigFileTmp = "format.json.tmp"
  29. // XL metadata file carries per object metadata.
  30. xlMetaJSONFile = "xl.json"
  31. // Uploads metadata file carries per multipart object metadata.
  32. uploadsJSONFile = "uploads.json"
  33. // 8GiB cache by default.
  34. maxCacheSize = 8 * 1024 * 1024 * 1024
  35. // Maximum erasure blocks.
  36. maxErasureBlocks = 16
  37. // Minimum erasure blocks.
  38. minErasureBlocks = 4
  39. )
  40. // xlObjects - Implements XL object layer.
  41. type xlObjects struct {
  42. storageDisks []StorageAPI // Collection of initialized backend disks.
  43. dataBlocks int // dataBlocks count caculated for erasure.
  44. parityBlocks int // parityBlocks count calculated for erasure.
  45. readQuorum int // readQuorum minimum required disks to read data.
  46. writeQuorum int // writeQuorum minimum required disks to write data.
  47. // ListObjects pool management.
  48. listPool *treeWalkPool
  49. // Object cache for caching objects.
  50. objCache *objcache.Cache
  51. // Object cache enabled.
  52. objCacheEnabled bool
  53. }
  54. // list of all errors that can be ignored in tree walk operation in XL
  55. var xlTreeWalkIgnoredErrs = []error{
  56. errFileNotFound,
  57. errVolumeNotFound,
  58. errDiskNotFound,
  59. errDiskAccessDenied,
  60. errFaultyDisk,
  61. }
  62. func healFormatXL(storageDisks []StorageAPI) error {
  63. // Attempt to load all `format.json`.
  64. formatConfigs, sErrs := loadAllFormats(storageDisks)
  65. // Generic format check validates
  66. // if (no quorum) return error
  67. // if (disks not recognized) // Always error.
  68. if err := genericFormatCheck(formatConfigs, sErrs); err != nil {
  69. return err
  70. }
  71. // Handles different cases properly.
  72. switch reduceFormatErrs(sErrs, len(storageDisks)) {
  73. case errCorruptedFormat:
  74. if err := healFormatXLCorruptedDisks(storageDisks); err != nil {
  75. return fmt.Errorf("Unable to repair corrupted format, %s", err)
  76. }
  77. case errSomeDiskUnformatted:
  78. // All drives online but some report missing format.json.
  79. if err := healFormatXLFreshDisks(storageDisks); err != nil {
  80. // There was an unexpected unrecoverable error during healing.
  81. return fmt.Errorf("Unable to heal backend %s", err)
  82. }
  83. case errSomeDiskOffline:
  84. // FIXME: in future.
  85. return fmt.Errorf("Unable to initialize format %s and %s", errSomeDiskOffline, errSomeDiskUnformatted)
  86. }
  87. return nil
  88. }
  89. // newXLObjects - initialize new xl object layer.
  90. func newXLObjects(storageDisks []StorageAPI) (ObjectLayer, error) {
  91. if storageDisks == nil {
  92. return nil, errInvalidArgument
  93. }
  94. // Runs house keeping code, like t, cleaning up tmp files etc.
  95. if err := xlHouseKeeping(storageDisks); err != nil {
  96. return nil, err
  97. }
  98. readQuorum := len(storageDisks) / 2
  99. writeQuorum := len(storageDisks)/2 + 1
  100. // Load saved XL format.json and validate.
  101. newStorageDisks, err := loadFormatXL(storageDisks, readQuorum)
  102. if err != nil {
  103. return nil, fmt.Errorf("Unable to recognize backend format, %s", err)
  104. }
  105. // Calculate data and parity blocks.
  106. dataBlocks, parityBlocks := len(newStorageDisks)/2, len(newStorageDisks)/2
  107. // Initialize object cache.
  108. objCache := objcache.New(globalMaxCacheSize, globalCacheExpiry)
  109. // Initialize list pool.
  110. listPool := newTreeWalkPool(globalLookupTimeout)
  111. // Initialize xl objects.
  112. xl := xlObjects{
  113. storageDisks: newStorageDisks,
  114. dataBlocks: dataBlocks,
  115. parityBlocks: parityBlocks,
  116. listPool: listPool,
  117. objCache: objCache,
  118. objCacheEnabled: globalMaxCacheSize > 0,
  119. }
  120. // Figure out read and write quorum based on number of storage disks.
  121. // READ and WRITE quorum is always set to (N/2) number of disks.
  122. xl.readQuorum = readQuorum
  123. xl.writeQuorum = writeQuorum
  124. // Return successfully initialized object layer.
  125. return xl, nil
  126. }
  127. // Shutdown function for object storage interface.
  128. func (xl xlObjects) Shutdown() error {
  129. // Add any object layer shutdown activities here.
  130. return nil
  131. }
  132. // byDiskTotal is a collection satisfying sort.Interface.
  133. type byDiskTotal []disk.Info
  134. func (d byDiskTotal) Len() int { return len(d) }
  135. func (d byDiskTotal) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
  136. func (d byDiskTotal) Less(i, j int) bool {
  137. return d[i].Total < d[j].Total
  138. }
  139. // getDisksInfo - fetch disks info across all other storage API.
  140. func getDisksInfo(disks []StorageAPI) (disksInfo []disk.Info, onlineDisks int, offlineDisks int) {
  141. for _, storageDisk := range disks {
  142. if storageDisk == nil {
  143. // Storage disk is empty, perhaps ignored disk or not available.
  144. offlineDisks++
  145. continue
  146. }
  147. info, err := storageDisk.DiskInfo()
  148. if err != nil {
  149. errorIf(err, "Unable to fetch disk info for %#v", storageDisk)
  150. if err == errDiskNotFound {
  151. offlineDisks++
  152. }
  153. continue
  154. }
  155. onlineDisks++
  156. disksInfo = append(disksInfo, info)
  157. }
  158. // Sort so that the first element is the smallest.
  159. sort.Sort(byDiskTotal(disksInfo))
  160. // Success.
  161. return disksInfo, onlineDisks, offlineDisks
  162. }
  163. // Get an aggregated storage info across all disks.
  164. func getStorageInfo(disks []StorageAPI) StorageInfo {
  165. disksInfo, onlineDisks, offlineDisks := getDisksInfo(disks)
  166. if len(disksInfo) == 0 {
  167. return StorageInfo{
  168. Total: -1,
  169. Free: -1,
  170. }
  171. }
  172. // Return calculated storage info, choose the lowest Total and
  173. // Free as the total aggregated values. Total capacity is always
  174. // the multiple of smallest disk among the disk list.
  175. storageInfo := StorageInfo{
  176. Total: disksInfo[0].Total * int64(onlineDisks),
  177. Free: disksInfo[0].Free * int64(onlineDisks),
  178. }
  179. storageInfo.Backend.Type = XL
  180. storageInfo.Backend.OnlineDisks = onlineDisks
  181. storageInfo.Backend.OfflineDisks = offlineDisks
  182. return storageInfo
  183. }
  184. // StorageInfo - returns underlying storage statistics.
  185. func (xl xlObjects) StorageInfo() StorageInfo {
  186. storageInfo := getStorageInfo(xl.storageDisks)
  187. storageInfo.Backend.ReadQuorum = xl.readQuorum
  188. storageInfo.Backend.WriteQuorum = xl.writeQuorum
  189. return storageInfo
  190. }