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.

650 lines
19 KiB

fs: Break fs package to top-level and introduce ObjectAPI interface. ObjectAPI interface brings in changes needed for XL ObjectAPI layer. The new interface for any ObjectAPI layer is as below ``` // ObjectAPI interface. type ObjectAPI interface { // Bucket resource API. DeleteBucket(bucket string) *probe.Error ListBuckets() ([]BucketInfo, *probe.Error) MakeBucket(bucket string) *probe.Error GetBucketInfo(bucket string) (BucketInfo, *probe.Error) // Bucket query API. ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsResult, *probe.Error) ListMultipartUploads(bucket string, resources BucketMultipartResourcesMetadata) (BucketMultipartResourcesMetadata, *probe.Error) // Object resource API. GetObject(bucket, object string, startOffset int64) (io.ReadCloser, *probe.Error) GetObjectInfo(bucket, object string) (ObjectInfo, *probe.Error) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (ObjectInfo, *probe.Error) DeleteObject(bucket, object string) *probe.Error // Object query API. NewMultipartUpload(bucket, object string) (string, *probe.Error) PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, *probe.Error) ListObjectParts(bucket, object string, resources ObjectResourcesMetadata) (ObjectResourcesMetadata, *probe.Error) CompleteMultipartUpload(bucket string, object string, uploadID string, parts []CompletePart) (ObjectInfo, *probe.Error) AbortMultipartUpload(bucket, object, uploadID string) *probe.Error } ```
9 years ago
  1. /*
  2. * Minio Cloud Storage, (C) 2015, 2016, 2017 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. "errors"
  19. "fmt"
  20. "net"
  21. "net/url"
  22. "os"
  23. "path"
  24. "sort"
  25. "strconv"
  26. "strings"
  27. "time"
  28. "runtime"
  29. "github.com/minio/cli"
  30. "github.com/minio/mc/pkg/console"
  31. )
  32. var serverFlags = []cli.Flag{
  33. cli.StringFlag{
  34. Name: "address",
  35. Value: ":9000",
  36. Usage: "Bind to a specific ADDRESS:PORT, ADDRESS can be an IP or hostname.",
  37. },
  38. }
  39. var serverCmd = cli.Command{
  40. Name: "server",
  41. Usage: "Start object storage server.",
  42. Flags: append(serverFlags, globalFlags...),
  43. Action: serverMain,
  44. CustomHelpTemplate: `NAME:
  45. {{.HelpName}} - {{.Usage}}
  46. USAGE:
  47. {{.HelpName}} {{if .VisibleFlags}}[FLAGS] {{end}}PATH [PATH...]
  48. {{if .VisibleFlags}}
  49. FLAGS:
  50. {{range .VisibleFlags}}{{.}}
  51. {{end}}{{end}}
  52. ENVIRONMENT VARIABLES:
  53. ACCESS:
  54. MINIO_ACCESS_KEY: Custom username or access key of 5 to 20 characters in length.
  55. MINIO_SECRET_KEY: Custom password or secret key of 8 to 40 characters in length.
  56. BROWSER:
  57. MINIO_BROWSER: To disable web browser access, set this value to "off".
  58. EXAMPLES:
  59. 1. Start minio server on "/home/shared" directory.
  60. $ {{.HelpName}} /home/shared
  61. 2. Start minio server bound to a specific ADDRESS:PORT.
  62. $ {{.HelpName}} --address 192.168.1.101:9000 /home/shared
  63. 3. Start erasure coded minio server on a 12 disks server.
  64. $ {{.HelpName}} /mnt/export1/ /mnt/export2/ /mnt/export3/ /mnt/export4/ \
  65. /mnt/export5/ /mnt/export6/ /mnt/export7/ /mnt/export8/ /mnt/export9/ \
  66. /mnt/export10/ /mnt/export11/ /mnt/export12/
  67. 4. Start erasure coded distributed minio server on a 4 node setup with 1 drive each. Run following commands on all the 4 nodes.
  68. $ export MINIO_ACCESS_KEY=minio
  69. $ export MINIO_SECRET_KEY=miniostorage
  70. $ {{.HelpName}} http://192.168.1.11/mnt/export/ http://192.168.1.12/mnt/export/ \
  71. http://192.168.1.13/mnt/export/ http://192.168.1.14/mnt/export/
  72. `,
  73. }
  74. // Check for updates and print a notification message
  75. func checkUpdate() {
  76. // Its OK to ignore any errors during getUpdateInfo() here.
  77. if older, downloadURL, err := getUpdateInfo(1 * time.Second); err == nil {
  78. if older > time.Duration(0) {
  79. console.Println(colorizeUpdateMessage(downloadURL, older))
  80. }
  81. }
  82. }
  83. // envParams holds all env parameters
  84. type envParams struct {
  85. creds credential
  86. browser string
  87. }
  88. func migrate() {
  89. // Migrate config file
  90. err := migrateConfig()
  91. fatalIf(err, "Config migration failed.")
  92. // Migrate other configs here.
  93. }
  94. func enableLoggers() {
  95. // Enable all loggers here.
  96. enableConsoleLogger()
  97. enableFileLogger()
  98. // Add your logger here.
  99. }
  100. // Initializes a new config if it doesn't exist, else migrates any old config
  101. // to newer config and finally loads the config to memory.
  102. func initConfig() {
  103. accessKey := os.Getenv("MINIO_ACCESS_KEY")
  104. secretKey := os.Getenv("MINIO_SECRET_KEY")
  105. var cred credential
  106. var err error
  107. if accessKey != "" && secretKey != "" {
  108. if cred, err = createCredential(accessKey, secretKey); err != nil {
  109. console.Fatalf("Invalid access/secret Key set in environment. Err: %s.\n", err)
  110. }
  111. // credential Envs are set globally.
  112. globalIsEnvCreds = true
  113. }
  114. browser := os.Getenv("MINIO_BROWSER")
  115. if browser != "" {
  116. if !(strings.EqualFold(browser, "off") || strings.EqualFold(browser, "on")) {
  117. console.Fatalf("Invalid value ‘%s’ in MINIO_BROWSER environment variable.", browser)
  118. }
  119. // browser Envs are set globally, this doesn't represent
  120. // if browser is turned off or on.
  121. globalIsEnvBrowser = true
  122. }
  123. envs := envParams{
  124. creds: cred,
  125. browser: browser,
  126. }
  127. // Config file does not exist, we create it fresh and return upon success.
  128. if !isConfigFileExists() {
  129. if err := newConfig(envs); err != nil {
  130. console.Fatalf("Unable to initialize minio config for the first time. Error: %s.\n", err)
  131. }
  132. console.Println("Created minio configuration file successfully at " + getConfigDir())
  133. return
  134. }
  135. // Migrate any old version of config / state files to newer format.
  136. migrate()
  137. // Validate config file
  138. if err := validateConfig(); err != nil {
  139. console.Fatalf("Cannot validate configuration file. Error: %s\n", err)
  140. }
  141. // Once we have migrated all the old config, now load them.
  142. if err := loadConfig(envs); err != nil {
  143. console.Fatalf("Unable to initialize minio config. Error: %s.\n", err)
  144. }
  145. }
  146. // Generic Minio initialization to create/load config, prepare loggers, etc..
  147. func minioInit(ctx *cli.Context) {
  148. // Create certs path.
  149. fatalIf(createConfigDir(), "Unable to create \"certs\" directory.")
  150. // Is TLS configured?.
  151. globalIsSSL = isSSL()
  152. // Initialize minio server config.
  153. initConfig()
  154. // Enable all loggers by now so we can use errorIf() and fatalIf()
  155. enableLoggers()
  156. // Init the error tracing module.
  157. initError()
  158. }
  159. type serverCmdConfig struct {
  160. serverAddr string
  161. endpoints []*url.URL
  162. }
  163. // Parse an array of end-points (from the command line)
  164. func parseStorageEndpoints(eps []string) (endpoints []*url.URL, err error) {
  165. for _, ep := range eps {
  166. if ep == "" {
  167. return nil, errInvalidArgument
  168. }
  169. var u *url.URL
  170. u, err = url.Parse(ep)
  171. if err != nil {
  172. return nil, err
  173. }
  174. if u.Host != "" {
  175. _, port, err := net.SplitHostPort(u.Host)
  176. // Ignore the missing port error as the default port can be globalMinioPort.
  177. if err != nil && !strings.Contains(err.Error(), "missing port in address") {
  178. return nil, err
  179. }
  180. if globalMinioHost == "" {
  181. // For ex.: minio server host1:port1 host2:port2...
  182. // we return error as port is configurable only
  183. // using "--address :port"
  184. if port != "" {
  185. return nil, fmt.Errorf("Invalid Argument %s, port configurable using --address :<port>", u.Host)
  186. }
  187. u.Host = net.JoinHostPort(u.Host, globalMinioPort)
  188. } else {
  189. // For ex.: minio server --address host:port host1:port1 host2:port2...
  190. // i.e if "--address host:port" is specified
  191. // port info in u.Host is mandatory else return error.
  192. if port == "" {
  193. return nil, fmt.Errorf("Invalid Argument %s, port mandatory when --address <host>:<port> is used", u.Host)
  194. }
  195. }
  196. }
  197. endpoints = append(endpoints, u)
  198. }
  199. return endpoints, nil
  200. }
  201. // initServer initialize server config.
  202. func initServerConfig(c *cli.Context) {
  203. // Initialization such as config generating/loading config, enable logging, ..
  204. minioInit(c)
  205. // Load user supplied root CAs
  206. fatalIf(loadRootCAs(), "Unable to load a CA files")
  207. // Set system resources to maximum.
  208. errorIf(setMaxResources(), "Unable to change resource limit")
  209. }
  210. // Validate if input disks are sufficient for initializing XL.
  211. func checkSufficientDisks(eps []*url.URL) error {
  212. // Verify total number of disks.
  213. total := len(eps)
  214. if total > maxErasureBlocks {
  215. return errXLMaxDisks
  216. }
  217. if total < minErasureBlocks {
  218. return errXLMinDisks
  219. }
  220. // isEven function to verify if a given number if even.
  221. isEven := func(number int) bool {
  222. return number%2 == 0
  223. }
  224. // Verify if we have even number of disks.
  225. // only combination of 4, 6, 8, 10, 12, 14, 16 are supported.
  226. if !isEven(total) {
  227. return errXLNumDisks
  228. }
  229. // Success.
  230. return nil
  231. }
  232. // Returns if slice of disks is a distributed setup.
  233. func isDistributedSetup(eps []*url.URL) bool {
  234. // Validate if one the disks is not local.
  235. for _, ep := range eps {
  236. if !isLocalStorage(ep) {
  237. // One or more disks supplied as arguments are
  238. // not attached to the local node.
  239. return true
  240. }
  241. }
  242. return false
  243. }
  244. // Returns true if path is empty, or equals to '.', '/', '\' characters.
  245. func isPathSentinel(path string) bool {
  246. return path == "" || path == "." || path == "/" || path == `\`
  247. }
  248. // Returned when path is empty or root path.
  249. var errEmptyRootPath = errors.New("Empty or root path is not allowed")
  250. // Invalid scheme passed.
  251. var errInvalidScheme = errors.New("Invalid scheme")
  252. // Check if endpoint is in expected syntax by valid scheme/path across all platforms.
  253. func checkEndpointURL(endpointURL *url.URL) (err error) {
  254. // Applicable to all OS.
  255. if endpointURL.Scheme == "" || endpointURL.Scheme == httpScheme || endpointURL.Scheme == httpsScheme {
  256. if isPathSentinel(path.Clean(endpointURL.Path)) {
  257. err = errEmptyRootPath
  258. }
  259. return err
  260. }
  261. // Applicable to Windows only.
  262. if runtime.GOOS == globalWindowsOSName {
  263. // On Windows, endpoint can be a path with drive eg. C:\Export and its URL.Scheme is 'C'.
  264. // Check if URL.Scheme is a single letter alphabet to represent a drive.
  265. // Note: URL.Parse() converts scheme into lower case always.
  266. if len(endpointURL.Scheme) == 1 && endpointURL.Scheme[0] >= 'a' && endpointURL.Scheme[0] <= 'z' {
  267. // If endpoint is C:\ or C:\export, URL.Path does not have path information like \ or \export
  268. // hence we directly work with endpoint.
  269. if isPathSentinel(strings.SplitN(path.Clean(endpointURL.String()), ":", 2)[1]) {
  270. err = errEmptyRootPath
  271. }
  272. return err
  273. }
  274. }
  275. return errInvalidScheme
  276. }
  277. // Check if endpoints are in expected syntax by valid scheme/path across all platforms.
  278. func checkEndpointsSyntax(eps []*url.URL, disks []string) error {
  279. for i, u := range eps {
  280. if err := checkEndpointURL(u); err != nil {
  281. return fmt.Errorf("%s: %s (%s)", err.Error(), u.Path, disks[i])
  282. }
  283. }
  284. return nil
  285. }
  286. // Make sure all the command line parameters are OK and exit in case of invalid parameters.
  287. func checkServerSyntax(c *cli.Context) {
  288. serverAddr := c.String("address")
  289. host, portStr, err := net.SplitHostPort(serverAddr)
  290. fatalIf(err, "Unable to parse %s.", serverAddr)
  291. // Verify syntax for all the XL disks.
  292. disks := c.Args()
  293. // Parse disks check if they comply with expected URI style.
  294. endpoints, err := parseStorageEndpoints(disks)
  295. fatalIf(err, "Unable to parse storage endpoints %s", strings.Join(disks, " "))
  296. // Validate if endpoints follow the expected syntax.
  297. err = checkEndpointsSyntax(endpoints, disks)
  298. fatalIf(err, "Invalid endpoints found %s", strings.Join(disks, " "))
  299. // Validate for duplicate endpoints are supplied.
  300. err = checkDuplicateEndpoints(endpoints)
  301. fatalIf(err, "Duplicate entries in %s", strings.Join(disks, " "))
  302. if len(endpoints) > 1 {
  303. // Validate if we have sufficient disks for XL setup.
  304. err = checkSufficientDisks(endpoints)
  305. fatalIf(err, "Insufficient number of disks.")
  306. } else {
  307. // Validate if we have invalid disk for FS setup.
  308. if endpoints[0].Host != "" && endpoints[0].Scheme != "" {
  309. fatalIf(errInvalidArgument, "%s, FS setup expects a filesystem path", endpoints[0])
  310. }
  311. }
  312. if !isDistributedSetup(endpoints) {
  313. // for FS and singlenode-XL validation is done, return.
  314. return
  315. }
  316. // Rest of the checks applies only to distributed XL setup.
  317. if host != "" {
  318. // We are here implies --address host:port is passed, hence the user is trying
  319. // to run one minio process per export disk.
  320. if portStr == "" {
  321. fatalIf(errInvalidArgument, "Port missing, Host:Port should be specified for --address")
  322. }
  323. foundCnt := 0
  324. for _, ep := range endpoints {
  325. if ep.Host == serverAddr {
  326. foundCnt++
  327. }
  328. }
  329. if foundCnt == 0 {
  330. // --address host:port should be available in the XL disk list.
  331. fatalIf(errInvalidArgument, "%s is not available in %s", serverAddr, strings.Join(disks, " "))
  332. }
  333. if foundCnt > 1 {
  334. // --address host:port should match exactly one entry in the XL disk list.
  335. fatalIf(errInvalidArgument, "%s matches % entries in %s", serverAddr, foundCnt, strings.Join(disks, " "))
  336. }
  337. }
  338. for _, ep := range endpoints {
  339. if ep.Scheme == httpsScheme && !globalIsSSL {
  340. // Certificates should be provided for https configuration.
  341. fatalIf(errInvalidArgument, "Certificates not provided for secure configuration")
  342. }
  343. }
  344. }
  345. // Checks if any of the endpoints supplied is local to this server.
  346. func isAnyEndpointLocal(eps []*url.URL) bool {
  347. anyLocalEp := false
  348. for _, ep := range eps {
  349. if isLocalStorage(ep) {
  350. anyLocalEp = true
  351. break
  352. }
  353. }
  354. return anyLocalEp
  355. }
  356. // Returned when there are no ports.
  357. var errEmptyPort = errors.New("Port cannot be empty or '0', please use `--address` to pick a specific port")
  358. // Convert an input address of form host:port into, host and port, returns if any.
  359. func getHostPort(address string) (host, port string, err error) {
  360. // Check if requested port is available.
  361. host, port, err = net.SplitHostPort(address)
  362. if err != nil {
  363. return "", "", err
  364. }
  365. // Empty ports.
  366. if port == "0" || port == "" {
  367. // Port zero or empty means use requested to choose any freely available
  368. // port. Avoid this since it won't work with any configured clients,
  369. // can lead to serious loss of availability.
  370. return "", "", errEmptyPort
  371. }
  372. // Parse port.
  373. if _, err = strconv.Atoi(port); err != nil {
  374. return "", "", err
  375. }
  376. if runtime.GOOS == "darwin" {
  377. // On macOS, if a process already listens on 127.0.0.1:PORT, net.Listen() falls back
  378. // to IPv6 address ie minio will start listening on IPv6 address whereas another
  379. // (non-)minio process is listening on IPv4 of given port.
  380. // To avoid this error sutiation we check for port availability only for macOS.
  381. if err = checkPortAvailability(port); err != nil {
  382. return "", "", err
  383. }
  384. }
  385. // Success.
  386. return host, port, nil
  387. }
  388. // serverMain handler called for 'minio server' command.
  389. func serverMain(c *cli.Context) {
  390. if !c.Args().Present() || c.Args().First() == "help" {
  391. cli.ShowCommandHelpAndExit(c, "server", 1)
  392. }
  393. // Get quiet flag from command line argument.
  394. quietFlag := c.Bool("quiet") || c.GlobalBool("quiet")
  395. // Get configuration directory from command line argument.
  396. configDir := c.String("config-dir")
  397. if !c.IsSet("config-dir") && c.GlobalIsSet("config-dir") {
  398. configDir = c.GlobalString("config-dir")
  399. }
  400. if configDir == "" {
  401. console.Fatalln("Configuration directory cannot be empty.")
  402. }
  403. // Set configuration directory.
  404. setConfigDir(configDir)
  405. // Start profiler if env is set.
  406. if profiler := os.Getenv("_MINIO_PROFILER"); profiler != "" {
  407. globalProfiler = startProfiler(profiler)
  408. }
  409. // Initializes server config, certs, logging and system settings.
  410. initServerConfig(c)
  411. // Check for new updates from dl.minio.io.
  412. if !quietFlag {
  413. checkUpdate()
  414. }
  415. // Server address.
  416. serverAddr := c.String("address")
  417. var err error
  418. globalMinioHost, globalMinioPort, err = getHostPort(serverAddr)
  419. fatalIf(err, "Unable to extract host and port %s", serverAddr)
  420. // Check server syntax and exit in case of errors.
  421. // Done after globalMinioHost and globalMinioPort is set
  422. // as parseStorageEndpoints() depends on it.
  423. checkServerSyntax(c)
  424. // Disks to be used in server init.
  425. endpoints, err := parseStorageEndpoints(c.Args())
  426. fatalIf(err, "Unable to parse storage endpoints %s", c.Args())
  427. // Should exit gracefully if none of the endpoints passed
  428. // as command line args are local to this server.
  429. if !isAnyEndpointLocal(endpoints) {
  430. fatalIf(errInvalidArgument, "None of the disks passed as command line args are local to this server.")
  431. }
  432. // Sort endpoints for consistent ordering across multiple
  433. // nodes in a distributed setup. This is to avoid format.json
  434. // corruption if the disks aren't supplied in the same order
  435. // on all nodes.
  436. sort.Sort(byHostPath(endpoints))
  437. // Configure server.
  438. srvConfig := serverCmdConfig{
  439. serverAddr: serverAddr,
  440. endpoints: endpoints,
  441. }
  442. // Check if endpoints are part of distributed setup.
  443. globalIsDistXL = isDistributedSetup(endpoints)
  444. // Set nodes for dsync for distributed setup.
  445. if globalIsDistXL {
  446. fatalIf(initDsyncNodes(endpoints), "Unable to initialize distributed locking clients")
  447. }
  448. // Set globalIsXL if erasure code backend is about to be
  449. // initialized for the given endpoints.
  450. if len(endpoints) > 1 {
  451. globalIsXL = true
  452. }
  453. // Initialize name space lock.
  454. initNSLock(globalIsDistXL)
  455. // Configure server.
  456. handler, err := configureServerHandler(srvConfig)
  457. fatalIf(err, "Unable to configure one of server's RPC services.")
  458. // Initialize a new HTTP server.
  459. apiServer := NewServerMux(serverAddr, handler)
  460. // Set the global minio addr for this server.
  461. globalMinioAddr = getLocalAddress(srvConfig)
  462. // Initialize S3 Peers inter-node communication only in distributed setup.
  463. initGlobalS3Peers(endpoints)
  464. // Initialize Admin Peers inter-node communication only in distributed setup.
  465. initGlobalAdminPeers(endpoints)
  466. // Determine API endpoints where we are going to serve the S3 API from.
  467. apiEndPoints, err := finalizeAPIEndpoints(apiServer.Addr)
  468. fatalIf(err, "Unable to finalize API endpoints for %s", apiServer.Addr)
  469. // Set the global API endpoints value.
  470. globalAPIEndpoints = apiEndPoints
  471. // Start server, automatically configures TLS if certs are available.
  472. go func() {
  473. cert, key := "", ""
  474. if globalIsSSL {
  475. cert, key = getPublicCertFile(), getPrivateKeyFile()
  476. }
  477. fatalIf(apiServer.ListenAndServe(cert, key), "Failed to start minio server.")
  478. }()
  479. // Set endpoints of []*url.URL type to globalEndpoints.
  480. globalEndpoints = endpoints
  481. newObject, err := newObjectLayer(srvConfig)
  482. fatalIf(err, "Initializing object layer failed")
  483. globalObjLayerMutex.Lock()
  484. globalObjectAPI = newObject
  485. globalObjLayerMutex.Unlock()
  486. // Prints the formatted startup message once object layer is initialized.
  487. if !quietFlag {
  488. printStartupMessage(apiEndPoints)
  489. }
  490. // Set uptime time after object layer has initialized.
  491. globalBootTime = time.Now().UTC()
  492. // Waits on the server.
  493. <-globalServiceDoneCh
  494. }
  495. // Initialize object layer with the supplied disks, objectLayer is nil upon any error.
  496. func newObjectLayer(srvCmdCfg serverCmdConfig) (newObject ObjectLayer, err error) {
  497. // For FS only, directly use the disk.
  498. isFS := len(srvCmdCfg.endpoints) == 1
  499. if isFS {
  500. // Unescape is needed for some UNC paths on windows
  501. // which are of this form \\127.0.0.1\\export\test.
  502. var fsPath string
  503. fsPath, err = url.QueryUnescape(srvCmdCfg.endpoints[0].String())
  504. if err != nil {
  505. return nil, err
  506. }
  507. // Initialize new FS object layer.
  508. newObject, err = newFSObjectLayer(fsPath)
  509. if err != nil {
  510. return nil, err
  511. }
  512. // FS initialized, return.
  513. return newObject, nil
  514. }
  515. // First disk argument check if it is local.
  516. firstDisk := isLocalStorage(srvCmdCfg.endpoints[0])
  517. // Initialize storage disks.
  518. storageDisks, err := initStorageDisks(srvCmdCfg.endpoints)
  519. if err != nil {
  520. return nil, err
  521. }
  522. // Wait for formatting disks for XL backend.
  523. var formattedDisks []StorageAPI
  524. formattedDisks, err = waitForFormatXLDisks(firstDisk, srvCmdCfg.endpoints, storageDisks)
  525. if err != nil {
  526. return nil, err
  527. }
  528. // Cleanup objects that weren't successfully written into the namespace.
  529. if err = houseKeeping(storageDisks); err != nil {
  530. return nil, err
  531. }
  532. // Once XL formatted, initialize object layer.
  533. newObject, err = newXLObjectLayer(formattedDisks)
  534. if err != nil {
  535. return nil, err
  536. }
  537. // XL initialized, return.
  538. return newObject, nil
  539. }