Compare commits

..

394 Commits

Author SHA1 Message Date
Harshavardhana 18b3871705 Add erasure code. 2016-06-03 12:50:36 -07:00
Harshavardhana 73ddb5be75 Merge pull request #1850 from harshavardhana/list-rewrite
XL: Implement ListObjects channel and pool management.
2016-06-03 12:30:06 -07:00
Krishna Srinivas 002c5bf7dd XL: Treewalk handle all the race conditions and blocking channels. 2016-06-03 12:17:54 -07:00
Harshavardhana 1cf1532ca3 XL: Implement ListObjects channel and pool management. 2016-06-03 12:17:54 -07:00
Harshavardhana 70a1231f02 Merge pull request #1849 from harshavardhana/multipart
XL/PutObject: Handle all pending cases of DiskNotFound.
2016-06-03 11:58:04 -07:00
Harshavardhana 82fd907933 XL/PutObject: Handle all pending cases of DiskNotFound. 2016-06-03 11:40:44 -07:00
Harshavardhana f39a6f8df7 Merge pull request #1852 from minio/harshavardhana-patch-1
Fix download link
2016-06-03 01:17:54 -07:00
Harshavardhana f6013c46ea Fix download link 2016-06-03 00:05:32 -07:00
Harshavardhana da069a18c4 Merge pull request #1847 from krisis/patch-1
Created ISSUE_TEMPLATE with basic information
2016-06-03 00:01:48 -07:00
Harshavardhana 5108ba6eb1 Merge pull request #1728 from minio/rewrite-xl
XL/FS: Rewrite in new format.
2016-06-02 23:19:17 -07:00
Krishnan Parthasarathi 1213bf9fa1 Created ISSUE_TEMPLATE with basic information
Added an issued template to gather as much useful information that we may need to resolve an issue while the user has access to the relevant details.
2016-06-03 10:31:08 +05:30
Krishna Srinivas b00ac40c35 XL/PutObject: Calculate size if not provided by the client and update xl.json with the correct size. (#1844) 2016-06-02 17:09:47 -07:00
Harshavardhana fb95c1fad3 XL: Bring in some modularity into format verification and healing. (#1832) 2016-06-02 16:34:15 -07:00
Krishna Srinivas aa1d769b1e FS/Multipart: remove uploads.json on complete-multipart if no more uploadIDs are present for the object. (#1843)
Fixes #1835
2016-06-02 15:54:00 -07:00
Krishna Srinivas 611c892f8f FS/Multipart: Lock() to avoid race during PutObjectPart. (#1842)
Fixes #1839
2016-06-02 15:19:13 -07:00
Harshavardhana 67bba270a0 FS: Cleanup and Fix all multipart related operations. (#1836) 2016-06-02 12:18:56 -07:00
Harshavardhana de21126f7e XL: Re-align the code again. 2016-06-02 01:54:06 -07:00
Harshavardhana ae311aa53b XL: Cleanup, comments and all the updated functions. (#1830) 2016-06-01 16:43:31 -07:00
Krishna Srinivas 9b79760dcf XL/heal: heal missing format.json on replaced drives. (#1828)
fixes #1817
2016-06-01 16:15:56 -07:00
Bala FA 116b5607d7 server: fix to have readable timeout value (#1823) 2016-06-01 09:14:50 -07:00
Krishna Srinivas 614c770b5d List Objects version 2. (#1815)
object: List Objects v2 support
2016-05-31 22:10:55 -07:00
Harshavardhana c493ab5d0d XL: Bring in sha512 checksum support. (#1797) 2016-05-31 20:23:31 -07:00
Bala FA db2fdbf38d erasure: allocate buffer only for non-nil disk (#1811) 2016-05-31 11:55:50 -07:00
Krishna Srinivas 89f65333fb XL/Multipart: Introduce "deleted" field for uploads.json (#1810)
To future proof backend in case #1805 becomes an issue.
2016-05-31 11:54:01 -07:00
Krishna Srinivas 22511dc4c7 XL/Multipart: During list-multipart-uploads ignore errFileNotFound and errDiskNotFound errors. (#1813)
Fixes #1795
2016-05-31 11:53:28 -07:00
karthic rao 1947ae198e Adding read nad write timeout for unresponsive client connectinos (#1809) 2016-05-31 11:53:21 -07:00
Harshavardhana 2e4ab71303 Web: Update with ui changes. (#1808) 2016-05-31 02:01:02 -07:00
Harshavardhana 445dc22118 XL: Cleanup and add more comments. (#1807) 2016-05-30 16:51:59 -07:00
karthic rao ffc2b3c304 Test for ListObjectParts. (#1802) 2016-05-30 14:36:33 -07:00
Krishnan Parthasarathi 967c2b2940 Handled possible short writes to httpResponseWriter (#1804)
* XL: Handled possible short writes to httpResponseWriter

* Added tests for Range Header combinations
2016-05-30 11:27:15 -07:00
Krishna Srinivas b466f27705 Nslock fixes (#1803)
* XL/Multipart: Support parallel upload of parts by doing NS locking appropriately.

* XL/Multipart: hold lock on the multipart upload while aborting.
2016-05-30 11:26:10 -07:00
Harshavardhana a4a0ea605b XL: Fix GetObject erasure decode issues. (#1793) 2016-05-29 15:38:14 -07:00
Harshavardhana 5e8de786b3 XL: Truly use unique id's in temp directory. (#1790)
This also helps in avoiding cleaning up directories after.

Additionally this patch also fixes the problem of Range offsets.
2016-05-29 00:42:09 -07:00
Harshavardhana feb337098d XL: bring in new storage API. (#1780)
Fixes #1771
2016-05-28 16:12:51 -07:00
Krishnan Parthasarathi c87f259820 Remove parts that are missing in CompleteMultipartUpload (#1786)
* Remove parts that are missing in CompleteMultipartUpload

* Moved isUploadIDExists under proper namespace locks

* Moved code that deletes part files to a function
2016-05-28 15:15:53 -07:00
karthic rao 7278b90fe1 Adding defer to the lock (#1785) 2016-05-28 15:15:53 -07:00
Krishna Srinivas 41a5b3908b XL/ListParts: take the size from xl.json instead of backend file size as it will be different. (#1781)
Fixes #1779
2016-05-28 15:15:53 -07:00
Krishna Srinivas 3fb0b5e455 XL/Multipart: check existance upload uploadID after lock. (#1778)
Fixes #1767
2016-05-28 15:15:53 -07:00
Harshavardhana ba8bdec077 XL: ListObjects should not list when delimiter and prefix are '/'. (#1777) 2016-05-28 15:15:53 -07:00
Harshavardhana 27cc8a6529 erasure: read only dataBlocks if we have enough. (#1776)
Reconstruct with parity blocks if we don't have enough data blocks.
2016-05-28 15:15:53 -07:00
Krishnan Parthasarathi 302ec27fa2 Fixed race during parallel PutObjectPart requests (#1775)
The race is between two parallel PutObjectPart requests updating partsInfo in xl.json.
Previously, it was being updated under a RLock().
2016-05-28 15:15:53 -07:00
Krishnan Parthasarathi 5f679d9d1e Rename back multipart objects if read/write Quorum was unavailable (#1773) 2016-05-28 15:15:53 -07:00
Bala FA 51bb613fdf pkg/safe: remove temporary file on failure (#1774) 2016-05-28 15:15:53 -07:00
Harshavardhana d65101a8c8 XL: Implement strided erasure distribution. (#1772)
Strided erasure distribution uses a new randomized
block distribution for each Put operation. This
information is captured inside `xl.json` for subsequent
Get operations.
2016-05-28 15:15:53 -07:00
Krishna Srinivas 6dc8323684 FS/ListMultipart: Fix FS list-multipart to work for unit test cases. 2016-05-28 15:15:53 -07:00
Krishna Srinivas 616a257bfa XL/Multipart: isMultipartUpload() checks for presence of uploads.json on a random disk. 2016-05-28 15:15:53 -07:00
Krishna Srinivas 3487b3c095 Multipart: Disable FS tests and certain test cases for list-incomplete-uploads. 2016-05-28 15:15:53 -07:00
Karthic Rao 1f51af6f37 Listmultipart tests. 2016-05-28 15:15:53 -07:00
Krishna Srinivas b1e2b7dea2 Fix list-incomplete uploads for XL. 2016-05-28 15:15:53 -07:00
Harshavardhana 34e9ad24aa XL: Introduce new API StorageInfo. (#1770)
This is necessary for calculating the total storage
capacity from object layer. This value is also needed for
browser UI.

Buckets used to carry this information, this patch
deprecates this feature.
2016-05-28 15:15:53 -07:00
Harshavardhana b2293c2bf4 XL: Rename, cleanup and add more comments. (#1769)
- xl-v1-bucket.go - removes a whole bunch of code.
- {xl-v1,fs-v1}-metadata.go - add a lot of comments and rename functions
   appropriately.
2016-05-28 15:15:53 -07:00
Harshavardhana 553fdb9211 XL: Bring in support for object versions written during writeQuorum. (#1762)
Erasure is initialized as needed depending on the quorum and onlineDisks.
This way we can manage the quorum at the object layer.
2016-05-28 15:15:53 -07:00
Harshavardhana cae4782973 XL: explicit deleteObject is not needed after rename failure. (#1760)
Reason is renameObject() does deleteObject() upon writeQuorum
failure if not keeps the successfully renamed parts if we have
reached readQuorum.
2016-05-28 15:15:53 -07:00
Krishnan Parthasarathi 3550660163 Return error for empty parts in multipartupload complete (#1758) 2016-05-28 15:15:53 -07:00
Harshavardhana a4771265cf XL: Abortmultipart should update uploads.json properly. (#1757) 2016-05-28 15:15:53 -07:00
Harshavardhana a9e778f460 XL/fs: initObjectLayer should cleanup tmpMetaPrefix in parallel. (#1752)
Fixes #1747
2016-05-28 15:15:53 -07:00
Harshavardhana ee6645f421 XL: Add additional PartNumber variable as part of xl.json (#1750)
This is needed for verification of incoming parts and to
support variadic part uploads. Which should be sorted
properly.

Fixes #1740
2016-05-28 15:15:53 -07:00
Harshavardhana a97230dd56 XL/erasure: Reset dataBlocks to reduce the memory usage. (#1749)
Fixes #1748
2016-05-28 15:15:53 -07:00
Harshavardhana 1e393c6c5b XL: Add new metadata for checksum. (#1743) 2016-05-28 15:15:53 -07:00
Krishna Srinivas b38b9fea79 XL/erasure: fix for skipping 0 padding. (#1737)
Fixes #1736
2016-05-28 15:15:53 -07:00
Krishna Srinivas 6d84e84b3c XL/mutltipart: fix partnumber to partname association. (#1739)
Fixes #1738
2016-05-28 15:15:53 -07:00
Harshavardhana a00a5c6e7e XL: Multipart update uploads.json properly. (#1741) 2016-05-28 15:15:53 -07:00
Harshavardhana ed43d5e02b No need to delete file inside erasure code (#1732) 2016-05-28 15:15:53 -07:00
Harshavardhana 293d246f95 XL/FS: Rewrite in new format. 2016-05-28 15:15:53 -07:00
Anand Babu (AB) Periasamy 63c65b4635 filter GOPATH from stack trace (#1755) 2016-05-25 02:32:35 -07:00
Harshavardhana 64b0976e1b Remove probe and tasker. (#1733)
Fixes #1717
2016-05-24 18:43:33 -07:00
Aakash Muttineni b48b2e7f7c Part ID check (#1730)
* Added check in PutObjectPartHandler to make sure part ID does not exceed 10000. ErrInvalidMaxParts written to response if part ID exceeds the maximum value.
2016-05-24 01:52:47 -07:00
Krishnan Parthasarathi 584813e214 Used MINIO_PROFILE_DIR for saving profile information of a minio server (#1722)
To specify the directory where profiling information should be saved
  ```
    export MINIO_PROFILE_DIR=/path/to/profile/dir
  ```
By default, profiling information would be saved in a directory created
using ioutil.TempDir, which would be displayed in stdout on starting the
minio server.
2016-05-22 22:11:39 -07:00
Krishnan Parthasarathi 3a980eac1a Fix shadowing of err variable (#1718) 2016-05-21 00:43:47 -07:00
Harshavardhana 5a4b074ca0 XL: PutObject incorrectly returned after deleting multipart object. (#1715)
Fixes #1714
2016-05-20 17:27:48 -07:00
Harshavardhana f76d975304 xl: StatVol and ListVols should handle cases when disks are missing. (#1703)
Remove unnecessary code, handle cases of missing disk.
2016-05-20 16:45:53 -07:00
Harshavardhana 6015a7a3cd Add mention-bot config 2016-05-20 13:53:15 -07:00
Krishna Srinivas b83b87a7f6 XL/Incompleteuploads: list should save the tree-walk go routine to the map if eof is not reached. (#1695) 2016-05-20 12:09:21 -07:00
Krishna Srinivas 5b95f097d4 multipart: listing does not skip uploadIDmarker. (#1708)
Fixes #1706
2016-05-20 11:48:28 -07:00
Harshavardhana e4240aa58f XL/objects: Initialize format.json outside of erasure. (#1640)
Fixes #1636

New format now generates a UUID and includes it along with
the order of disks. So that UUID is the real order of disks
and on command line user is able to specify disks in any order.

This pre-dominantly solves our dilemma.
```
{
   "format" : "xl",
   "xl" : {
      "version" : "1",
      "disk": "00e4cf06-5bf5-4bb5-b885-4b2fff4a7959",
      "jbod" : [
         "00e4cf06-5bf5-4bb5-b885-4b2fff4a7959",
         ....
         "c47d2608-5067-4ed7-b1e4-fb81bdbb549f",
         "a543293e-99f1-4310-b540-1e450878e844",
         "18f97cbe-529a-456a-b6d4-0feacf64534d"
      ]
   },
   "version" : "1"
}
```
2016-05-20 02:22:22 -07:00
Andreas Linz f5dfa895a5 Exit with error code if minio server fails to start (#1704)
This commit replaces the call to `errorIf` with `fatalIf`, so that the
minio server exits with an non-zero exit status if something fails, e.g.
the port was already openend by another process.
2016-05-20 02:05:52 -07:00
Harshavardhana 50c328ff19 XL: RenameFile should rename and cleanup on writeQuorum. (#1702)
Fixes #1683
2016-05-20 01:56:46 -07:00
Anand Babu (AB) Periasamy b8405ca172 simplify profiler cleanup 2016-05-19 19:19:32 -07:00
Harshavardhana f6d9e73548 posix: Do not lowercase names, return as is. Object layer will filter them out. (#1699) 2016-05-19 18:52:55 -07:00
Harshavardhana 7ae5470395 XL: simplify isMultipartObject not need to handle unknown errors. (#1686)
Unknown errors are just logged with errorIf.
2016-05-19 17:10:33 -07:00
Harshavardhana 9fdb69563d handler: CopyObject should save metadata. (#1698)
- Content-Type
- Content-Encoding
- ETag

Fixes #1682
2016-05-19 17:10:08 -07:00
Harshavardhana 00d0558131 XL: Enable tests for content-type for Head and Get
Fixes #1674
2016-05-19 15:34:18 -07:00
Bala.FA 13e4618309 XL/fs: Return saved content-type during GetObject
Fixes #1674
2016-05-19 15:34:18 -07:00
Krishnan Parthasarathi 2f05aacbf2 Stop profiling on exit of main goroutine (#1670)
* Stop profiling on exit of main goroutine

Previously, profiling was stopped since Stop() method was called on exit of cli.BeforeFunc.
This lead to profiling to be stopped prematurely.

* Moved profiling switch statement to a separate func
2016-05-19 14:50:54 -07:00
Krishna Srinivas dc36594ef4 XL/heal: Should skip healing if CreateFile() failed on the part which needed healing. (#1693)
Fixes #1684
2016-05-19 11:32:47 -07:00
Krishna Srinivas 537568f9f9 XL/ListVols: Fix panic. Skip if slice is nil. (#1694)
Fixes #1692
2016-05-19 11:32:19 -07:00
Harshavardhana 62b4fd6964 XL: Close the reader properly. 2016-05-18 20:16:19 -07:00
Harshavardhana 7d6ed50fc2 objects: Save all the incoming metadata properly. (#1688)
For both multipart and single put operation
2016-05-18 19:54:25 -07:00
Frank af85acf388 Added ETag as an exposed header (required for multi part uploads) (#1681)
* Added ETag as an exposed header (required for multi part uploads)

* Fix formatting on adding ETag for exposed headers
2016-05-18 19:17:32 -07:00
Frank a4fef436c8 Fix formatting for adding ETag for exposed headers (#1687) 2016-05-18 19:17:17 -07:00
Harshavardhana 404364ba73 XL/fs: ListMultipartUploads should list only requested entries. (#1668)
Fixes #1665
2016-05-18 15:06:29 -07:00
Krishna Srinivas 3c1ef3fee2 Locking: move locking code from xl-erasure to xl-objects. (#1673)
Fixes #1639 #1649 #1650 #1624
2016-05-18 15:05:23 -07:00
Bala FA a0865122a7 XL/objectLayer: Save additional meta data during PutObject (#1672)
Fixes  #1602
2016-05-18 13:56:11 -07:00
Krishna Srinivas 824c8a39f1 XL/Multipart: If the part is already uploaded ignore the newly uploaded part. (#1677)
Fixes #1644
2016-05-18 13:37:28 -07:00
Krishna Srinivas 71b9341fc7 XL/Multipart: Cleanup uploads.json after abort-multipart-upload. (#1678)
Fixes #1663
2016-05-18 13:30:58 -07:00
Harshavardhana b69a97aed4 server: Set rLimit properly to the max. (#1676)
4000 is too small to handle 500 go-routines.

Fixes #1666
2016-05-18 11:34:24 -07:00
karthic rao 2da34e4668 vendor changes to pkg/profile (#1671) 2016-05-18 09:22:06 -07:00
Harshavardhana 4bc923e63b XL/fs: Optimize calling isBucketExist() (#1656)
* posix: Avoid using getAllVolumeInfo() in getVolumeDir()

This is necessary compromise to avoid significant slowness this
causes under load. The compromise is also substantial in a way
so that to avoid penalizing common cases v/s special cases.

For buckets with Caps on Unixes, we filter buckets based on the
latest anyways, so this is completely acceptable.

* XL/fs: Change the usage of verification of existance of buckets.

Optimize calling isBucketExists, it is not needed for all call
paths. isBucketExist should be called only for calls which use
temporary volume location for operations, for the rest rely on
the errors returned on their original call path.

Remove usage of filtering as well across all volume names.
2016-05-17 21:22:27 -07:00
Harshavardhana 4214da65af XL/fs: MakeVol replies should be consistent. (#1667)
Fixes #1658
Fixes #1633
2016-05-17 18:41:17 -07:00
Krishnan Parthasarathi 596fe65e84 Write pprof output files under config dir supplied (#1660)
Since config dir, supplied as command line argument, is parsed after pprof
output directory is determined, pprof output files are  written in ~/.minio/profile
directory instead of <configDir>/profile/. This change fixes this behaviour.
2016-05-17 11:44:40 -07:00
Krishna Srinivas 39865c0d2e XL/Multipart: Fix list multipart output. (#1661)
Fixes #1541
2016-05-17 11:44:32 -07:00
Harshavardhana 1760687c83 XL: Make sure to create proper temporary files for renames to succeed. (#1654)
Renames work in a special manner, temporary location files should
be created properly.

Fixes #1653
Fixes #1651
2016-05-16 15:40:57 -07:00
Harshavardhana 9472299308 logging: Log only for unhandled errors, remove all the debug logging. (#1652)
This patch brings in the removal of debug logging altogether, instead
we bring in the functionality of being able to trace the errors properly
pointing back to the origination of the problem.

To enable tracing you need to enable "MINIO_TRACE" set to "1" or "true"
environment variable which would print back traces whenever there is an
error which is unhandled or at the handler layer.

By default this tracing is turned off and only user level logging is
provided.
2016-05-16 14:31:28 -07:00
Harshavardhana 8828fd1e5c vendor: Remove unused packages. 2016-05-15 00:04:56 -07:00
Harshavardhana 7de206cb85 XL: ListVols should provide consistent view. (#1648)
Additionally get list of all volumes in parallel for aggregation
and quorum verification.

Fixes #1647
2016-05-14 23:57:57 -07:00
Harshavardhana 498ce1e9bb handler: Add a waitgroup to avoid expect100Continue crash. (#1623)
This waitgroup allows for safe blocking operation where we can cleanly
control the flow of the writes and the underlying pipe altogether.

Fixes #1553
2016-05-14 17:18:00 -07:00
Harshavardhana 5b29cefd40 api: DeleteObject should always return 204. (#1645)
Fixes #1643
2016-05-14 15:47:19 -07:00
Hori Ryota e03ebfd13b Add default cmd (#1625) 2016-05-14 02:58:50 -07:00
Harshavardhana 74c23a3544 docs: Move developer docs from top-level to its own directory. (#1642) 2016-05-14 02:47:16 -07:00
Harshavardhana 025054fb36 XL: CreateFile/ReadFile should write and read from all disks in parallel. (#1612)
* XL: CreateFile should write to all disks in parallel.

* XL: ReadFile should read from all disks in parallel.
2016-05-14 01:57:04 -07:00
Bala FA 7264cd2ab3 Fix error message when wrong set of disks are passed (#1634)
Previously when wrong set of disks are given with last minio server
run, it throws unclear error message.  This is fixed by returning
appropriate errors.

Fixes #1591
2016-05-13 23:04:10 -07:00
Harshavardhana 0e4e9c4bc1 XL: ListDir should return each List from a random disk in the set. (#1613)
Fixes #1609
2016-05-13 18:12:26 -07:00
Krishna Srinivas 8099396ff0 xl/putObject: Should take care of the situation if an object already exists at the location. (#1606)
Fixes  #1598 #1594 #1595
2016-05-13 11:52:36 -07:00
Krishna Srinivas d267696110 Validation: Reject object names with trailing "/". (#1619)
Fixes #1616
2016-05-13 11:43:06 -07:00
Bala FA 43539a0c86 posix: parseDirents() should follow symlink and get values. (#1631)
Previously parseDirents() ignores symbolic links.  This patch fixes
the issue by following the symlink using os.Stat().

Fixes #1545
2016-05-13 11:39:48 -07:00
Krishnan Parthasarathi 9e45d138cc Closed readCloser for each multipart in xl.GetObject (#1629)
* Closed readCloser for each multipart in xl.GetObject
2016-05-13 04:50:13 -07:00
karthic rao ee8605e333 Make bucket failure fix with high concurrent load (#1630) 2016-05-13 04:03:38 -07:00
karthic rao e4958f9757 Removing regexp check and adding string based check, regexp check was unnecessary here (#1627) 2016-05-13 03:33:53 -07:00
Harshavardhana b62774d32f storage/xl: Return errVolumeAccessDenied if disks cannot be accessed. (#1621)
Fixes #1614
2016-05-12 21:01:11 -07:00
koolhead17 d6e0f3ab33 added awscli commands & minor typo fix. (#1578) 2016-05-12 16:35:11 -07:00
Bala FA 3ff0a56e62 XL: Ignore errDiskNotFound in certain situations (#1610)
When a disk is removed while an operation is going on
(eg. single/multipart put object, list/multipart list objects etc),
its required to ignore errDiskNotFound error and continue the
operation.

Fixes #1552
2016-05-11 23:42:14 -07:00
Harshavardhana 50431e91a6 erasure: Handle failed disks so that we initialize properly if they are missing. (#1607)
Fixes #1592
Fixes #1579
2016-05-11 18:58:32 -07:00
Harshavardhana d4745c7d6a object: PutObjectHandler should set the md5Sum properly. (#1604)
Additionally add a test case as well for validating for us
to reply BadDigest properly.

Fixes #1603
2016-05-11 16:13:37 -07:00
Bala FA adbcafefad xl/CreateFile: handle errFileNameTooLong error properly (#1523)
When errFileNameTooLong error is returned from posix, xl.CreateFile()
treats the error specially by returning the same error immediately.

Fixes #1501
2016-05-11 12:55:02 -07:00
Harshavardhana 86e5d71519 erasure: MakeVol, DeleteVol and StatVol should hold locks. (#1597)
Since there is a good amount of overlap, each code has to lock
properly for the operation they are going to perform.

- MakeVol create vols in a routine on all disks, hold locks.
- DeleteVol delete vols in a routine on all disks, hold locks.
- StatVol stat vols in a routine on all disks, hold locks.

Fixes #1588
2016-05-11 12:54:21 -07:00
Harshavardhana 72748d2073 erasure: healVolume err should be different from shadowed version. (#1590)
Multiple go-routines updating the same shadowed variable can
cause a data race, avoid it by using its own err variable.

Fixes #1589
2016-05-11 01:36:09 -07:00
Harshavardhana 49141eb3e4 http: Remove minhttp package and use standard Golang http. (#1587)
The functionality provided by minhttp will be implemented
cleanly through our own APIs. Since we are not going to
send SIGUSR2 and manage configuration in that manner, it
doesn't make sense to use minhttp.

Fixes #1586
2016-05-10 18:03:00 -07:00
Harshavardhana d1fa1d9352 Remove binary files from previous commit. 2016-05-10 15:49:17 -07:00
karthic rao 26e2c4bf4d Replacing fastsha256 with crypto/sha256 package from golang standard package (#1584) 2016-05-10 14:20:11 -07:00
Krishna Srinivas b044336329 XL/GetObject: If the offset does not fall in the first "dataBlock" it gives incorrect data. (#1583)
Fixes #1582
2016-05-10 11:38:49 -07:00
Krishna Srinivas e99cb05516 XL/GetObject: offset should be reset to 0 after reading first part. (#1580) (#1581) 2016-05-10 10:38:12 -07:00
Krishna Srinivas 409e09c1e5 XL/Selfheal: skip reading from disk if ReadFile had returned error. (#1575) 2016-05-10 01:24:58 -07:00
Krishna Srinivas c314a98c1a XL/list: fix panic on list when a disk is down. (#1562) 2016-05-10 00:35:29 -07:00
Harshavardhana 5f0ca64346 erasure: listOnlineDisks should return errFileNotFound for errReadQuorum. (#1573)
Fixes #1571
2016-05-10 00:10:34 -07:00
Harshavardhana 0db3218d5d xl: getPartsMetadata fetch parts and decode in go-routine. (#1569)
Ref #1516
2016-05-09 23:51:05 -07:00
Harshavardhana eec41c369c posix: Return diskNotFound error rather than errVolumeNotFound (#1568)
Fixes #1559
2016-05-09 18:57:39 -07:00
Harshavardhana b66c3bf35e server: Enable server profiling as needed. (#1565) 2016-05-09 16:18:56 -07:00
Harshavardhana f733120d3d xl: CompleteMultipartUpload make sure to delete uploads.json (#1539)
Fixes #1537

Ref #1540 - for missing functionality in this patch.
2016-05-09 12:09:48 -07:00
Krishna Srinivas 6627388dc3 posix: remove dead code related to posix reserved suffixes. (#1555) 2016-05-09 11:40:30 -07:00
Harshavardhana 9d41414fb5 posix: reserved files should be filtered out at posix not object layer. (#1554) 2016-05-09 02:53:08 -07:00
Harshavardhana 722abe2d0f xl/fs: pathJoin now takes variadic inputs. (#1550)
Retains slash for the last element.

Fixes #1546
2016-05-09 00:46:54 -07:00
Krishna Srinivas 04a5b25929 Multipart: Minimum part size limit does not apply to the last part during CompleteMultipartUpload. (#1518) (#1538) 2016-05-08 23:49:49 -07:00
Harshavardhana 90ea494338 erasure: waitCloser should implement CloseWithError. (#1543)
This is needed so that the other end of the pipe receives
and error, cleanups temporary files.
2016-05-08 16:26:10 -07:00
Harshavardhana a8fdd04e62 erasure: ReadFile should honor proper offsets. (#1542)
Fixes #1535
2016-05-08 15:39:24 -07:00
Harshavardhana 76c511c9fe api: Extend S3 errors with Minio errors. (#1533)
Fixes #1530
2016-05-08 12:36:16 -07:00
Krishna Srinivas 75320f70d0 multipart: reject part upload if size is less than 5MB. (#1518) 2016-05-08 12:06:05 -07:00
Krishna Srinivas 88e1c04259 XL/ListDir: break out of loop if list on one disk is a success. (#1534) 2016-05-08 12:05:19 -07:00
Krishna Srinivas a205aca6d2 init: Cleanup .minio/tmp directories recursively. Also takes care of cleaning up of parts directory during abortMultipartUpload. (#1532) 2016-05-08 10:15:34 -07:00
Harshavardhana 3f51dd4fd4 xl: CompleteMultipartUpload should rename files in a routine. (#1527)
This solves the client timeout while renaming 9000+ parts.

Fixes #1526
2016-05-08 02:38:35 -07:00
Harshavardhana 56b7df90e1 xl/fs: ListObjectParts should set nextPartNumberMarker properly. (#1528)
For list requests on parts more than 1000, would lead to an infinite
loop.

Fixes #1522
2016-05-08 02:21:12 -07:00
Harshavardhana a56d5ef415 xl/fs: isFunctions should only return boolean. (#1525)
log the unrecognize errors.
2016-05-08 01:58:05 -07:00
Harshavardhana 937d68202d server: Deadcode removal. (#1517) 2016-05-07 21:47:33 -07:00
Harshavardhana bf563afb80 xl: DeleteObject regression over errChannel. (#1521)
DeleteObject would hang indefinitely - fix it.
2016-05-07 12:48:12 -07:00
Harshavardhana 091c1e8456 copyObject: No need to verify md5sum. (#1520)
Multipart objects are kept in non hex md5sum format.
This format doesn't comply with hex, so decoding
would fail invariably.

This is not necessary to validate and its not expected
error during a CopyObject operation.

Fixes #1519
2016-05-07 03:43:08 -07:00
Harshavardhana 751fa972f5 xl/fs: Multipart re-org introduce "uploads.json" (#1505)
Fixes #1457
2016-05-07 02:08:03 -07:00
Harshavardhana 434423de89 xl: Move format detection inside xl objects. (#1515)
Fixes #1449
2016-05-07 00:59:43 -07:00
Harshavardhana a20ccb1e83 server: Print proper endpoint, along with https if configured. (#1514)
Fixes #1492
2016-05-06 21:18:29 -07:00
Harshavardhana 0625c050e6 xl/tests: Enable server handler tests over XL. (#1512)
Fixes #1513
2016-05-06 16:47:23 -07:00
Harshavardhana 0b74f5624e xl: Fix how we deal with read offsets at erasure layer. (#1511)
Requires skipping necessary parts of dataBlocks during
decoding phase and requires us to properly skip the
entries as needed.

Thanks to Karthic for reproducing this important issue.

Fixes #1503
2016-05-06 16:25:08 -07:00
Krishna Srinivas c06b9abc15 bucket-handlers: do not unescape marker as gorilla layer would have already done it. (#1495) (#1510) 2016-05-06 16:04:46 -07:00
Krishna Srinivas a5d31d4254 XL/ListObjects: use string.TrimSuffix instead of Trim. (#1498) (#1509) 2016-05-06 13:28:55 -07:00
karthic rao 20ca65c793 Cleanup: mispell fixes 2016-05-06 12:32:44 -07:00
karthic rao 0b4bbe6d9e Adding XL Object layer validation for existing unit tests of single node (#1499)
object layer.

Adding isBucketExist check for GetObjectInfo in the XL layer.
2016-05-06 11:57:04 -07:00
Krishna Srinivas 48d3be36da XL/ListObjects: Fix ordering issue during listing if the files were uploaded as multipart uploads. (#1498) (#1506)
i.e if two files "tmp" and "tmp.1" are uploaded as multipart we would list ""tmp.1"" before ""tmp"" as "tmp.1/" < "tmp/"
2016-05-06 10:19:09 -07:00
Harshavardhana 5133ea50bd xl/fs: Make i/o operations atomic. (#1496) 2016-05-05 20:28:22 -07:00
Harshavardhana 17868ccd7f handlers: overhaul entire writErrorResponse, simplify. (#1472) 2016-05-05 20:24:29 -07:00
Harshavardhana ba5805e60a bucketPolicy: Do not use regexes, just do prefix matches. (#1497)
AWS arn supports wildcards and this is flat namespace, simple
prefix matching is fine.

Fixes #1481
Fixes #1482
2016-05-05 19:58:48 -07:00
Harshavardhana ca097de96c xl/fs: Add initObjectLayer function. (#1494)
Fixes #1493
2016-05-05 15:00:03 -07:00
Bala FA 658a595d7a xl-erasure: RenameFile should support quorum. (#1487)
Fixes #1463
2016-05-05 13:03:53 -07:00
Krishna Srinivas 247e835d7b object: move go-routine listing from posix to objectLayer. (#1491) 2016-05-05 12:51:56 -07:00
Harshavardhana 46680788f9 xl/fs: cleanup '/.minio/tmp' directory on each initialization. (#1490) 2016-05-05 01:54:43 -07:00
Harshavardhana ad40036cba posix: filepath shouldn't be used anymore use path.Join (#1486) 2016-05-05 01:39:26 -07:00
Harshavardhana 82fbe908a3 object: DeleteBucket should return proper error for BucketNotEmpty. (#1489)
Fixes #1488
2016-05-05 01:35:21 -07:00
Harshavardhana f145e1042f quick/config: No need to use Data() with type assertion. (#1480)
Since input to quick.New() is a pointer the unmarshalled value
internally already has the value, subsequent type assertions
are not needed.

Thanks to Bala for finding this behavior.

Fixes #1475
2016-05-04 20:22:15 -07:00
Rajiv Makhijani 9dccbd6478 New Dockerfile for building & running minio inside Docker (inc. autobuild support) (#1473) (#1485) 2016-05-04 17:07:19 -07:00
karthic rao 82113b747c Resource matching fix to overcome issues with regular expression based match (#1476) 2016-05-04 16:56:57 -07:00
Rajiv Makhijani a5959789d5 Make minimum file space percent a constant (#1484) 2016-05-04 15:39:06 -07:00
Harshavardhana 6988ed9257 xl/getObjectInfo: Returns back proper size, modTime and md5Sum. (#1479)
Fixes #1469
2016-05-04 15:28:58 -07:00
Rajiv Makhijani 321aefa026 Add Response for PostPolicyBucketHandler (#1477) (#1483) 2016-05-04 15:24:10 -07:00
Harshavardhana dd417e5476 fs: Handle cases of PutObject for an existing prefix. (#1478) 2016-05-04 12:18:40 -07:00
Bala FA da3a53376c server: save and compare multiple disks are used (#1474)
When server is run with multiple disks which uses xl interface where
order and count of disks are important, this patch saves such disks
configuration and compares in next run if there is a mismatch.

Fixes #1458
2016-05-04 12:18:20 -07:00
Harshavardhana e4d89d8156 xl/deleteObject: Support deleting special multipart object. (#1470)
Fixes #1452
2016-05-03 17:52:54 -07:00
Harshavardhana d0e854afb7 xl/fs: Bring in ".minio/tmp" directory support. (#1464)
All transactions happen through this directory inside ".minio/temp".
Adding this allows us to remove any temporary files which were not
committed before.

Fixes #1462
Fixes #1444
2016-05-03 16:10:24 -07:00
Harshavardhana 6f1811ee4d config: Migration should save region properly. (#1468)
Fixes #1466
2016-05-03 15:17:58 -07:00
Yurii bba5468368 minio: Replace 'bucket already exists' error by 'bucket already owned by you'. (#1465)
S3 API returns BucketAlreadyExists error when some another user has such bucket.
If user that creates the bucket already has it, s3 returns BucketAlreadyOwnedByYou.
As minio has only one user, it should behave accordingly.
Otherwise it causes failures in the applications that ignore creation of already existing bucket in the account, but fail when bucket name is used by someone else.
2016-05-03 03:19:04 -07:00
Harshavardhana 7ae40eb1bb minhttp: Remove probe usage, move to golang error. (#1459)
Fixes #1454
2016-05-03 01:07:34 -07:00
Harshavardhana ad8e27a963 xl: Rename 'xl.json' to 'file.json' (#1461)
Fixes #1460
2016-05-02 17:42:01 -07:00
Harshavardhana ac7a7cec20 bucket-policy: Delete policy should remove policy properly. (#1456) 2016-05-02 16:58:10 -07:00
Harshavardhana afd59c45dc xl/fs: Move few functions into common code. (#1453)
- PutObject()
- PutObjectPart()
- NewMultipartUpload()
- AbortMultipartUpload()

Implementations across both FS and XL object layer
share common implementation.
2016-05-02 16:57:31 -07:00
Harshavardhana 3bf3d18f1f rpc/client: Implement RenameFile properly. (#1443) 2016-05-02 03:12:18 -07:00
Harshavardhana 8102a4712a xl/metadata: Keep the json erasure tag consistent. (#1447)
Currently the on-disk json has "Erasure" we should
keep it consistent name and move to lower case instead.
2016-05-02 01:13:07 -07:00
karthic rao 2393a3a0be XL non-recursive fix (#1450) 2016-05-01 23:16:44 -07:00
Harshavardhana d006129fb5 xl/vol: Add healing and quorum support for StatVol, MakeVol.
Fixes #1437
2016-05-01 18:42:00 -07:00
Harshavardhana 7caa82f32f xl/fs: Rename minioMetaVolume to minioMetaBucket. (#1442) 2016-05-01 18:13:10 -07:00
Krishna Srinivas 286de4de2c XL - fixes mostly related to multipart listing. (#1441)
* XL/Multipart: Use json.NewDecoder to decode read stream.

* XL/Multipart: fix recursive and non-recursive listing.

* XL/Multipart: Create object part with md5sum later using RenameFile.

* XL/Multipart: ListObjectParts should list parts in order.

previously: uploadID.10.md5sum < uploadID.2.md5sum
fix       : uploadID.00010.md5sum > uploadID.00002.md5sum

* XL/Multipart: Keep the size of each part in the multipart metadata file to avoid stats on the parts.

* XL/Multipart: fix listing bug which was showing size of the multipart uploaded objects as 0.
2016-05-01 17:52:16 -07:00
Harshavardhana ba7a55c321 xl: ReedSolomon code fix small file erasure bug. (#1431)
For files less than 'dataBlocks', erasure encoding would fail
with short data due to a bug in the implementation itself.

Relax the error return, even a single byte can be properly
erasure coded without issues.

Fixes #1413
2016-05-01 15:30:40 -07:00
Harshavardhana e05aa762a9 fs: Create object part with md5sum later using RenameFile. (#1440)
Fixes #1340.
2016-05-01 14:50:30 -07:00
Krishna Srinivas 0c27d8e5b1 XL/Multipart: maintain the parts info in multipart.json after complete-multipart-upload. (#1436) 2016-05-01 01:25:48 -07:00
Harshavardhana 443ec37765 xl: Add disk usages properly for ListVols() and StatVol(). (#1435) 2016-04-30 23:34:43 -07:00
Bala FA d5df8b8b8d xl: remove unused err return in listFileVersions() (#1434) 2016-04-30 03:21:54 -07:00
Harshavardhana ac2933c799 windows: Enable erasure test for windows. (#1432)
Fixes #1363
2016-04-30 02:52:23 -07:00
Bala FA 84afec9ae0 xl: fix DeleteFile() removing meta data files without updating it (#1433)
Fixes #1428 #1427
2016-04-30 02:52:15 -07:00
Harshavardhana 27c50a70cc obj: support object names with curly braces. (#1429)
Example files like

```
/usr/share/mozilla/extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}/ubufox@ubuntu.com.xpi
Song's Son.ogg
```

Should be supported.
2016-04-29 20:19:08 -07:00
Bala FA a978975eea xl: add quorum support for DeleteFile() (#1426)
Fixes #1396
2016-04-29 18:43:18 -07:00
Harshavardhana dc45ea3946 log: Fix file logging, enable it properly. (#1424)
Fixes #1419
2016-04-29 17:54:02 -07:00
Harshavardhana 9eb56f0676 xl/healFile: Handle errors and continue (#1425)
Fixes #1354
2016-04-29 17:52:49 -07:00
Harshavardhana 10a010c1ad xl/fs: Object layer - keep common functions into single place. (#1423) 2016-04-29 17:52:17 -07:00
Harshavardhana a9935f886c vendor: update reedsolomon package with new perm improvements. (#1422) 2016-04-29 14:59:03 -07:00
Harshavardhana 4e34e03dd4 xl/fs: Split object layer into interface. (#1415) 2016-04-29 14:24:10 -07:00
nomadlogic 4d1b3d5e9a docs: FreeBSD minio source intructions (#1421)
* Modifications of documentation for using and building minio server on FreeBSD.

- update example of enabling compression to use lz4 vs gzip and provide
  explanation of benefits of lz4

- provide walkthrough of building minio server on FreeBSD with binary
  golang and gmake

* Fixing markdown syntax for code blocks so we render correctly.

* typo fix

* reword compression enablement docs for easier reading
2016-04-29 13:35:20 -07:00
Krishna Srinivas 7066ce5160 XL/Multipart: rename the parts instead of concatenating. (#1416) 2016-04-29 12:17:48 -07:00
Krishna Srinivas 39df425b2a lock: bug fixes. (#1420)
* release Lock on map before trying to Lock NS
* delete NS lock from map if no more refs.
* refactor to avoid repetition of code.
2016-04-29 11:39:20 -07:00
Harshavardhana 984903cce1 server: Add global namespace lock. (#1398)
Fixes #1393
2016-04-29 01:29:09 -07:00
karthic rao 8deddb82f4 Cleanup: Moving IsValidLocationContraint to handler utils 2016-04-28 20:01:11 -07:00
Harshavardhana a1a667ae5d xl: Change fileMetadata to xlMetadata. (#1404)
Finalized backend format

```
{
    "version": "1.0.0",
    "stat": {
        "size": 24256,
        "modTime": "2016-04-28T00:11:37.843Z"
    },
    "erasure": {
        "data": 5,
        "parity": 5,
        "blockSize": 4194304
    ],
    "minio": {
        "release": "RELEASE.2016-04-28T00-09-47Z"
    }
}
```
2016-04-28 19:27:02 -07:00
Harshavardhana 41b35cff7b xl: Fixes a bug in read quorum ListFiles() (#1412)
Fixes a bug in #1406
2016-04-28 17:32:46 -07:00
Harshavardhana eed756777b object: Allow '[' and ']' as part of object names. 2016-04-28 13:29:32 -07:00
Harshavardhana 2ac10209cc xl: ListFiles - return sorted files. (#1408)
Fixes #1407
2016-04-28 01:48:57 -07:00
Bala FA 5bd6b0b510 xl: check read quorum for ListFiles() (#1406)
Fixes #1364
2016-04-27 21:09:26 -07:00
karthic rao 1813e9c070 Cleanup - Comments and readability fixes (#1386) 2016-04-27 19:28:13 -07:00
Scott McClellan f87a19a15c Minor changes to CONTIRBUTING.md instructions (#1403) 2016-04-27 16:57:16 -07:00
Harshavardhana 5fffd558d0 xl/heal: Make healFile non-blocking for StatFile and ReadFile. (#1399)
Fixes #1355
2016-04-27 15:10:19 -07:00
Harshavardhana b51bef85e6 objectapi: ListMultipart now lists more than 1000 entries in non-recursive. (#1397)
Having keyMarker with "/" is a valid marker.

Fixes #1394
2016-04-27 13:19:48 -07:00
Krishna Srinivas d0e5470050 ListMultipart fixes (#1392)
* ListMultipart: listLeafEntries() - return earlier if a directory is found.
* ListMultipart: do listLeafEntries() only for directories.
2016-04-27 00:15:40 -07:00
Harshavardhana 90987df9b4 objectapi: Simplify ListMultipart combine recursive and non-recursive. (#1390)
Fixes #1365
2016-04-26 17:57:16 -07:00
Harshavardhana ad1abc4486 xl-v1/Cleanup: use listOnlineDisks instead of getReadableDisks. (#1389)
Remove usage of getFileVersionQuorumMap, instead use listFileVersions
to get the version list and extract higherVersion.

Fixes #1379
Fixes #1378
Fixes #1377
2016-04-26 13:03:37 -07:00
Krishna Srinivas 4333e529e6 xl/ListFiles: return as many objects as requested. (#1383)
* xl/ListFiles: return as many objects as requested and take care of eof (#1361)

* xl/ListFiles: fix review comments.

* xl/ListFiles: Add windows filepath translation.

* xl/ListFiles: Use slashSeparator instead of "/". Remove filepath.FromSlash() as golang-windows takes care of it automatically.
2016-04-26 10:35:39 -07:00
koolhead17 9685f88b84 Added FreeBSD installation steps with ZFS. (#1388) 2016-04-26 10:17:02 -07:00
Harshavardhana 5f80edf232 routers: Fix a crash while initializing network fs. (#1382)
Crash happens when 'minio server filename' a file name is
provided instead of a directory on command line argument.

```
panic: runtime error: slice bounds out of range

goroutine 1 [running]:
panic(0x5eb460, 0xc82000e0b0)
	/usr/local/opt/go/libexec/src/runtime/panic.go:464 +0x3e6
main.splitNetPath(0x7fff5fbff9bd, 0x7, 0x0, 0x0, 0x0, 0x0)
	/Users/harsha/mygo/src/github.com/minio/minio/network-fs.go:49 +0xb7
main.newNetworkFS(0x7fff5fbff9bd, 0x7, 0x0, 0x0, 0x0, 0x0)
	/Users/harsha/mygo/src/github.com/minio/minio/network-fs.go:90 +0x20a
main.configureServerHandler(0xc82024e1c8, 0x5, 0xc8200640e0, 0x1, 0x1, 0x0, 0x0)
	/Users/harsha/mygo/src/github.com/minio/minio/routers.go:43 +0x6ce
main.configureServer(0xc82024e1c8, 0x5, 0xc8200640e0, 0x1, 0x1, 0x5)
	/Users/harsha/mygo/src/github.com/minio/minio/server-main.go:86 +0x67
```
2016-04-25 18:10:40 -07:00
Harshavardhana 42254b5c4d xl: Rename blockingWriteCloser to waitCloser. (#1376) 2016-04-25 16:00:58 -07:00
Harshavardhana 00c697393a Merge pull request #1381 from minio/xl-layer
Implement XL layer
2016-04-25 15:59:30 -07:00
Harshavardhana 55032ffdf9 xl: Simplify blockingWriter and its usage. (#1373)
This removes odd races since we don't need to
track errors and avoids locking. All we need
is a Wait() and Done() waitgroup.
2016-04-25 12:47:31 -07:00
Harshavardhana 8bce699dae xl: Add logging. (#1372) 2016-04-25 12:47:31 -07:00
Harshavardhana 57f35c2bcc xl: Introduce new blocking writer to make CreateFile atomic. (#1362)
Creates a new write closer that must be released
by the read consumer. This is necessary so that
while commiting the underlying writers in erasure
coding we need to make sure we reply success only if
we have committed to disk.

This in turn also fixes plethora of bugs related to
subsequent PutObject() races with namespace locking.

This patch also enables most of the tests, other than
ListObjects paging which has some issues still.

Fixes #1358, #1360
2016-04-25 12:47:31 -07:00
Harshavardhana cab6805f09 xl: Enable a subset of tests for XL branch. (#1359) 2016-04-25 12:47:31 -07:00
Krishna Srinivas 8c85815106 xl: refactor functions to xl-v1-common.go xl-v1-utils.go. (#1357) 2016-04-25 12:47:31 -07:00
Krishna Srinivas becc814531 Xl layer selfheal quorum2
* xl/selfheal: selfheal based on read quorum on GET

* xl: getReadableDisks() also returns whether self-heal is needed so that this info can be used by ReadFile/SelfHeal/StatFile.

* xl: trigger selfheal from StatFile.
2016-04-25 12:47:31 -07:00
Harshavardhana 9bd9441107 xl: Simplify reading metadata and add a new fileMetadata type. (#1346) 2016-04-25 12:47:31 -07:00
Harshavardhana f3784d1087 xl: Handle read quorum for StatVol, ListVols 2016-04-25 12:47:31 -07:00
Harshavardhana 91588209fa obj: Object api handle all errors in common location. (#1343) 2016-04-25 12:47:31 -07:00
Krishna Srinivas 5c33b68318 xl: code refactor, cleanup ReadFile and CreateFile. 2016-04-25 12:47:31 -07:00
Bala FA 45b3d3e21f xl: add quorum support for create file 2016-04-25 12:47:31 -07:00
Harshavardhana 141a44bfbf xl: Fix ReadFile to keep the order always for reading the data back. (#1339)
Also fixes a stackoverflow bug in namespace locking.
2016-04-25 12:47:31 -07:00
Harshavardhana c7bf471c9e list/xl: Fix the way marker is handled in leafDirectory verification. 2016-04-25 12:47:31 -07:00
Krishna Srinivas c302875774 selfheal: implement self-heal. Heals the missing parts. (#1335) 2016-04-25 12:47:31 -07:00
Harshavardhana b76f3f1d62 xl: Add more fixes and cleanup.
Simplify cleanup of temporary files during createFile operations.
2016-04-25 12:47:31 -07:00
Bala FA ada0f82b9a xl: add quorum support for read file and name space locking. (#1333) 2016-04-25 12:47:31 -07:00
Harshavardhana a98a7fb1ad Implement XL layer - preliminary work. 2016-04-25 12:47:31 -07:00
Harshavardhana bf8a9702a4 tests: Fix a bug in TestObjectAPIIsUploadIDExists. (#1375)
The following code crashes when upload ID does not
exist, since we are setting err == nil when we find
err == errFileNotFound.

```
if e == nil {
   t.Fatal(e.Error())
```

Fix it.
2016-04-25 12:47:08 -07:00
karthic rao 6e372f83b4 Tests: object api multipart tests and bug fixes. 2016-04-25 10:39:28 -07:00
Harshavardhana e9fba04b36 logging: Enable logging across storage fs layer. (#1367)
Adds log.Debugf at all the layer - fixes #1074
2016-04-24 00:36:00 -07:00
Harshavardhana d63d17012d tests: Add API suite tests back for object api. (#1352) 2016-04-21 23:40:01 -07:00
Harshavardhana 444d1f8a65 miniobrowser: Vendorize to new changes in miniobrowser. 2016-04-21 20:35:48 -07:00
karthic rao 560c3bd153 Adding return statement after error response in the lastest commit to verify location constraint (#1348) 2016-04-21 20:08:08 -07:00
Harshavardhana 4cf73caf02 api: Add diskInfo as part of StatVol and ListVols. (#1349)
It is the bucket and volumes which needs to have this
value rather than the DiskInfo API itself. Eventually
this can be extended to show disk usage per
Buckets/Volumes whenever we have that functionality.

For now since buckets/volumes are thinly provisioned
this is the right approach.
2016-04-21 20:07:47 -07:00
Harshavardhana 1284ecc6f2 api: Fix verification of checkLeafDirectory. (#1347)
This fixes a problem where leaf directory has more than 1000
entries, also resulting in listing issues, leading to an infinite
loop.

Fixes #1334
2016-04-21 18:05:26 -07:00
karthic rao cb1116725b api: verify Location constraint for make bucket. (#1342) 2016-04-20 17:35:38 -07:00
koolhead17 c3d0a3d51e Update README.md
Our community contributor bought this to our attention so we have to add region as well in s3cmd config file.
2016-04-19 10:22:40 -07:00
Harshavardhana e0f8fed011 object: handle Error responses and handle errDiskFull. (#1331) 2016-04-19 02:42:10 -07:00
Harshavardhana 6bc17a3aea server: Attempt to increase max open files. (#1328) 2016-04-18 22:05:32 -07:00
Dave Henderson ff9a6b00cc isContainerized should look for /.dockerenv not /.dockerinit
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
2016-04-18 19:26:56 -07:00
matt robinson af907a35a9 add environment var to explicitly indicate containerized and allow running as root (#1327) 2016-04-18 16:30:11 -07:00
Harshavardhana b47d722d8e fs: Fix filtering out valid paths from previous #1321 fix (#1323)
Fixes #1324
2016-04-17 12:00:23 -07:00
Harshavardhana 33633fd15d fs: Filter out valid paths and volnames. (#1321) 2016-04-16 19:33:29 -07:00
Harshavardhana b2ec7da9f9 Merge pull request #1314 from minio/split-fs
Split fs code into storage layer and object layer.
2016-04-16 18:09:31 -07:00
Harshavardhana be002ac01e fs/object: Fix issues from review comments. 2016-04-16 17:57:14 -07:00
Krishna Srinivas 149c6ca094 listMultipart: bugfixes. (#1318) 2016-04-16 16:25:53 -07:00
Harshavardhana 8457af5708 fs: Add proper volume and path validation. 2016-04-16 16:25:53 -07:00
Krishna Srinivas caa35f68fa listMultipart: implement support for marker. (#1313) 2016-04-16 16:25:53 -07:00
Harshavardhana 30b0b4deba storage/server/client: Enable storage server, enable client storage. 2016-04-16 16:25:53 -07:00
Krishna Srinivas 01a439f95b refactor: add multipart code to the object layer. 2016-04-16 16:25:53 -07:00
Krishna Srinivas 3c48537f20 refactor: refactor code to separate fs into object-layer and fs layer. (#1305) 2016-04-16 16:25:53 -07:00
karthic rao 188bb92d8a bucket-policy parset tests, and bug fixes (#1317) 2016-04-15 18:23:19 -07:00
Harshavardhana 6b3fc03701 docker: Update docker document 2016-04-15 17:40:56 -07:00
Harshavardhana 8112291d43 Add FreeBSD binary link and make a release 2016-04-14 17:22:47 -07:00
Harshavardhana 93666827f4 release: Add freebsd/amd64 build and remove zip, tgz. (#1316) 2016-04-13 23:34:55 -07:00
GarimaKapoor ac30bef72a Revised Docker.md (#1311) 2016-04-11 23:59:12 -07:00
Bala FA bea6f33b08 backend/fs: remove timer channel from scanMultipartDir() (#1310)
Previously scanMultipartDir() returns object info channel and timer
channel where timer channel is used to check whether object info
channel is alive or not.  This causes a race condition that timeout
may occur while object info channel in use.

This patch fixes the issue by removing timer channel and uses object
info channel directly where each object info has End bool field
indicates whether received object info is end or not.
2016-04-11 19:00:08 -07:00
Michael Werle 9fb1c79456 Improved Docker examples (#1308)
- Fixed a bug in the persistent docker command ("server" in place of "export")
- Added example of how to set consistent keys with ephemeral data, particularly useful for testing.
2016-04-11 12:33:34 -07:00
Harshavardhana 6b5699b15f config: console logging should be enabled by default. (#1307) 2016-04-09 14:19:59 -07:00
Harshavardhana 33cd910d3a backend/fs: More cleanup and start using checkBuckeArg. (#1306)
backend/fs: More cleanup and start using checkBucketArg.
2016-04-08 17:13:16 -07:00
Bala FA 6af761c86c enhance multipart functions to use fsDirent (#1304)
* backend/fs: scanMulitpartDir returns directories only for recursive listing

* backend/fs: enhance multipart functions to use fsDirent
2016-04-08 11:46:03 -07:00
Matthew Buckett bedd867c0b The fs command doesn't work any more, using server
Also there's no /export/data folder which stops minio starting so using /export instead.
2016-04-08 08:53:55 -07:00
Harshavardhana 37330bda98 Merge pull request #1302 from harshavardhana/web-api
web: Change /rpc to /webrpc
2016-04-08 02:04:06 -07:00
Harshavardhana 8603185f2f browser: Add new ui-assets.go 2016-04-08 01:47:30 -07:00
Harshavardhana fbd02d530d web: Change /rpc to /webrpc 2016-04-08 01:46:46 -07:00
Harshavardhana bad2f2afbb storage: Add storage interface. 2016-04-07 19:33:06 -07:00
Harshavardhana b182e94acc signature: Handle presigned payload if set.
Validate payload with incoming content.



Fixes #1288
2016-04-07 03:04:18 -07:00
Anand Babu (AB) Periasamy 4e6c4da518 Update README.md 2016-04-06 20:54:06 -07:00
Donald Guy e8cd1aad8d accessPolicy: prevent backdoor ListBucket via brute-force 404s, per docs + small fixes
* accessPolicy: copy object should require PutObject

* accessPolicy: cite mpu perms doc only for relevant operations

* accessPolicy: prevent backdoor ListBucket via brute-force 404s, per docs
2016-04-06 18:31:40 -07:00
Donald Guy 8b4a5f07b4 accessPolicy: allow anonymous HEAD for Getable objects
* accessPolicy: allow anonymous HEAD for Getable objects

* accessPolicy: allow anonymous HEAD of Listable Buckets
2016-04-06 16:40:54 -07:00
Harshavardhana ff4e04d942 atomic/fs: use safe package for atomic writes, even in multipart. 2016-04-06 16:05:30 -07:00
Bala FA dfba4ff397 doc: add multipart documentation about staging files 2016-04-05 19:56:19 -07:00
Harshavardhana 06e3171076 Merge pull request #1290 from balamurugana/devel
Refactor multipart upload
2016-04-05 18:21:46 -07:00
Bala FA 2b3a118636 Merge pull request #1 from harshavardhana/devel
Fix list objects test and remove all the old unnecessary files.
2016-04-06 06:43:43 +05:30
Harshavardhana 8986a6802a Fix ListMultipartUploads 'mc ls -I' now works properly. 2016-04-05 16:39:02 -07:00
Harshavardhana 3fcc60de91 Move the files and rename some functions.
- Rename dir.go as 'fs-multipart-dir.go'
- Move the push/pop to fs-multipart.go and rename them as save/lookup.
- Rename objectInfo instances in fs-multipart as multipartObjInfo.
2016-04-05 12:26:19 -07:00
Harshavardhana 9632c94e7a Fix list objects test and remove all the old unnecessary files.
- Fix tests for new changes.
- Change Golang err as 'e' for the time being, before we bring in
  probe removal change.
- Remove old structs and temporary files.
2016-04-05 12:07:19 -07:00
Bala.FA 083e4e9479 backend/fs: Refactor multipart upload
This patch modifies multipart upload related functions as below

* New multipart upload call creates file
  EXPORT_DIR/.minio/BUCKET/PATH/TO/OBJECT/UPLOAD_ID.uploadid

* Put object part call creates file
  EXPORT_DIR/.minio/BUCKET/PATH/TO/OBJECT/UPLOAD_ID.PART_NUMBER.MD5SUM_STRING

* Abort multipart call removes all files matching
  EXPORT_DIR/.minio/BUCKET/PATH/TO/OBJECT/UPLOAD_ID.*

* Complete multipart call does
  1. creates a staging file
  EXPORT_DIR/.minio/BUCKET/PATH/TO/OBJECT/UPLOAD_ID.complete.TEMP_NAME
  then renames to
  EXPORT_DIR/.minio/BUCKET/PATH/TO/OBJECT/UPLOAD_ID.complete
  2. rename staging file
  EXPORT_DIR/.minio/BUCKET/PATH/TO/OBJECT/UPLOAD_ID.complete
  to EXPORT_DIR/BUCKET/PATH/TO/OBJECT
2016-04-05 22:22:29 +05:30
Harshavardhana c69fdf0cf2 listObjects: Cleanup and naming conventions.
- Marker should be escaped outside in handlers.

- Delimiter should be handled outside in handlers.

- Add missing comments and change the function names.

- Handle case of 'maxKeys' when its set to '0', its a valid

  case and should be treated as such.
2016-04-04 19:55:07 -07:00
Krishna Srinivas 85ab1df5a8 listObjects: do not do stat during readdir()
* listObjects: improve response time by not doing stat during readDir() operation.

* listObjects: Add windows support.

* listObjects: Readdir() in batches to conserve memory. Add solaris build.

* listObjects: cleanup code.
2016-04-04 17:27:55 -07:00
Harshavardhana 7623e0f8e8 docker: Fix bug in start.sh arguments. 2016-04-04 11:30:18 -07:00
Anand Babu (AB) Periasamy 9843aa1f7a Merge pull request #1284 from harshavardhana/list
objectAPI: Fix object API interface, remove unnecessary structs.
2016-04-03 21:13:48 -07:00
Harshavardhana 0479d4976b objectAPI: Fix object API interface, remove unnecessary structs.
ObjectAPI changes.
```
ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, *probe.Error)
ListMultipartUploads(bucket, objectPrefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, *probe.Error)
ListObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (ListPartsInfo, *probe.Error)
CompleteMultipartUpload(bucket string, object string, uploadID string, parts []completePart) (ObjectInfo, *probe.Error)
```
2016-04-03 15:25:01 -07:00
Anand Babu (AB) Periasamy 12515eabe2 Merge pull request #1280 from harshavardhana/region
signature: No need to validate region for getBucketLocation and listBuckets
2016-04-03 01:01:05 -07:00
Harshavardhana a6a4e7e297 signature: No need to validate region for getBucketLocation and listBuckets.
This type of check is added for making sure that we can support
custom regions.

ListBuckets and GetBucketLocation are always "us-east-1" rest
should look for the configured region.

Fixes #1278
2016-04-02 18:42:32 -07:00
Anand Babu (AB) Periasamy 2c793a2ea7 Merge pull request #1282 from harshavardhana/remove-old-code
cleanup: Remove old donut/xl code and erasure implementation.
2016-04-02 18:14:21 -07:00
Anand Babu (AB) Periasamy 2bb262cc56 Merge pull request #1279 from harshavardhana/backend
config: Migrate to the new version. Remove backend details.
2016-04-02 17:59:21 -07:00
Harshavardhana 379e0abf03 cleanup: Remove old donut/xl code and erasure implementation.
This is a change to bring in 'klauspost/reedsolomon' library
in #1270 patch.
2016-04-02 17:30:35 -07:00
Harshavardhana 484ba91b08 config: Migrate to the new version. Remove backend details.
Migrate to new config format v4.
```
{
	"version": "4",
	"credential": {
		"accessKey": "WLGDGYAQYIGI833EV05A",
		"secretKey": "BYvgJM101sHngl2uzjXS/OBF/aMxAN06JrJ3qJlF"
	},
	"region": "us-east-1",
	"logger": {
		"console": {
			"enable": true,
			"level": "fatal"
		},
		"file": {
			"enable": false,
			"fileName": "",
			"level": "error"
		},
		"syslog": {
			"enable": false,
			"address": "",
			"level": "debug"
		}
	}
}
```

This patch also updates [minio cli spec](./minio.md)
2016-04-02 17:29:31 -07:00
Harshavardhana 6037fe66e9 minio: Simplify for gosimple tool complaints. 2016-04-02 17:28:54 -07:00
Harshavardhana ba3a5805c1 vendorize: Add updated ui-assets.go. 2016-04-02 17:27:36 -07:00
Anand Babu (AB) Periasamy 33830bfcae Merge pull request #1273 from harshavardhana/fs-linux
fs: Break fs package to top-level and introduce ObjectAPI interface.
2016-04-01 17:03:23 -07:00
Harshavardhana efc80343e3 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
}
```
2016-04-01 15:58:39 -07:00
Harshavardhana 272c5165aa Merge pull request #1272 from krishnasrinivas/get-auth
GetAuth implementation. min/max check for accessKey and secretKey.
2016-04-01 09:38:58 -07:00
Krishna Srinivas e318925f62 credentials: min/max length check for credentials. 2016-04-01 21:52:39 +05:30
Harshavardhana 2395c42fb5 Merge pull request #1277 from krishnasrinivas/remove-minio-go2
UI-handler: remove minio-go dependancy.
2016-04-01 08:56:16 -07:00
Harshavardhana 9333dc3294 Merge pull request #1204 from hackintoshrao/test-bucket
Test: Changes to TestPutBucket to catch the race
2016-04-01 08:53:34 -07:00
Karthic Rao 30fc970eab Changes to TestPutBucket to catch the race 2016-04-01 15:21:16 +05:30
Krishna Srinivas 331890c4c8 UI-handler: remove minio-go dependancy. 2016-04-01 13:56:32 +05:30
Anand Babu (AB) Periasamy ae5c65d3c6 Merge pull request #1275 from harshavardhana/signature
error: Signature errors should be returned with APIErrorCode.
2016-03-31 23:37:38 -07:00
Harshavardhana 02ad48466d error: Signature errors should be returned with APIErrorCode.
The reasoning is that we can reply back with wide range of
S3 error responses, which would provide more richer context
to S3 client.

Fixes #1267
2016-03-31 23:28:40 -07:00
Harshavardhana a84c466a40 Merge pull request #1251 from harshavardhana/release-fixes
release: gz doesn't preserve permissions use tar.gz
2016-03-30 14:34:19 -07:00
Harshavardhana 956142be37 Merge pull request #1271 from krishnasrinivas/set-auth2
UI: implement SetAuth/GenerateAuth handlers for changing credentials.
2016-03-29 09:38:18 -07:00
Krishna Srinivas 5201905ad0 UI: implement SetAuth/GenerateAuth handlers for changing credentials. 2016-03-29 21:08:36 +05:30
Anand Babu (AB) Periasamy 186998ad99 Merge pull request #1266 from harshavardhana/cleanup
routers: Move API and Web routers into their own files.
2016-03-27 14:25:02 -07:00
Harshavardhana aa8c9bad54 routers: Move API and Web routers into their own files.
This is done to ensure we have a clean way to add new routers such as

  - diskRouter
  - configRouter
  - lockRouter
2016-03-27 13:28:36 -07:00
Harshavardhana 59ee5a547c release: gz doesn't preserve permissions use tar.gz
And fix various other issues with release script.
2016-03-26 23:44:32 -07:00
Harshavardhana 1502e2f29f Merge pull request #1265 from vadmeste/add_fbsd_support
Add simple FreeBSD support, make the minio project compilable
2016-03-26 22:28:07 -07:00
Anand Babu (AB) Periasamy 90a46faf31 Merge pull request #1228 from harshavardhana/signature-cleanup
signature: Move signature outside, use a layered approach for signature verification
2016-03-26 15:46:52 -07:00
Harshavardhana 9dca46e156 signature: Use a layered approach for signature verification.
Signature calculation has now moved out from being a package to
top-level as a layered mechanism.

In case of payload calculation with body, go-routines are initiated
to simultaneously write and calculate shasum. Errors are sent
over the writer so that the lower layer removes the temporary files
properly.
2016-03-26 15:21:05 -07:00
Anis Elleuch 663f24064b Add simple FreeBSD support, make the minio project compilable 2016-03-26 22:39:34 +01:00
Harshavardhana 1b0bc814c4 docker: Fix docker Makefile. 2016-03-24 22:53:13 -07:00
Anand Babu (AB) Periasamy cd5992c6db Merge pull request #1262 from harshavardhana/docker-file
docker: Fix docker command entry.
2016-03-24 22:44:09 -07:00
Harshavardhana 1ef5ab3c28 docker: Fix docker command entry. 2016-03-24 20:38:36 -07:00
Anand Babu (AB) Periasamy 5bd47861d6 Merge pull request #1261 from harshavardhana/update-message
minio: Server upon start displays a message if update is available.
2016-03-24 20:25:36 -07:00
Harshavardhana 3538c9f598 minio: Server upon start displays a message if update is available.
This code also handles to turn itself off when network is not
available and if request fails. Also prints only when the update
is available.
2016-03-24 20:03:51 -07:00
Anand Babu (AB) Periasamy 24ae5467c8 Merge pull request #1260 from harshavardhana/minio
server: Print a message if not backends are configured.
2016-03-24 17:23:03 -07:00
Harshavardhana 36267eb6e2 server: Print a message if not backends are configured. 2016-03-24 10:47:54 -07:00
Harshavardhana 8255590b3c config/main: set the missing value. 2016-03-24 10:41:42 -07:00
Anand Babu (AB) Periasamy 4f6cf5a6b2 Merge pull request #1123 from harshavardhana/rewrite-v1
config/main: Re-write config files - add to new config v3
2016-03-24 08:52:35 -07:00
Harshavardhana aaf97ea02c config/main: Re-write config files - add to new config v3
- New config format.

```
{
	"version": "3",
	"address": ":9000",
    "backend": {
          "type": "fs",
          "disk": "/path"
    },
	"credential": {
		"accessKey": "WLGDGYAQYIGI833EV05A",
		"secretKey": "BYvgJM101sHngl2uzjXS/OBF/aMxAN06JrJ3qJlF"
	},
	"region": "us-east-1",
	"logger": {
		"file": {
			"enable": false,
			"fileName": "",
			"level": "error"
		},
		"syslog": {
			"enable": false,
			"address": "",
			"level": "debug"
		},
		"console": {
			"enable": true,
			"level": "fatal"
		}
	}
}
```

New command lines in lieu of supporting XL.

Minio initialize filesystem backend.
~~~
$ minio init fs <path>
~~~

Minio initialize XL backend.
~~~
$ minio init xl <url1>...<url16>
~~~

For 'fs' backend it starts the server.
~~~
$ minio server
~~~

For 'xl' backend it waits for servers to join.
~~~
$ minio server
... [PROGRESS BAR] of servers connecting
~~~

Now on other servers execute 'join' and they connect.
~~~
....
minio join <url1> -- from <url2> && minio server
minio join <url1> -- from <url3> && minio server
...
...
minio join <url1> -- from <url16> && minio server
~~~
2016-03-23 19:16:09 -07:00
Harshavardhana 85e50f2bb9 Merge pull request #1258 from hackintoshrao/list-object-prefix-response
api: GetObjectInfo fix
2016-03-23 11:09:22 -07:00
Karthic Rao c8570edaab Issue of 'mc ls' when prefix is a directory fixed, tests added for GetObjectInfo 2016-03-23 19:42:04 +05:30
Anand Babu (AB) Periasamy f1161d830f Merge pull request #1257 from harshavardhana/content-md5
routers: Fix order of PostPolicyHandlers and headers.
2016-03-22 20:49:21 -07:00
Anand Babu (AB) Periasamy 4aa098ede9 Merge pull request #1256 from harshavardhana/resources
bucketpolicy: checkBucketPolicy should keep resources in map.
2016-03-22 20:47:48 -07:00
Harshavardhana 76bda0d8f1 routers: Fix order of PostPolicyHandlers and headers. 2016-03-22 17:54:44 -07:00
Harshavardhana 996d2e2a10 bucketpolicy: checkBucketPolicy should keep resources in map.
This is done to make sure to avoid appending duplicates for
resources for each actions.
2016-03-22 17:04:39 -07:00
Harshavardhana 2edf32adfa Merge pull request #1253 from koolhead17/patch-5
Update README.md
2016-03-22 17:01:59 -07:00
Harshavardhana e3a3283883 Merge pull request #1255 from hackintoshrao/list-object-prefix-response
api: ListObject - Changing to empty response when prefixDir doesn't exist
2016-03-22 17:01:48 -07:00
Karthic Rao 7be79b507b Changing to empty response when prefixDir doesn't exist 2016-03-23 04:46:10 +05:30
Harshavardhana 600a932acb Merge pull request #1254 from hackintoshrao/formatting-fix
Formating: Formating issues fixed
2016-03-22 04:34:57 -07:00
Karthic Rao ff41c050d5 Formatting issues fixed. 2016-03-22 15:55:29 +05:30
koolhead17 7f993bb5e6 Update README.md
fixed missing spelling,
2016-03-22 13:37:04 +05:30
Harshavardhana 7a97622fed Merge pull request #1252 from koolhead17/patch-4
docs: Add more s3cmd commands
2016-03-22 01:04:35 -07:00
koolhead17 da691dc100 Update README.md
Added more s3cmd commands associated & known to work well with Minio server
2016-03-22 12:39:44 +05:30
Harshavardhana e2c515b334 Merge pull request #1245 from hackintoshrao/fs-bucket-tests
api: ListObject - tests, benchmark, optimization
2016-03-21 21:51:48 -07:00
Karthic Rao b55922effe Fix for Istruncated set to true under certain conditions.
Optimizing List Objects by using binary sort to discard entries in cases
where prefix or marker is set.

Adding test coverage to ListObjects.

Adding benchmark to ListObjects.
2016-03-22 10:09:16 +05:30
Anand Babu (AB) Periasamy 407316c631 Merge pull request #1249 from harshavardhana/remove-deadcode
main: Remove all the dead/unused code.
2016-03-21 07:55:07 -07:00
Harshavardhana 902aa05021 main: Remove all the dead/unused code.
This patch removes some dead and unused code.
2016-03-21 01:12:29 -07:00
Anand Babu (AB) Periasamy d95aac4b36 Merge pull request #1246 from harshavardhana/list-response
api: ListMultipartUploads and ListParts responded more entries.
2016-03-20 00:07:06 -07:00
Harshavardhana fc72a0362f api: ListMultipartUploads and ListParts responded more entries.
Issue is empty entries were added since allocating an array was
followed by an append. Keep the index and copy the right entries
precisely.

Fixes an issue reported at - https://github.com/minio/mc/issues/1642
2016-03-19 23:52:40 -07:00
Anand Babu (AB) Periasamy 1900d16d22 Merge pull request #1244 from harshavardhana/zip-compress
buildscripts: compress release binaries.
2016-03-18 23:37:52 -07:00
Harshavardhana 41cba3a457 buildscripts: compress release binaries.
Fix update command as well to show compressed files in updates.
2016-03-18 23:30:54 -07:00
Harshavardhana a90faf5996 Merge pull request #1216 from sreeram-boyapati/choose_arch
buildscripts: Enable user to choose an arch to build
2016-03-18 12:13:03 -07:00
Harshavardhana e7e2fae156 Merge pull request #1238 from hackintoshrao/maxkeys-zero-fix
[GH-1237] api: Handling maxKeys=0 case with a empty response
2016-03-18 02:57:40 -07:00
Karthic Rao 99af0444b7 Handling maxKeys=0 case with a empty response 2016-03-18 15:16:30 +05:30
Harshavardhana 2357e00317 Fix s3cmd config 2016-03-18 02:07:17 -07:00
Anand Babu (AB) Periasamy 27c75d8be4 Merge pull request #1241 from harshavardhana/print
[GH-1240] main: Print keys after init and full server initialization.
2016-03-17 23:42:46 -07:00
Harshavardhana 72d364cbf2 [GH-1240] main: Print keys after init and full server initialization.
Setting MINIO_ACCESS_KEY and MINIO_SECRET_KEY re-writes the values
in config properly, but the init message is not updated.

Fix it by delay printing keys until everything is properly
initialized.
2016-03-17 20:01:52 -07:00
Harshavardhana a729d02c31 Merge pull request #1227 from krishnasrinivas/docker-md
docker: simplify docker run instructions.
2016-03-16 18:38:37 -07:00
Harshavardhana c80c06680d Merge pull request #1235 from krishnasrinivas/handle-eaddrinuse
startup: do not fail for non-EADDRINUSE errors. Fixes #1234
2016-03-16 18:38:27 -07:00
Harshavardhana facd5a9ffd Merge pull request #1239 from awwalker/verify-headers
api: CopyObject - verify the additional headers before starting to write.
2016-03-16 18:37:56 -07:00
awwalker 34f2c5bcdf verify before writing
merge

verify headers before writing
2016-03-16 18:03:23 -07:00
Krishna Srinivas fd943c704d docker: simplify docker run instructions. 2016-03-17 01:39:49 +05:30
Krishna Srinivas e5b411caf4 startup: do not fail for non-EADDRINUSE errors. Fixes #1234 2016-03-17 01:36:08 +05:30
Anand Babu (AB) Periasamy c784707ed8 Merge pull request #1233 from harshavardhana/bucket-policy
bucketpolicy: Improve bucket policy validation, avoid nested rules.
2016-03-15 18:57:02 -07:00
Harshavardhana 88714e7c8e bucketpolicy: Improve bucket policy validation, avoid nested rules.
Bucket policy validation is more stricter now, to avoid nested
rules. The reason to do this is keep the rules simpler and more
meaningful avoiding conflicts.

This patch implements stricter checks.

Example policy to be generally avoided.
```
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Action": [
				"s3:GetObject",
				"s3:DeleteObject"
			],
			"Effect": "Allow",
			"Principal": {
				"AWS": [
					"*"
				]
			},
			"Resource": [
				"arn:aws:s3:::jarjarbing/*"
			]
		},
		{
			"Action": [
				"s3:GetObject",
				"s3:DeleteObject"
			],
			"Effect": "Deny",
			"Principal": {
				"AWS": [
					"*"
				]
			},
			"Resource": [
				"arn:aws:s3:::jarjarbing/restic/key/*"
			]
		}
	]
}
```
2016-03-15 17:50:23 -07:00
Harshavardhana 2e3e164f16 Update Docker.md 2016-03-15 10:01:58 -07:00
Harshavardhana 15b5f4d7a3 Update Caddy.md 2016-03-14 20:51:16 -07:00
Harshavardhana 83f9a50137 Merge pull request #1229 from brendanashworth/improve-fs-getobject
pkg/fs: optimize GetObject, add benchmark
2016-03-13 14:03:45 -07:00
Brendan Ashworth 583e4ecff6 pkg/fs: optimize GetObject syscalls for common case
In the common case, GetObject is called on a bucket that exists and an
object that exists and is not a directory. It should be optimized for
this case, thus error-related syscalls are pushed back until they are
necessary.

This should not impact performance negatively in the uncommon case, and
instead drops two otherwise unnecessary os.Stat's in the common case.

The race conditions around a proper error being returned were present
beforehand.

It also renames 'err' to 'e'.
2016-03-13 13:56:33 -07:00
Brendan Ashworth b2257682e4 pkg/fs: add benchmark for GetObject
This commit adds a benchmark for GetObject. It doesn't leverage the I/O
as much because it uses short text for data, just 58 chars.
2016-03-13 11:13:06 -07:00
Harshavardhana 09789de586 Merge branch 'awwalker-new-copy-headers' 2016-03-12 10:56:11 -08:00
awwalker 9a5e3299fc api/object: Add CopyObject to support match/modified copy headers
Adds support for the following request headers:

- x-amz-copy-source-if-match
- x-amz-copy-source-if-none-match
- x-amz-copy-source-if-unmodified-since
- x-amz-copy-source-if-modified-since

Fixes #1176
2016-03-12 10:54:23 -08:00
Harshavardhana 2c09df24ee Merge pull request #1225 from hackintoshrao/fs-bucket-tests
tests: Add tests for ListBuckets
2016-03-12 01:26:56 -08:00
Karthic Rao 53a76439a2 test for GetBucketInfo 2016-03-12 14:31:30 +05:30
Anand Babu (AB) Periasamy 6b0af08885 Merge pull request #1224 from harshavardhana/simplify
cleanup: Remove unecessary packages and tests. Simplify.
2016-03-11 22:58:43 -08:00
Harshavardhana 5282a79eda cleanup: Remove unecessary packages and tests. Simplify. 2016-03-11 19:53:55 -08:00
Anand Babu (AB) Periasamy 3d8b9afa8f Merge pull request #1222 from harshavardhana/cleanup-fix
cleanup: Rename ObjectMetadata as ObjectInfo.
2016-03-11 17:14:22 -08:00
Harshavardhana 52751d81cb cleanup: Rename ObjectMetadata as ObjectInfo.
Fixes #1215
2016-03-11 16:58:08 -08:00
Harshavardhana c81f4b0228 Merge pull request #1218 from hackintoshrao/better-fs-util-test
Test: Better structuring of fs-utils test
2016-03-11 06:54:43 -08:00
Karthic Rao ec8c1d4ef6 Better structuring of fs-utils test 2016-03-11 19:19:47 +05:30
Sreeram Boyapati 62bd44f873 buildscripts: Enable user to choose an arch to build
- Building minio for all architectures takes a lot of time.
    Choose the one user needs
2016-03-11 10:17:56 +05:30
334 changed files with 21196 additions and 46649 deletions
+13
View File
@@ -0,0 +1,13 @@
## Expected behaviour
## Actual behaviour
## Steps to reproduce the behaviour
## Minio version
- (paste output of `minio version`)
## System information
+2 -1
View File
@@ -10,7 +10,8 @@ site/
/Minio.iml
**/access.log
build
isa-l
vendor/**/*.js
vendor/**/*.json
release
.DS_Store
*.syso
+4
View File
@@ -0,0 +1,4 @@
{
"numFilesToCheck": 10,
"requiredOrgs": ["minio"]
}
-10
View File
@@ -6,16 +6,6 @@ os:
- linux
- osx
before_install:
- git clone https://github.com/yasm/yasm
- cd yasm
- git checkout v1.3.0
- "./autogen.sh"
- "./configure"
- make
- export PATH=$PATH:`pwd`
- cd ..
osx_image: xcode7.2
env:
+2 -2
View File
@@ -3,11 +3,11 @@
If you do not have a working Golang environment setup please follow [Golang Installation Guide](./INSTALLGO.md).
### Setup your Minio Github Repository
Fork [Minio upstream](https://github.com/minio/minio/fork) source repository to your own personal repository. Copy the URL and pass it to ``go get`` command. Go uses git to clone a copy into your project workspace folder.
Fork [Minio upstream](https://github.com/minio/minio/fork) source repository to your own personal repository. Copy the URL for minio from your personal github repo (you will need it for the `git clone` command below).
```sh
$ mkdir -p $GOPATH/src/github.com/minio
$ cd $GOPATH/src/github.com/minio
$ git clone https://github.com/$USER_ID/minio
$ git clone <paste saved URL for personal forked minio repo>
$ cd minio
```
+8 -4
View File
@@ -1,23 +1,27 @@
### Proxy using Caddy.
## Setting up Proxy using Caddy.
Please download [Caddy Server](https://caddyserver.com/download)
Create a caddy configuration file as below, change the ip addresses according to your local
minio and DNS configuration.
```bash
$ ./minio --address localhost:9000 server <your_export_dir>
```
```bash
your.public.com {
proxy / 10.0.1.3:9000 {
proxy / localhost:9000 {
proxy_header Host {host}
proxy_header X-Real-IP {remote}
proxy_header X-Forwarded-Proto {scheme}
}
tls off
}
```
```bash
$ ./caddy
Activating privacy features... done.
your.public.com
your.public.com:443
your.public.com:80
```
+18 -28
View File
@@ -1,38 +1,28 @@
## Running Minio in Docker.
### Installing Docker.
### Run Minio docker image
## Test Minio Docker Container
Minio generates new access and secret keys each time you run this command. Container state is lost after you end this session. This mode is only intended for testing purpose.
```bash
sudo apt-get install Docker.io
docker run -p 9000:9000 minio/minio /export
```
### Generating `minio configs` for the first time.
## Run Minio Docker Container
Minio container requires a persistent volume to store configuration and application data. Following command maps local persistent directories from the host OS to virtual config `~/.minio` and export `/export` directories.
```bash
docker run -p 9000 minio/minio:latest
docker run -p 9000:9000 --name minio1 \
-v /mnt/export/minio1:/export \
-v /mnt/config/minio1:/root/.minio \
minio/minio /export
```
### Persist `minio configs`.
## Custom Access and Secret Keys
To override Minio's auto-generated keys, you may pass secret and access keys explicitly as environment variables. Minio server also allows regular strings as access and secret keys.
```bash
docker commit <running_minio_container_id> minio/my-minio
docker stop <running_minio_container_id>
```
### Create a data volume container.
```bash
docker create -v /export --name minio-export minio/my-minio /bin/true
```
You can also map ``.minio`` directory containing authentication information.
```bash
docker create -v /export --name minio-export -v /.minio --name minio-config minio/my-minio /bin/true
```
You can then use the `--volumes-from` flag to mount the `/export` and ``/.minio`` volume in another container.
```bash
docker run -p 9000 --volumes-from minio-export --volumes-from minio-config --name minio-server1 minio/my-minio
docker run -p 9000:9000 --name minio1 \
-e "MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE" \
-e "MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
-v /mnt/export/minio1:/export \
-v /mnt/config/minio1:/root/.minio \
minio/minio /export
```
+14 -7
View File
@@ -1,8 +1,15 @@
# use "make dockerimage" to build
FROM alpine:3.3
RUN apk add --no-cache ca-certificates
ADD minio.dockerimage /minio
ADD export /export
FROM golang:1.6
RUN mkdir -p /go/src/app
WORKDIR /go/src/app
COPY . /go/src/app
RUN go-wrapper download
RUN go-wrapper install
ENV ALLOW_CONTAINER_ROOT=1
RUN mkdir -p /export/docker && cp /go/src/app/Docker.md /export/docker/
EXPOSE 9000
ENTRYPOINT ["/minio"]
CMD ["server", "/export"]
ENTRYPOINT ["go-wrapper", "run", "server"]
CMD ["/export"]
+156
View File
@@ -0,0 +1,156 @@
### Minio with ZFS backend - FreeBSD
This example assumes that you have a FreeBSD 10.x running
#### Step *1*
As root on the FreeBSD edit `/etc/rc.conf`
```
zfs_enable="YES"
```
Start ZFS service
```
# service zfs start
```
```
# dd if=/dev/zero of=/zfs bs=1M count=4000
```
Configure a loopback device on the `/zfs` file.
```
# mdconfig -a -t vnode -f /zfs
```
Create zfs pool
```
# zpool create minio-example /dev/md0
```
```
# df /minio-example
Filesystem 512-blocks Used Avail Capacity Mounted on
minio-example 7872440 38 7872402 0% /minio-example
```
Verify if it is writable
```
# touch /minio-example/testfile
# ls -l /minio-example/testfile
-rw-r--r-- 1 root wheel 0 Apr 26 00:51 /minio-example/testfile
```
Now you have successfully created a ZFS pool for futher reading please refer to [ZFS Quickstart Guide](https://www.freebsd.org/doc/handbook/zfs-quickstart.html)
However, this pool is not taking advantage of any ZFS features, so let's create a ZFS filesytem on this pool with compression enabled. ZFS supports many compression algorithms: lzjb, gzip, zle, lz4. LZ4 is often the most performant algorithm in terms of compression of data versus system overhead.
```
# zfs create minio-example/compressed-objects
# zfs set compression=lz4 minio-example/compressed-objects
```
To keep monitoring your pool use
```
# zpool status
pool: minio-example
state: ONLINE
scan: none requested
config:
NAME STATE READ WRITE CKSUM
minio-example ONLINE 0 0 0
md0 ONLINE 0 0 0
errors: No known data errors
```
#### Step *2*
Now start minio server on the ``/minio-example/compressed-objects``, change the permissions such that this directory is accessibly by a normal user
```
# chown -R minio-user:minio-user /minio-example/compressed-objects
```
Now login as ``minio-user`` and start minio server.
```
$ curl https://dl.minio.io/server/minio/release/freebsd-amd64/minio > minio
$ chmod 755 minio
$ ./minio server /minio-example/compressed-objects
AccessKey: X3RU3Q6B4T20TYB281W5 SecretKey: bvp0DF8c+6MzL60UxFqOZGlA5Z8UPfIAATUQuzLS Region: us-east-1
Minio Object Storage:
http://159.203.234.49:9000
http://10.12.0.5:9000
http://127.0.0.1:9000
Minio Browser:
http://159.203.234.49:9000
http://10.12.0.5:9000
http://127.0.0.1:9000
To configure Minio Client:
$ wget https://dl.minio.io/client/mc/release/freebsd-amd64/mc
$ chmod 755 mc
$ ./mc config host add myminio http://localhost:9000 X3RU3Q6B4T20TYB281W5 bvp0DF8c+6MzL60UxFqOZGlA5Z8UPfIAATUQuzLS
```
Point your browser to http://localhost:9000 and login with the credentials displayed on the command line.
Now you have a S3 compatible server running on top of your ZFS backend which transparently provides disk level compression for your uploaded objects.
Thanks for using Minio, awaiting feedback :-)
#### Building Minio Server From Source
It is possible to build the minio server from source on FreeBSD. To do this we will used the golang distribution provided by the FreeBSD pkg infrastructure.
First we will need to install golang as well as GNU make:
```
$ sudo pkg install go gmake
Updating FreeBSD repository catalogue..
FreeBSD repository is up-to-date.
All repositories are up-to-date.
The following 1 package(s) will be affected (of 0 checked):
New packages to be INSTALLED:
go: 1.6.2,1
gmake: 4.1_2
The process will require 261 MiB more space.
40 MiB to be downloaded.
Proceed with this action? [y/N]: y
Fetching go-1.6.2,1.txz: 100% 40 MiB 10.4MB/s 00:04
Fetching gmake-4.1_2.txz: 100% 363 KiB 372.2kB/s 00:01
Checking integrity... done (0 conflicting)
[1/2] Installing go-1.6.2,1...
[1/2] Extracting go-1.6.2,1: 100%
[2/2] Installing gmake-4.1_2...
[2/2] Extracting gmake-4.1_2: 100%
```
Next we need to configure our environment for golang. Insert the following lines into your ~/.profile file, and be sure to source the file before proceeding to the next step:
```
GOPATH=$HOME/golang; export GOPATH
GOROOT=/usr/local/go/; export GOROOT
```
Now we can proceed with the normal build process of minio server as found [here](https://github.com/nomadlogic/minio/blob/master/CONTRIBUTING.md). The only caveat is we need to specify gmake (GNU make) when building minio server as the current Makefile is not BSD make compatible:
```
$ mkdir -p $GOPATH/src/github.com/minio
$ cd $GOPATH/src/github.com/minio
$ git clone <paste saved URL for personal forked minio repo>
$ cd minio
$ gmake
```
From here you can start the server as you would with a precompiled minio server build.
+25 -22
View File
@@ -19,10 +19,14 @@ endif
ifeq ($(CPU), x86_64)
HOST := $(HOST)64
else
ifeq ($(CPU), amd64)
HOST := $(HOST)64
else
ifeq ($(CPU), i686)
HOST := $(HOST)32
endif
endif
endif
#############################################
@@ -41,6 +45,10 @@ ifndef (OS)
else
ifeq ($(HOST), Darwin32)
arch = clang
else
ifeq ($(HOST), FreeBSD64)
arch = gcc
endif
endif
endif
endif
@@ -60,12 +68,12 @@ checkdocker:
getdeps: checks
@go get -u github.com/golang/lint/golint && echo "Installed golint:"
@go get -u golang.org/x/tools/cmd/vet && echo "Installed vet:"
@go get -u github.com/fzipp/gocyclo && echo "Installed gocyclo:"
@go get -u github.com/remyoudompheng/go-misc/deadcode && echo "Installed deadcode:"
@go get -u github.com/client9/misspell/cmd/misspell && echo "Installed misspell:"
@go get -u github.com/gordonklaus/ineffassign && echo "Installed ineffassign:"
verifiers: getdeps vet fmt lint cyclo spelling
verifiers: vet fmt lint cyclo spelling
vet:
@echo "Running $@:"
@@ -79,25 +87,21 @@ fmt:
@GO15VENDOREXPERIMENT=1 gofmt -s -l *.go
@GO15VENDOREXPERIMENT=1 gofmt -s -l pkg
## Configure Intel library.
isa-l:
@echo "Configuring $@:"
@git clone -q https://github.com/minio/isa-l.git
@cd isa-l; make -f Makefile.unx arch=$(arch) >/dev/null; mv include isa-l;
lint:
@echo "Running $@:"
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/golint *.go
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/golint github.com/minio/minio/pkg...
ineffassign:
@echo "Running $@:"
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/ineffassign .
cyclo:
@echo "Running $@:"
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/gocyclo -over 65 *.go
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/gocyclo -over 65 pkg
build: getdeps verifiers $(UI_ASSETS) isa-l
@GO15VENDOREXPERIMENT=1 go generate github.com/minio/minio/pkg/crypto/sha1
build: getdeps verifiers $(UI_ASSETS)
deadcode:
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/deadcode
@@ -108,12 +112,12 @@ spelling:
test: build
@echo "Running all minio testing:"
@GODEBUG=cgocheck=0 CGO_CPPFLAGS="-I$(PWD)/isa-l" CGO_LDFLAGS="$(PWD)/isa-l/isa-l.a" GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) .
@GODEBUG=cgocheck=0 CGO_CPPFLAGS="-I$(PWD)/isa-l" CGO_LDFLAGS="$(PWD)/isa-l/isa-l.a" GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) github.com/minio/minio/pkg...
@GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) .
@GO15VENDOREXPERIMENT=1 go test $(GOFLAGS) github.com/minio/minio/pkg...
gomake-all: build
@echo "Installing minio:"
@CGO_CPPFLAGS="-I$(PWD)/isa-l" CGO_LDFLAGS="$(PWD)/isa-l/isa-l.a" GO15VENDOREXPERIMENT=1 go build --ldflags $(BUILD_LDFLAGS) -o $(GOPATH)/bin/minio
@GO15VENDOREXPERIMENT=1 go build --ldflags $(BUILD_LDFLAGS) -o $(GOPATH)/bin/minio
pkg-add:
@GO15VENDOREXPERIMENT=1 ${GOPATH}/bin/govendor add $(PKG)
@@ -129,19 +133,18 @@ pkg-list:
install: gomake-all
dockerimage: checkdocker verifiers $(UI_ASSETS)
dockerimage: checkdocker getdeps verifiers $(UI_ASSETS)
@echo "Building docker image:" minio:$(TAG)
@GO15VENDOREXPERIMENT=1 GOOS=linux GOARCH=amd64 go build --ldflags $(DOCKER_LDFLAGS) -o minio.dockerimage
@mkdir -p export
@sudo docker build --rm --tag=minio/minio:$(TAG) .
@rmdir export
@rm minio.dockerimage
@GO15VENDOREXPERIMENT=1 GOOS=linux GOARCH=amd64 go build --ldflags $(DOCKER_LDFLAGS) -o docker/minio.dockerimage
@cd docker; mkdir -p export; sudo docker build --rm --tag=minio/minio:$(TAG) .
@rmdir docker/export
@rm docker/minio.dockerimage
release: verifiers
@MINIO_RELEASE=RELEASE GO15VENDOREXPERIMENT=1 ./buildscripts/build.sh
@MINIO_RELEASE=RELEASE ./buildscripts/build.sh
experimental: verifiers
@MINIO_RELEASE=EXPERIMENTAL GO15VENDOREXPERIMENT=1 ./buildscripts/build.sh
@MINIO_RELEASE=EXPERIMENTAL ./buildscripts/build.sh
clean:
@echo "Cleaning up all the generated files:"
+95 -43
View File
@@ -2,16 +2,15 @@
Minio is an object storage server compatible with Amazon S3 and licensed under [Apache license 2.0](./LICENSE).
## Description
Minio is an open source object storage server released under Apache License V2. It is compatible with Amazon S3 cloud storage service. Minio follows a minimalist design philosophy.
Minio is light enough to be bundled with the application stack. It sits on the side of NodeJS, Redis, MySQL and the likes. Unlike databases, Minio stores objects such as photos, videos, log files, backups, container / VM images and so on. Minio is best suited for storing blobs of information ranging from KBs to TBs each. In a simplistic sense, it is like a FTP server with a simple get / put API over HTTP.
Minio is light enough to be bundled with the application stack. It sits on the side of NodeJS, Redis, MySQL and the likes. Unlike databases, Minio stores objects such as photos, videos, log files, backups, container / VM images and so on. Minio is best suited for storing blobs of information ranging from KBs to 5 TBs each. In a simplistic sense, it is like a FTP server with a simple get / put API over HTTP.
Minio currently implements two backends
- Filesystem (FS) - is available and ready for general purpose use. This version of the Minio binary is built using Filesystem storage backend for magnetic and solid state disks.
- ErasureCoded (XL) - is work in progress and not ready for general purpose use.
- Filesystem (FS) - is available and ready for general purpose use.
- ErasureCoded (XL) - is available, but it is not ready for general purpose use.
## Minio Client
@@ -59,6 +58,16 @@ Download ``minio`` for:
C:\Users\Username\Downloads> minio.exe --help
~~~
#### FreeBSD
Download ``minio`` from https://dl.minio.io/server/minio/release/freebsd-amd64/minio
~~~
$ chmod 755 minio
$ ./minio --help
~~~
Read more here on [How to configure Minio on FreeBSD with ZFS backend.](./FreeBSD.md)
#### Docker container
Download ``minio`` for docker.
@@ -71,7 +80,7 @@ Read more here on [How to configure data volume containers for Minio?](./Docker.
#### Source
<blockquote>
NOTE: Source installation is intended for only developers and advanced users. For general use, please download official releases from https://minio.io/download.
NOTE: Source installation is intended for only developers and advanced users. For general use, please download official releases from https://minio.io/downloads.
</blockquote>
If you do not have a working Golang environment, please follow [Install Golang](./INSTALLGO.md).
@@ -84,46 +93,20 @@ $ make
### How to use Minio?
```
NAME:
minio server - Start Minio cloud storage server.
USAGE:
minio server [OPTION VALUE] PATH
OPTION = expiry VALUE = NN[h|m|s] [DEFAULT=Unlimited]
OPTION = min-free-disk VALUE = NN% [DEFAULT: 10%]
EXAMPLES:
1. Start minio server on Linux.
$ minio server /home/shared
2. Start minio server on Windows.
$ minio server C:\MyShare
3. Start minio server bound to a specific IP:PORT, when you have multiple network interfaces.
$ minio --address 192.168.1.101:9000 server /home/shared
4. Start minio server with minimum free disk threshold to 5%
$ minio server min-free-disk 5% /home/shared/Pictures
5. Start minio server with minimum free disk threshold to 15% with auto expiration set to 1h
$ minio server min-free-disk 15% expiry 1h /home/shared/Documents
```
#### Start Minio server.
Start minio server.
~~~
$ minio server ~/Photos
AccessKey: WLGDGYAQYIGI833EV05A SecretKey: BYvgJM101sHngl2uzjXS/OBF/aMxAN06JrJ3qJlF Region: us-east-1
Minio Object Storage:
http://127.0.0.1:9000
http://10.0.0.3:9000
http://10.1.10.177:9000
Minio Browser:
http://127.0.0.1:9000
http://10.0.0.3:9000
http://10.1.10.177:9000
To configure Minio Client:
$ wget https://dl.minio.io/client/mc/release/darwin-amd64/mc
@@ -156,16 +139,41 @@ $ aws configure set default.s3.signature_version s3v4
To list your buckets.
```
$ aws --endpoint-url http://localhost:9000 s3 ls
2016-01-07 16:38:23 testbucket
2016-03-27 02:06:30 deebucket
2016-03-28 21:53:49 guestbucket
2016-03-29 13:34:34 mbtest
2016-03-26 22:01:36 mybucket
2016-03-26 15:37:02 testbucket
```
To list contents inside bucket.
```
$ aws --endpoint-url http://localhost:9000 s3 ls s3://testbucket
PRE test/
2015-12-17 08:46:41 12232928 vim
2016-01-07 16:38:23 32232928 emacs
2015-12-09 08:05:24 138504 s3cmd
$ aws --endpoint-url http://localhost:9000 s3 ls s3://mybucket
2016-03-30 00:26:53 69297 argparse-1.2.1.tar.gz
2016-03-30 00:35:37 67250 simplejson-3.3.0.tar.gz
```
To make a bucket.
```
$ aws --endpoint-url http://localhost:9000 s3 mb s3://mybucket
make_bucket: s3://mybucket/
```
To add an object to a bucket.
```
$ aws --endpoint-url http://localhost:9000 s3 cp simplejson-3.3.0.tar.gz s3://mybucket
upload: ./simplejson-3.3.0.tar.gz to s3://mybucket/simplejson-3.3.0.tar.gz
```
Delete an object from a bucket.
```
$ aws --endpoint-url http://localhost:9000 s3 rm s3://mybucket/argparse-1.2.1.tar.gzdelete: s3://mybucket/argparse-1.2.1.tar.gz
```
Remove a bucket.
```
$ aws --endpoint-url http://localhost:9000 s3 rb s3://mybucket
remove_bucket: s3://mybucket/
```
#### How to use AWS SDK with Minio?
@@ -185,21 +193,65 @@ host_base = localhost:9000
host_bucket = localhost:9000
access_key = YOUR_ACCESS_KEY_HERE
secret_key = YOUR_SECRET_KEY_HERE
signature_v2 = False
bucket_location = us-east-1
```
To list your buckets.
To make a bucket.
```
$ s3cmd mb s3://mybucket
Bucket 's3://mybucket/' created
```
To copy an object to bucket.
```
$ s3cmd put newfile s3://testbucket
upload: 'newfile' -> 's3://testbucket/newfile'
```
To copy an object to local system.
```
$ s3cmd get s3://testbucket/newfile
download: 's3://testbucket/newfile' -> './newfile'
```
To sync local file/directory to a bucket.
```
$ s3cmd sync newdemo s3://testbucket
upload: 'newdemo/newdemofile.txt' -> 's3://testbucket/newdemo/newdemofile.txt'
```
To sync bucket or object with local filesystem.
```
$ s3cmd sync s3://otherbucket otherlocalbucket
download: 's3://otherbucket/cat.jpg' -> 'otherlocalbucket/cat.jpg'
```
To list buckets.
```
$ s3cmd ls s3://
2015-12-09 16:12 s3://testbbucket
```
To list contents inside buckets.
To list contents inside bucket.
```
$ s3cmd ls s3://testbucket/
DIR s3://testbucket/test/
2015-12-09 16:05 138504 s3://testbucket/newfile
```
Delete an object from bucket.
```
$ s3cmd del s3://testbucket/newfile
delete: 's3://testbucket/newfile'
```
Delete a bucket.
```
$ s3cmd rb s3://testbucket
Bucket 's3://testbucket/' removed
```
## Contribute to Minio Project
Please follow Minio [Contributor's Guide](./CONTRIBUTING.md)
+94
View File
@@ -0,0 +1,94 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* 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 main
import (
"crypto/rand"
"encoding/base64"
"fmt"
"regexp"
)
// credential container for access and secret keys.
type credential struct {
AccessKeyID string `json:"accessKey"`
SecretAccessKey string `json:"secretKey"`
}
// stringer colorized access keys.
func (a credential) String() string {
accessStr := colorMagenta("AccessKey: ") + colorWhite(a.AccessKeyID)
secretStr := colorMagenta("SecretKey: ") + colorWhite(a.SecretAccessKey)
return fmt.Sprint(accessStr + " " + secretStr)
}
const (
minioAccessID = 20
minioSecretID = 40
)
// isValidSecretKey - validate secret key.
var isValidSecretKey = regexp.MustCompile(`^.{8,40}$`)
// isValidAccessKey - validate access key.
var isValidAccessKey = regexp.MustCompile(`^[a-zA-Z0-9\\-\\.\\_\\~]{5,20}$`)
// mustGenAccessKeys - must generate access credentials.
func mustGenAccessKeys() (creds credential) {
creds, err := genAccessKeys()
fatalIf(err, "Unable to generate access keys.")
return creds
}
// genAccessKeys - generate access credentials.
func genAccessKeys() (credential, error) {
accessKeyID, err := genAccessKeyID()
if err != nil {
return credential{}, err
}
secretAccessKey, err := genSecretAccessKey()
if err != nil {
return credential{}, err
}
creds := credential{
AccessKeyID: string(accessKeyID),
SecretAccessKey: string(secretAccessKey),
}
return creds, nil
}
// genAccessKeyID - generate random alpha numeric value using only uppercase characters
// takes input as size in integer
func genAccessKeyID() ([]byte, error) {
alpha := make([]byte, minioAccessID)
if _, err := rand.Read(alpha); err != nil {
return nil, err
}
for i := 0; i < minioAccessID; i++ {
alpha[i] = alphaNumericTable[alpha[i]%byte(len(alphaNumericTable))]
}
return alpha, nil
}
// genSecretAccessKey - generate random base64 numeric value from a random seed.
func genSecretAccessKey() ([]byte, error) {
rb := make([]byte, minioSecretID)
if _, err := rand.Read(rb); err != nil {
return nil, err
}
return []byte(base64.StdEncoding.EncodeToString(rb))[:minioSecretID], nil
}
+9 -11
View File
@@ -22,8 +22,6 @@ import (
"net/url"
"os"
"time"
"github.com/minio/minio/pkg/probe"
)
type accessLogHandler struct {
@@ -60,14 +58,14 @@ type LogMessage struct {
func (h *accessLogHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
message, err := getLogMessage(w, req)
fatalIf(err.Trace(), "Unable to extract http message.", nil)
_, e := h.accessLogFile.Write(message)
fatalIf(probe.NewError(e), "Writing to log file failed.", nil)
fatalIf(err, "Unable to parse HTTP request and response fields.")
_, err = h.accessLogFile.Write(message)
fatalIf(err, "Unable to log HTTP access.")
h.Handler.ServeHTTP(w, req)
}
func getLogMessage(w http.ResponseWriter, req *http.Request) ([]byte, *probe.Error) {
func getLogMessage(w http.ResponseWriter, req *http.Request) ([]byte, error) {
logMessage := &LogMessage{
StartTime: time.Now().UTC(),
}
@@ -103,9 +101,9 @@ func getLogMessage(w http.ResponseWriter, req *http.Request) ([]byte, *probe.Err
// logMessage.HTTP.Request = req
logMessage.Duration = time.Now().UTC().Sub(logMessage.StartTime)
js, e := json.Marshal(logMessage)
if e != nil {
return nil, probe.NewError(e)
js, err := json.Marshal(logMessage)
if err != nil {
return nil, err
}
js = append(js, byte('\n')) // append a new line
return js, nil
@@ -113,8 +111,8 @@ func getLogMessage(w http.ResponseWriter, req *http.Request) ([]byte, *probe.Err
// setAccessLogHandler logs requests
func setAccessLogHandler(h http.Handler) http.Handler {
file, e := os.OpenFile("access.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
fatalIf(probe.NewError(e), "Unable to open access log.", nil)
file, err := os.OpenFile("access.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
fatalIf(err, "Failed top open access log.")
return &accessLogHandler{Handler: h, accessLogFile: file}
}
-82
View File
@@ -1,82 +0,0 @@
/*
* Minio Cloud Storage, (C) 2015 Minio, Inc.
*
* 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 main
import (
"crypto/rand"
"encoding/base64"
"regexp"
"github.com/minio/minio/pkg/probe"
)
const (
minioAccessID = 20
minioSecretID = 40
)
// isValidAccessKey - validate access key
func isValidAccessKey(accessKeyID string) bool {
if accessKeyID == "" {
return true
}
regex := regexp.MustCompile("^[A-Z0-9\\-\\.\\_\\~]{20}$")
return regex.MatchString(accessKeyID)
}
// isValidSecretKey - validate secret key
func isValidSecretKey(secretKeyID string) bool {
regex := regexp.MustCompile("^[a-zA-Z0-9\\-\\.\\_\\~]{40}$")
return regex.MatchString(secretKeyID)
}
// generateAccessKeyID - generate random alpha numeric value using only uppercase characters
// takes input as size in integer
func generateAccessKeyID() ([]byte, *probe.Error) {
alpha := make([]byte, minioAccessID)
if _, e := rand.Read(alpha); e != nil {
return nil, probe.NewError(e)
}
for i := 0; i < minioAccessID; i++ {
alpha[i] = alphaNumericTable[alpha[i]%byte(len(alphaNumericTable))]
}
return alpha, nil
}
// generateSecretAccessKey - generate random base64 numeric value from a random seed.
func generateSecretAccessKey() ([]byte, *probe.Error) {
rb := make([]byte, minioSecretID)
if _, e := rand.Read(rb); e != nil {
return nil, probe.NewError(e)
}
return []byte(base64.StdEncoding.EncodeToString(rb))[:minioSecretID], nil
}
// mustGenerateAccessKeyID - must generate random alpha numeric value using only uppercase characters
// takes input as size in integer
func mustGenerateAccessKeyID() []byte {
alpha, err := generateAccessKeyID()
fatalIf(err.Trace(), "Unable to generate accessKeyID.", nil)
return alpha
}
// mustGenerateSecretAccessKey - generate random base64 numeric value from a random seed.
func mustGenerateSecretAccessKey() []byte {
secretKey, err := generateSecretAccessKey()
fatalIf(err.Trace(), "Unable to generate secretAccessKey.", nil)
return secretKey
}
+11
View File
@@ -16,11 +16,22 @@
package main
import (
"encoding/xml"
)
// ObjectIdentifier carries key name for the object to delete.
type ObjectIdentifier struct {
ObjectName string `xml:"Key"`
}
// createBucketConfiguration container for bucket configuration request from client.
// Used for parsing the location from the request body for MakeBucketbucket.
type createBucketLocationConfiguration struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CreateBucketConfiguration" json:"-"`
Location string `xml:"LocationConstraint"`
}
// DeleteObjectsRequest - xml carrying the object key names which needs to be deleted.
type DeleteObjectsRequest struct {
// Element to enable quiet mode for the request
+178 -13
View File
@@ -83,11 +83,32 @@ const (
ErrMalformedPOSTRequest
ErrSignatureVersionNotSupported
ErrBucketNotEmpty
ErrRootPathFull
ErrObjectExistsAsPrefix
ErrAllAccessDisabled
ErrMalformedPolicy
ErrMissingFields
ErrMissingCredTag
ErrCredMalformed
ErrInvalidRegion
ErrInvalidService
ErrInvalidRequestVersion
ErrMissingSignTag
ErrMissingSignHeadersTag
ErrPolicyAlreadyExpired
ErrMalformedDate
ErrMalformedExpires
ErrAuthHeaderEmpty
ErrExpiredPresignRequest
ErrMissingDateHeader
ErrInvalidQuerySignatureAlgo
ErrInvalidQueryParams
ErrBucketAlreadyOwnedByYou
// Add new error codes here.
// Minio extended errors.
ErrReadQuorum
ErrWriteQuorum
ErrStorageFull
ErrObjectExistsAsDirectory
)
// error code to APIError structure, these fields carry respective
@@ -278,16 +299,6 @@ var errorCodeResponse = map[APIErrorCode]APIError{
Description: "The bucket you tried to delete is not empty.",
HTTPStatusCode: http.StatusConflict,
},
ErrRootPathFull: {
Code: "RootPathFull",
Description: "Root path has reached its minimum free disk threshold. Please delete few objects to proceed.",
HTTPStatusCode: http.StatusInternalServerError,
},
ErrObjectExistsAsPrefix: {
Code: "ObjectExistsAsPrefix",
Description: "An object already exists as your prefix, choose a different prefix to proceed.",
HTTPStatusCode: http.StatusConflict,
},
ErrAllAccessDisabled: {
Code: "AllAccessDisabled",
Description: "All access to this bucket has been disabled.",
@@ -295,12 +306,166 @@ var errorCodeResponse = map[APIErrorCode]APIError{
},
ErrMalformedPolicy: {
Code: "MalformedPolicy",
Description: "Policy has invalid resource",
Description: "Policy has invalid resource.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrMissingFields: {
Code: "MissingFields",
Description: "Missing fields in request.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrMissingCredTag: {
Code: "InvalidRequest",
Description: "Missing Credential field for this request.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrCredMalformed: {
Code: "CredentialMalformed",
Description: "Credential field malformed does not follow accessKeyID/credScope.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrMalformedDate: {
Code: "MalformedDate",
Description: "Invalid date format header, expected to be in ISO8601, RFC1123 or RFC1123Z time format.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrInvalidRegion: {
Code: "InvalidRegion",
Description: "Region does not match.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrInvalidService: {
Code: "AccessDenied",
Description: "Service scope should be of value 's3'.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrInvalidRequestVersion: {
Code: "AccessDenied",
Description: "Request scope should be of value 'aws4_request'.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrMissingSignTag: {
Code: "AccessDenied",
Description: "Signature header missing Signature field.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrMissingSignHeadersTag: {
Code: "InvalidArgument",
Description: "Signature header missing SignedHeaders field.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrPolicyAlreadyExpired: {
Code: "AccessDenied",
Description: "Invalid according to Policy: Policy expired.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrMalformedExpires: {
Code: "MalformedExpires",
Description: "Malformed expires header, expected non-zero number.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrAuthHeaderEmpty: {
Code: "InvalidArgument",
Description: "Authorization header is invalid -- one and only one ' ' (space) required.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrMissingDateHeader: {
Code: "AccessDenied",
Description: "AWS authentication requires a valid Date or x-amz-date header",
HTTPStatusCode: http.StatusBadRequest,
},
ErrInvalidQuerySignatureAlgo: {
Code: "AuthorizationQueryParametersError",
Description: "X-Amz-Algorithm only supports \"AWS4-HMAC-SHA256\".",
HTTPStatusCode: http.StatusBadRequest,
},
ErrExpiredPresignRequest: {
Code: "AccessDenied",
Description: "Request has expired.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrInvalidQueryParams: {
Code: "AuthorizationQueryParametersError",
Description: "Query-string authentication version 4 requires the X-Amz-Algorithm, X-Amz-Credential, X-Amz-Signature, X-Amz-Date, X-Amz-SignedHeaders, and X-Amz-Expires parameters.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrBucketAlreadyOwnedByYou: {
Code: "BucketAlreadyOwnedByYou",
Description: "Your previous request to create the named bucket succeeded and you already own it.",
HTTPStatusCode: http.StatusConflict,
},
/// Minio extensions.
ErrStorageFull: {
Code: "XMinioStorageFull",
Description: "Storage backend has reached its minimum free disk threshold. Please delete few objects to proceed.",
HTTPStatusCode: http.StatusInternalServerError,
},
ErrObjectExistsAsDirectory: {
Code: "XMinioObjectExistsAsDirectory",
Description: "Object name already exists as a directory.",
HTTPStatusCode: http.StatusConflict,
},
ErrReadQuorum: {
Code: "XMinioReadQuorum",
Description: "Multiple disk failures, unable to reconstruct data.",
HTTPStatusCode: http.StatusServiceUnavailable,
},
ErrWriteQuorum: {
Code: "XMinioWriteQuorum",
Description: "Multiple disks failures, unable to write data.",
HTTPStatusCode: http.StatusServiceUnavailable,
},
// Add your error structure here.
}
// toAPIErrorCode - Converts embedded errors. Convenience
// function written to handle all cases where we have known types of
// errors returned by underlying layers.
func toAPIErrorCode(err error) (apiErr APIErrorCode) {
if err == nil {
return ErrNone
}
// Verify if the underlying error is signature mismatch.
if err == errSignatureMismatch {
return ErrSignatureDoesNotMatch
}
switch err.(type) {
case StorageFull:
apiErr = ErrStorageFull
case BadDigest:
apiErr = ErrBadDigest
case IncompleteBody:
apiErr = ErrIncompleteBody
case ObjectExistsAsDirectory:
apiErr = ErrObjectExistsAsDirectory
case BucketNameInvalid:
apiErr = ErrInvalidBucketName
case BucketNotFound:
apiErr = ErrNoSuchBucket
case BucketNotEmpty:
apiErr = ErrBucketNotEmpty
case BucketExists:
apiErr = ErrBucketAlreadyOwnedByYou
case ObjectNotFound:
apiErr = ErrNoSuchKey
case ObjectNameInvalid:
apiErr = ErrNoSuchKey
case InvalidUploadID:
apiErr = ErrNoSuchUpload
case InvalidPart:
apiErr = ErrInvalidPart
case InsufficientWriteQuorum:
apiErr = ErrWriteQuorum
case InsufficientReadQuorum:
apiErr = ErrReadQuorum
case PartTooSmall:
apiErr = ErrEntityTooSmall
default:
apiErr = ErrInternalError
}
return apiErr
}
// getAPIError provides API Error for input API error code.
func getAPIError(code APIErrorCode) APIError {
return errorCodeResponse[code]
+6 -8
View File
@@ -23,8 +23,6 @@ import (
"net/http"
"runtime"
"strconv"
"github.com/minio/minio/pkg/fs"
)
//// helpers
@@ -60,20 +58,20 @@ func encodeResponse(response interface{}) []byte {
}
// Write object header
func setObjectHeaders(w http.ResponseWriter, metadata fs.ObjectMetadata, contentRange *httpRange) {
func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, contentRange *httpRange) {
// set common headers
setCommonHeaders(w)
// set object-related metadata headers
lastModified := metadata.LastModified.UTC().Format(http.TimeFormat)
lastModified := objInfo.ModTime.UTC().Format(http.TimeFormat)
w.Header().Set("Last-Modified", lastModified)
w.Header().Set("Content-Type", metadata.ContentType)
if metadata.MD5 != "" {
w.Header().Set("ETag", "\""+metadata.MD5+"\"")
w.Header().Set("Content-Type", objInfo.ContentType)
if objInfo.MD5Sum != "" {
w.Header().Set("ETag", "\""+objInfo.MD5Sum+"\"")
}
w.Header().Set("Content-Length", strconv.FormatInt(metadata.Size, 10))
w.Header().Set("Content-Length", strconv.FormatInt(objInfo.Size, 10))
// for providing ranged content
if contentRange != nil {
+61 -19
View File
@@ -19,36 +19,78 @@ package main
import (
"net/url"
"strconv"
"github.com/minio/minio/pkg/fs"
)
// parse bucket url queries
func getBucketResources(values url.Values) (prefix, marker, delimiter string, maxkeys int, encodingType string) {
// Parse bucket url queries
func getListObjectsV1Args(values url.Values) (prefix, marker, delimiter string, maxkeys int, encodingType string) {
prefix = values.Get("prefix")
marker = values.Get("marker")
delimiter = values.Get("delimiter")
maxkeys, _ = strconv.Atoi(values.Get("max-keys"))
if values.Get("max-keys") != "" {
maxkeys, _ = strconv.Atoi(values.Get("max-keys"))
} else {
maxkeys = maxObjectList
}
encodingType = values.Get("encoding-type")
return
}
// part bucket url queries for ?uploads
func getBucketMultipartResources(values url.Values) (v fs.BucketMultipartResourcesMetadata) {
v.Prefix = values.Get("prefix")
v.KeyMarker = values.Get("key-marker")
v.MaxUploads, _ = strconv.Atoi(values.Get("max-uploads"))
v.Delimiter = values.Get("delimiter")
v.EncodingType = values.Get("encoding-type")
v.UploadIDMarker = values.Get("upload-id-marker")
// Parse bucket url queries for ListObjects V2.
func getListObjectsV2Args(values url.Values) (prefix, token, startAfter, delimiter string, maxkeys int, encodingType string) {
prefix = values.Get("prefix")
startAfter = values.Get("start-after")
delimiter = values.Get("delimiter")
if values.Get("max-keys") != "" {
maxkeys, _ = strconv.Atoi(values.Get("max-keys"))
} else {
maxkeys = maxObjectList
}
encodingType = values.Get("encoding-type")
token = values.Get("continuation-token")
return
}
// parse object url queries
func getObjectResources(values url.Values) (v fs.ObjectResourcesMetadata) {
v.UploadID = values.Get("uploadId")
v.PartNumberMarker, _ = strconv.Atoi(values.Get("part-number-marker"))
v.MaxParts, _ = strconv.Atoi(values.Get("max-parts"))
v.EncodingType = values.Get("encoding-type")
// Parse bucket url queries
func getBucketResources(values url.Values) (listType int, prefix, marker, delimiter string, maxkeys int, encodingType string) {
if values.Get("list-type") != "" {
listType, _ = strconv.Atoi(values.Get("list-type"))
}
prefix = values.Get("prefix")
marker = values.Get("marker")
delimiter = values.Get("delimiter")
if values.Get("max-keys") != "" {
maxkeys, _ = strconv.Atoi(values.Get("max-keys"))
} else {
maxkeys = maxObjectList
}
encodingType = values.Get("encoding-type")
return
}
// Parse bucket url queries for ?uploads
func getBucketMultipartResources(values url.Values) (prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int, encodingType string) {
prefix = values.Get("prefix")
keyMarker = values.Get("key-marker")
uploadIDMarker = values.Get("upload-id-marker")
delimiter = values.Get("delimiter")
if values.Get("max-uploads") != "" {
maxUploads, _ = strconv.Atoi(values.Get("max-uploads"))
} else {
maxUploads = maxUploadsList
}
encodingType = values.Get("encoding-type")
return
}
// Parse object url queries
func getObjectResources(values url.Values) (uploadID string, partNumberMarker, maxParts int, encodingType string) {
uploadID = values.Get("uploadId")
partNumberMarker, _ = strconv.Atoi(values.Get("part-number-marker"))
if values.Get("max-parts") != "" {
maxParts, _ = strconv.Atoi(values.Get("max-parts"))
} else {
maxParts = maxPartsList
}
encodingType = values.Get("encoding-type")
return
}
+133 -61
View File
@@ -20,15 +20,13 @@ import (
"encoding/xml"
"net/http"
"time"
"github.com/minio/minio/pkg/fs"
)
const (
// Reply date format
timeFormatAMZ = "2006-01-02T15:04:05.000Z"
// Limit number of objects in a given response.
maxObjectList = 1000
timeFormatAMZ = "2006-01-02T15:04:05.000Z" // Reply date format
maxObjectList = 1000 // Limit number of objects in a listObjectsResponse.
maxUploadsList = 1000 // Limit number of uploads in a listUploadsResponse.
maxPartsList = 1000 // Limit number of parts in a listPartsResponse.
)
// LocationResponse - format for location response.
@@ -37,28 +35,6 @@ type LocationResponse struct {
Location string `xml:",chardata"`
}
// AccessControlPolicyResponse - format for get bucket acl response.
type AccessControlPolicyResponse struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ AccessControlPolicy" json:"-"`
AccessControlList struct {
Grants []Grant `xml:"Grant"`
}
Owner Owner
}
// Grant container for grantee and permission.
type Grant struct {
Grantee struct {
ID string
DisplayName string
EmailAddress string
Type string
URI string
}
Permission string
}
// ListObjectsResponse - format for list objects response.
type ListObjectsResponse struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListBucketResult" json:"-"`
@@ -89,6 +65,37 @@ type ListObjectsResponse struct {
Prefix string
}
// ListObjectsV2Response - format for list objects response.
type ListObjectsV2Response struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListBucketResult" json:"-"`
CommonPrefixes []CommonPrefix
Contents []Object
Delimiter string
// Encoding type used to encode object keys in the response.
EncodingType string
// A flag that indicates whether or not ListObjects returned all of the results
// that satisfied the search criteria.
IsTruncated bool
StartAfter string
MaxKeys int
Name string
// When response is truncated (the IsTruncated element value in the response
// is true), you can use the key name in this field as marker in the subsequent
// request to get next set of objects. Server lists objects in alphabetical
// order Note: This element is returned only if you have delimiter request parameter
// specified. If response does not include the NextMaker and it is truncated,
// you can use the value of the last Key in the response as the marker in the
// subsequent request to get the next set of object keys.
ContinuationToken string
NextContinuationToken string
Prefix string
}
// Part container for part metadata.
type Part struct {
PartNumber int
@@ -218,6 +225,16 @@ type CompleteMultipartUploadResponse struct {
ETag string
}
// PostResponse container for completed post upload response
type PostResponse struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ PostResponse" json:"-"`
Location string
Bucket string
Key string
ETag string
}
// DeleteError structure.
type DeleteError struct {
Code string
@@ -241,13 +258,18 @@ func getLocation(r *http.Request) string {
return r.URL.Path
}
// getObjectLocation gets the relative URL for an object
func getObjectLocation(bucketName string, key string) string {
return "/" + bucketName + "/" + key
}
// takes an array of Bucketmetadata information for serialization
// input:
// array of bucket metadata
//
// output:
// populated struct that can be serialized to match xml and json api spec output
func generateListBucketsResponse(buckets []fs.BucketMetadata) ListBucketsResponse {
func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
var listbuckets []Bucket
var data = ListBucketsResponse{}
var owner = Owner{}
@@ -269,7 +291,7 @@ func generateListBucketsResponse(buckets []fs.BucketMetadata) ListBucketsRespons
}
// generates an ListObjects response for the said bucket with other enumerated options.
func generateListObjectsResponse(bucket, prefix, marker, delimiter string, maxKeys int, resp fs.ListObjectsResult) ListObjectsResponse {
func generateListObjectsResponse(bucket, prefix, marker, delimiter string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
var contents []Object
var prefixes []CommonPrefix
var owner = Owner{}
@@ -280,13 +302,13 @@ func generateListObjectsResponse(bucket, prefix, marker, delimiter string, maxKe
for _, object := range resp.Objects {
var content = Object{}
if object.Object == "" {
if object.Name == "" {
continue
}
content.Key = object.Object
content.LastModified = object.LastModified.UTC().Format(timeFormatAMZ)
if object.MD5 != "" {
content.ETag = "\"" + object.MD5 + "\""
content.Key = object.Name
content.LastModified = object.ModTime.UTC().Format(timeFormatAMZ)
if object.MD5Sum != "" {
content.ETag = "\"" + object.MD5Sum + "\""
}
content.Size = object.Size
content.StorageClass = "STANDARD"
@@ -313,6 +335,51 @@ func generateListObjectsResponse(bucket, prefix, marker, delimiter string, maxKe
return data
}
// generates an ListObjects response for the said bucket with other enumerated options.
func generateListObjectsV2Response(bucket, prefix, token, startAfter, delimiter string, maxKeys int, resp ListObjectsInfo) ListObjectsV2Response {
var contents []Object
var prefixes []CommonPrefix
var owner = Owner{}
var data = ListObjectsV2Response{}
owner.ID = "minio"
owner.DisplayName = "minio"
for _, object := range resp.Objects {
var content = Object{}
if object.Name == "" {
continue
}
content.Key = object.Name
content.LastModified = object.ModTime.UTC().Format(timeFormatAMZ)
if object.MD5Sum != "" {
content.ETag = "\"" + object.MD5Sum + "\""
}
content.Size = object.Size
content.StorageClass = "STANDARD"
content.Owner = owner
contents = append(contents, content)
}
// TODO - support EncodingType in xml decoding
data.Name = bucket
data.Contents = contents
data.StartAfter = startAfter
data.Delimiter = delimiter
data.Prefix = prefix
data.MaxKeys = maxKeys
data.ContinuationToken = token
data.NextContinuationToken = resp.NextMarker
data.IsTruncated = resp.IsTruncated
for _, prefix := range resp.Prefixes {
var prefixItem = CommonPrefix{}
prefixItem.Prefix = prefix
prefixes = append(prefixes, prefixItem)
}
data.CommonPrefixes = prefixes
return data
}
// generateCopyObjectResponse
func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectResponse {
return CopyObjectResponse{
@@ -341,56 +408,61 @@ func generateCompleteMultpartUploadResponse(bucket, key, location, etag string)
}
// generateListPartsResult
func generateListPartsResponse(objectMetadata fs.ObjectResourcesMetadata) ListPartsResponse {
func generateListPartsResponse(partsInfo ListPartsInfo) ListPartsResponse {
// TODO - support EncodingType in xml decoding
listPartsResponse := ListPartsResponse{}
listPartsResponse.Bucket = objectMetadata.Bucket
listPartsResponse.Key = objectMetadata.Object
listPartsResponse.UploadID = objectMetadata.UploadID
listPartsResponse.Bucket = partsInfo.Bucket
listPartsResponse.Key = partsInfo.Object
listPartsResponse.UploadID = partsInfo.UploadID
listPartsResponse.StorageClass = "STANDARD"
listPartsResponse.Initiator.ID = "minio"
listPartsResponse.Initiator.DisplayName = "minio"
listPartsResponse.Owner.ID = "minio"
listPartsResponse.Owner.DisplayName = "minio"
listPartsResponse.MaxParts = objectMetadata.MaxParts
listPartsResponse.PartNumberMarker = objectMetadata.PartNumberMarker
listPartsResponse.IsTruncated = objectMetadata.IsTruncated
listPartsResponse.NextPartNumberMarker = objectMetadata.NextPartNumberMarker
listPartsResponse.MaxParts = partsInfo.MaxParts
listPartsResponse.PartNumberMarker = partsInfo.PartNumberMarker
listPartsResponse.IsTruncated = partsInfo.IsTruncated
listPartsResponse.NextPartNumberMarker = partsInfo.NextPartNumberMarker
listPartsResponse.Parts = make([]Part, len(objectMetadata.Part))
for _, part := range objectMetadata.Part {
listPartsResponse.Parts = make([]Part, len(partsInfo.Parts))
for index, part := range partsInfo.Parts {
newPart := Part{}
newPart.PartNumber = part.PartNumber
newPart.ETag = "\"" + part.ETag + "\""
newPart.Size = part.Size
newPart.LastModified = part.LastModified.UTC().Format(timeFormatAMZ)
listPartsResponse.Parts = append(listPartsResponse.Parts, newPart)
listPartsResponse.Parts[index] = newPart
}
return listPartsResponse
}
// generateListMultipartUploadsResponse
func generateListMultipartUploadsResponse(bucket string, metadata fs.BucketMultipartResourcesMetadata) ListMultipartUploadsResponse {
func generateListMultipartUploadsResponse(bucket string, multipartsInfo ListMultipartsInfo) ListMultipartUploadsResponse {
listMultipartUploadsResponse := ListMultipartUploadsResponse{}
listMultipartUploadsResponse.Bucket = bucket
listMultipartUploadsResponse.Delimiter = metadata.Delimiter
listMultipartUploadsResponse.IsTruncated = metadata.IsTruncated
listMultipartUploadsResponse.EncodingType = metadata.EncodingType
listMultipartUploadsResponse.Prefix = metadata.Prefix
listMultipartUploadsResponse.KeyMarker = metadata.KeyMarker
listMultipartUploadsResponse.NextKeyMarker = metadata.NextKeyMarker
listMultipartUploadsResponse.MaxUploads = metadata.MaxUploads
listMultipartUploadsResponse.NextUploadIDMarker = metadata.NextUploadIDMarker
listMultipartUploadsResponse.UploadIDMarker = metadata.UploadIDMarker
listMultipartUploadsResponse.Uploads = make([]Upload, len(metadata.Upload))
for _, upload := range metadata.Upload {
listMultipartUploadsResponse.Delimiter = multipartsInfo.Delimiter
listMultipartUploadsResponse.IsTruncated = multipartsInfo.IsTruncated
listMultipartUploadsResponse.EncodingType = multipartsInfo.EncodingType
listMultipartUploadsResponse.Prefix = multipartsInfo.Prefix
listMultipartUploadsResponse.KeyMarker = multipartsInfo.KeyMarker
listMultipartUploadsResponse.NextKeyMarker = multipartsInfo.NextKeyMarker
listMultipartUploadsResponse.MaxUploads = multipartsInfo.MaxUploads
listMultipartUploadsResponse.NextUploadIDMarker = multipartsInfo.NextUploadIDMarker
listMultipartUploadsResponse.UploadIDMarker = multipartsInfo.UploadIDMarker
listMultipartUploadsResponse.CommonPrefixes = make([]CommonPrefix, len(multipartsInfo.CommonPrefixes))
for index, commonPrefix := range multipartsInfo.CommonPrefixes {
listMultipartUploadsResponse.CommonPrefixes[index] = CommonPrefix{
Prefix: commonPrefix,
}
}
listMultipartUploadsResponse.Uploads = make([]Upload, len(multipartsInfo.Uploads))
for index, upload := range multipartsInfo.Uploads {
newUpload := Upload{}
newUpload.UploadID = upload.UploadID
newUpload.Key = upload.Object
newUpload.Initiated = upload.Initiated.Format(timeFormatAMZ)
listMultipartUploadsResponse.Uploads = append(listMultipartUploadsResponse.Uploads, newUpload)
newUpload.Initiated = upload.Initiated.UTC().Format(timeFormatAMZ)
listMultipartUploadsResponse.Uploads[index] = newUpload
}
return listMultipartUploadsResponse
}
+86
View File
@@ -0,0 +1,86 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import router "github.com/gorilla/mux"
// objectAPIHandler implements and provides http handlers for S3 API.
type objectAPIHandlers struct {
ObjectAPI ObjectLayer
}
// registerAPIRouter - registers S3 compatible APIs.
func registerAPIRouter(mux *router.Router, api objectAPIHandlers) {
// API Router
apiRouter := mux.NewRoute().PathPrefix("/").Subrouter()
// Bucket router
bucket := apiRouter.PathPrefix("/{bucket}").Subrouter()
/// Object operations
// HeadObject
bucket.Methods("HEAD").Path("/{object:.+}").HandlerFunc(api.HeadObjectHandler)
// PutObjectPart
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(api.PutObjectPartHandler).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
// ListObjectPxarts
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(api.ListObjectPartsHandler).Queries("uploadId", "{uploadId:.*}")
// CompleteMultipartUpload
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(api.CompleteMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}")
// NewMultipartUpload
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(api.NewMultipartUploadHandler).Queries("uploads", "")
// AbortMultipartUpload
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(api.AbortMultipartUploadHandler).Queries("uploadId", "{uploadId:.*}")
// GetObject
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(api.GetObjectHandler)
// CopyObject
bucket.Methods("PUT").Path("/{object:.+}").HeadersRegexp("X-Amz-Copy-Source", ".*?(\\/).*?").HandlerFunc(api.CopyObjectHandler)
// PutObject
bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(api.PutObjectHandler)
// DeleteObject
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(api.DeleteObjectHandler)
/// Bucket operations
// GetBucketLocation
bucket.Methods("GET").HandlerFunc(api.GetBucketLocationHandler).Queries("location", "")
// GetBucketPolicy
bucket.Methods("GET").HandlerFunc(api.GetBucketPolicyHandler).Queries("policy", "")
// ListMultipartUploads
bucket.Methods("GET").HandlerFunc(api.ListMultipartUploadsHandler).Queries("uploads", "")
// ListObjects
bucket.Methods("GET").HandlerFunc(api.ListObjectsHandler)
// PutBucketPolicy
bucket.Methods("PUT").HandlerFunc(api.PutBucketPolicyHandler).Queries("policy", "")
// PutBucket
bucket.Methods("PUT").HandlerFunc(api.PutBucketHandler)
// HeadBucket
bucket.Methods("HEAD").HandlerFunc(api.HeadBucketHandler)
// PostPolicy
bucket.Methods("POST").HeadersRegexp("Content-Type", "multipart/form-data*").HandlerFunc(api.PostPolicyBucketHandler)
// DeleteMultipleObjects
bucket.Methods("POST").HandlerFunc(api.DeleteMultipleObjectsHandler)
// DeleteBucketPolicy
bucket.Methods("DELETE").HandlerFunc(api.DeleteBucketPolicyHandler).Queries("policy", "")
// DeleteBucket
bucket.Methods("DELETE").HandlerFunc(api.DeleteBucketHandler)
/// Root operation
// ListBuckets
apiRouter.Methods("GET").HandlerFunc(api.ListBucketsHandler)
}
-16
View File
@@ -10,36 +10,20 @@ environment:
GOPATH: c:\gopath
GO_EXTLINK_ENABLED: 0
GO15VENDOREXPERIMENT: 1
# Remove this after fixing erasure code.
GODEBUG: cgocheck=0
# scripts that run after cloning repository
install:
- '"C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64'
- curl -fsSL -o c:\go\bin\yasm.exe http://www.tortall.net/projects/yasm/releases/yasm-1.3.0-win64.exe
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
- rd C:\Go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.6.windows-amd64.zip
- 7z x go1.6.windows-amd64.zip -oC:\ >nul
- curl -fsSL -o mingw-w64.7z http://downloads.sourceforge.net/project/mingw-w64-dgn/mingw-w64/mingw-w64-bin-x86_64-20151206.7z
- 7z x -oC:\ mingw-w64.7z > NUL
- set PATH=C:\mingw64\bin;%PATH%
- x86_64-w64-mingw32-gcc --version
- go version
- go env
- git clone https://github.com/minio/isa-l
- cd isa-l
- make -f Makefile.unx arch=mingw
- mv include isa-l
- set CGO_CPPFLAGS=-Ic:/gopath/src/github.com/minio/minio/isa-l
- set CGO_LDFLAGS=c:/gopath/src/github.com/minio/minio/isa-l/isa-l.a
- set CC=x86_64-w64-mingw32-gcc
- set CXX=x86_64-w64-mingw32-g++
- cd %GOPATH%\src\github.com\minio\minio
# to run your custom scripts instead of automatic MSBuild
build_script:
- go generate github.com/minio/minio/pkg/crypto/sha1
- go test .
- go test -race .
- go test github.com/minio/minio/pkg...
+37 -36
View File
@@ -17,17 +17,14 @@
package main
import (
"bytes"
"crypto/md5"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"io/ioutil"
"net/http"
"strings"
"github.com/minio/minio/pkg/s3/signature4"
)
const (
signV4Algorithm = "AWS4-HMAC-SHA256"
jwtAlgorithm = "Bearer"
)
// Verify if request has JWT.
@@ -68,14 +65,6 @@ func isRequestPostPolicySignatureV4(r *http.Request) bool {
return false
}
// Verify if incoming request is anonymous.
func isRequestAnonymous(r *http.Request) bool {
if isRequestJWT(r) || isRequestSignatureV4(r) || isRequestPresignedSignatureV4(r) || isRequestPostPolicySignatureV4(r) {
return false
}
return true
}
// Authorization type.
type authType int
@@ -105,30 +94,42 @@ func getRequestAuthType(r *http.Request) authType {
return authTypeUnknown
}
// sum256 calculate sha256 sum for an input byte array
func sum256(data []byte) []byte {
hash := sha256.New()
hash.Write(data)
return hash.Sum(nil)
}
// sumMD5 calculate md5 sum for an input byte array
func sumMD5(data []byte) []byte {
hash := md5.New()
hash.Write(data)
return hash.Sum(nil)
}
// Verify if request has valid AWS Signature Version '4'.
func isReqAuthenticated(sign *signature4.Sign, r *http.Request) (s3Error APIErrorCode) {
auth := sign.SetHTTPRequestToVerify(r)
func isReqAuthenticated(r *http.Request) (s3Error APIErrorCode) {
if r == nil {
return ErrInternalError
}
payload, err := ioutil.ReadAll(r.Body)
if err != nil {
return ErrInternalError
}
// Verify Content-Md5, if payload is set.
if r.Header.Get("Content-Md5") != "" {
if r.Header.Get("Content-Md5") != base64.StdEncoding.EncodeToString(sumMD5(payload)) {
return ErrBadDigest
}
}
// Populate back the payload.
r.Body = ioutil.NopCloser(bytes.NewReader(payload))
validateRegion := true // Validate region.
if isRequestSignatureV4(r) {
dummyPayload := sha256.Sum256([]byte(""))
ok, err := auth.DoesSignatureMatch(hex.EncodeToString(dummyPayload[:]))
if err != nil {
errorIf(err.Trace(), "Signature verification failed.", nil)
return ErrInternalError
}
if !ok {
return ErrSignatureDoesNotMatch
}
return ErrNone
return doesSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
} else if isRequestPresignedSignatureV4(r) {
ok, err := auth.DoesPresignedSignatureMatch()
if err != nil {
errorIf(err.Trace(), "Presigned signature verification failed.", nil)
return ErrInternalError
}
if !ok {
return ErrSignatureDoesNotMatch
}
return ErrNone
return doesPresignedSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
}
return ErrAccessDenied
}
+245 -316
View File
@@ -18,7 +18,6 @@ package main
import (
"bytes"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"encoding/xml"
@@ -30,23 +29,18 @@ import (
"strings"
mux "github.com/gorilla/mux"
"github.com/minio/minio/pkg/crypto/sha256"
"github.com/minio/minio/pkg/fs"
"github.com/minio/minio/pkg/probe"
"github.com/minio/minio/pkg/s3/access"
"github.com/minio/minio/pkg/s3/signature4"
)
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
func enforceBucketPolicy(action string, bucket string, reqURL *url.URL) (s3Error APIErrorCode) {
// Read saved bucket policy.
policy, err := readBucketPolicy(bucket)
if err != nil {
errorIf(err.Trace(bucket), "GetBucketPolicy failed.", nil)
switch err.ToGoError().(type) {
case fs.BucketNotFound:
errorIf(err, "Unable read bucket policy.")
switch err.(type) {
case BucketNotFound:
return ErrNoSuchBucket
case fs.BucketNameInvalid:
case BucketNameInvalid:
return ErrInvalidBucketName
default:
// For any other error just return AccessDenied.
@@ -54,23 +48,23 @@ func enforceBucketPolicy(action string, bucket string, reqURL *url.URL) (s3Error
}
}
// Parse the saved policy.
accessPolicy, e := accesspolicy.Validate(policy)
if e != nil {
errorIf(probe.NewError(e), "Parse policy failed.", nil)
bucketPolicy, err := parseBucketPolicy(policy)
if err != nil {
errorIf(err, "Unable to parse bucket policy.")
return ErrAccessDenied
}
// Construct resource in 'arn:aws:s3:::examplebucket' format.
resource := accesspolicy.AWSResourcePrefix + strings.TrimPrefix(reqURL.Path, "/")
// Construct resource in 'arn:aws:s3:::examplebucket/object' format.
resource := AWSResourcePrefix + strings.TrimPrefix(reqURL.Path, "/")
// Get conditions for policy verification.
conditions := make(map[string]string)
for queryParam := range reqURL.Query() {
conditions[queryParam] = reqURL.Query().Get("queryParam")
conditions[queryParam] = reqURL.Query().Get(queryParam)
}
// Validate action, resource and conditions with current policy statements.
if !bucketPolicyEvalStatements(action, resource, conditions, accessPolicy.Statements) {
if !bucketPolicyEvalStatements(action, resource, conditions, bucketPolicy.Statements) {
return ErrAccessDenied
}
return ErrNone
@@ -79,7 +73,7 @@ func enforceBucketPolicy(action string, bucket string, reqURL *url.URL) (s3Error
// GetBucketLocationHandler - GET Bucket location.
// -------------------------
// This operation returns bucket location.
func (api storageAPI) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
@@ -89,40 +83,55 @@ func (api storageAPI) GetBucketLocationHandler(w http.ResponseWriter, r *http.Re
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypeAnonymous:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
if s3Error := enforceBucketPolicy("s3:GetBucketLocation", bucket, r.URL); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypeSigned, authTypePresigned:
if s3Error := isReqAuthenticated(api.Signature, r); s3Error != ErrNone {
payload, err := ioutil.ReadAll(r.Body)
if err != nil {
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
return
}
// Verify Content-Md5, if payload is set.
if r.Header.Get("Content-Md5") != "" {
if r.Header.Get("Content-Md5") != base64.StdEncoding.EncodeToString(sumMD5(payload)) {
writeErrorResponse(w, r, ErrBadDigest, r.URL.Path)
return
}
}
// Populate back the payload.
r.Body = ioutil.NopCloser(bytes.NewReader(payload))
var s3Error APIErrorCode // API error code.
validateRegion := false // Validate region.
if isRequestSignatureV4(r) {
s3Error = doesSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
} else if isRequestPresignedSignatureV4(r) {
s3Error = doesPresignedSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
}
if s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
}
_, err := api.Filesystem.GetBucketMetadata(bucket)
if err != nil {
errorIf(err.Trace(), "GetBucketMetadata failed.", nil)
switch err.ToGoError().(type) {
case fs.BucketNotFound:
writeErrorResponse(w, r, ErrNoSuchBucket, r.URL.Path)
case fs.BucketNameInvalid:
writeErrorResponse(w, r, ErrInvalidBucketName, r.URL.Path)
default:
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
}
if _, err := api.ObjectAPI.GetBucketInfo(bucket); err != nil {
errorIf(err, "Unable to fetch bucket info.")
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
return
}
// Generate response.
encodedSuccessResponse := encodeResponse(LocationResponse{})
if api.Region != "us-east-1" {
// Get current region.
region := serverConfig.GetRegion()
if region != "us-east-1" {
encodedSuccessResponse = encodeResponse(LocationResponse{
Location: api.Region,
Location: region,
})
}
setCommonHeaders(w) // write headers.
setCommonHeaders(w) // Write headers.
writeSuccessResponse(w, encodedSuccessResponse)
}
@@ -134,7 +143,7 @@ func (api storageAPI) GetBucketLocationHandler(w http.ResponseWriter, r *http.Re
// completed or aborted. This operation returns at most 1,000 multipart
// uploads in the response.
//
func (api storageAPI) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) {
func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
@@ -150,34 +159,33 @@ func (api storageAPI) ListMultipartUploadsHandler(w http.ResponseWriter, r *http
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(api.Signature, r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
}
resources := getBucketMultipartResources(r.URL.Query())
if resources.MaxUploads < 0 {
prefix, keyMarker, uploadIDMarker, delimiter, maxUploads, _ := getBucketMultipartResources(r.URL.Query())
if maxUploads < 0 {
writeErrorResponse(w, r, ErrInvalidMaxUploads, r.URL.Path)
return
}
if resources.MaxUploads == 0 {
resources.MaxUploads = maxObjectList
if keyMarker != "" {
// Marker not common with prefix is not implemented.
if !strings.HasPrefix(keyMarker, prefix) {
writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
return
}
}
resources, err := api.Filesystem.ListMultipartUploads(bucket, resources)
listMultipartsInfo, err := api.ObjectAPI.ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads)
if err != nil {
errorIf(err.Trace(), "ListMultipartUploads failed.", nil)
switch err.ToGoError().(type) {
case fs.BucketNotFound:
writeErrorResponse(w, r, ErrNoSuchBucket, r.URL.Path)
default:
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
}
errorIf(err, "Unable to list multipart uploads.")
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
return
}
// generate response
response := generateListMultipartUploadsResponse(bucket, resources)
response := generateListMultipartUploadsResponse(bucket, listMultipartsInfo)
encodedSuccessResponse := encodeResponse(response)
// write headers.
setCommonHeaders(w)
@@ -191,7 +199,7 @@ func (api storageAPI) ListMultipartUploadsHandler(w http.ResponseWriter, r *http
// of the objects in a bucket. You can use the request parameters as selection
// criteria to return a subset of the objects in a bucket.
//
func (api storageAPI) ListObjectsHandler(w http.ResponseWriter, r *http.Request) {
func (api objectAPIHandlers) ListObjectsHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
@@ -201,59 +209,78 @@ func (api storageAPI) ListObjectsHandler(w http.ResponseWriter, r *http.Request)
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypeAnonymous:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
if s3Error := enforceBucketPolicy("s3:ListBucket", bucket, r.URL); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypeSigned, authTypePresigned:
if s3Error := isReqAuthenticated(api.Signature, r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
}
var prefix, marker, token, delimiter, startAfter string
var maxkeys int
var listV2 bool
// TODO handle encoding type.
prefix, marker, delimiter, maxkeys, _ := getBucketResources(r.URL.Query())
if r.URL.Query().Get("list-type") == "2" {
listV2 = true
prefix, token, startAfter, delimiter, maxkeys, _ = getListObjectsV2Args(r.URL.Query())
// For ListV2 "start-after" is considered only if "continuation-token" is empty.
if token == "" {
marker = startAfter
} else {
marker = token
}
} else {
prefix, marker, delimiter, maxkeys, _ = getListObjectsV1Args(r.URL.Query())
}
if maxkeys < 0 {
writeErrorResponse(w, r, ErrInvalidMaxKeys, r.URL.Path)
return
}
if maxkeys == 0 {
maxkeys = maxObjectList
// Verify if delimiter is anything other than '/', which we do not support.
if delimiter != "" && delimiter != "/" {
writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
return
}
// If marker is set unescape.
if marker != "" {
// Marker not common with prefix is not implemented.
if !strings.HasPrefix(marker, prefix) {
writeErrorResponse(w, r, ErrNotImplemented, r.URL.Path)
return
}
}
listResp, err := api.Filesystem.ListObjects(bucket, prefix, marker, delimiter, maxkeys)
listObjectsInfo, err := api.ObjectAPI.ListObjects(bucket, prefix, marker, delimiter, maxkeys)
if err == nil {
var encodedSuccessResponse []byte
// generate response
response := generateListObjectsResponse(bucket, prefix, marker, delimiter, maxkeys, listResp)
encodedSuccessResponse := encodeResponse(response)
if listV2 {
response := generateListObjectsV2Response(bucket, prefix, token, startAfter, delimiter, maxkeys, listObjectsInfo)
encodedSuccessResponse = encodeResponse(response)
} else {
response := generateListObjectsResponse(bucket, prefix, marker, delimiter, maxkeys, listObjectsInfo)
encodedSuccessResponse = encodeResponse(response)
}
// Write headers
setCommonHeaders(w)
// Write success response.
writeSuccessResponse(w, encodedSuccessResponse)
return
}
switch err.ToGoError().(type) {
case fs.BucketNameInvalid:
writeErrorResponse(w, r, ErrInvalidBucketName, r.URL.Path)
case fs.BucketNotFound:
writeErrorResponse(w, r, ErrNoSuchBucket, r.URL.Path)
case fs.ObjectNotFound:
writeErrorResponse(w, r, ErrNoSuchKey, r.URL.Path)
case fs.ObjectNameInvalid:
writeErrorResponse(w, r, ErrNoSuchKey, r.URL.Path)
default:
errorIf(err.Trace(), "ListObjects failed.", nil)
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
}
errorIf(err, "Unable to list objects.")
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
}
// ListBucketsHandler - GET Service
// -----------
// This implementation of the GET operation returns a list of all buckets
// owned by the authenticated sender of the request.
func (api storageAPI) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
// List buckets does not support bucket policies.
switch getRequestAuthType(r) {
default:
@@ -261,16 +288,37 @@ func (api storageAPI) ListBucketsHandler(w http.ResponseWriter, r *http.Request)
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypeSigned, authTypePresigned:
if s3Error := isReqAuthenticated(api.Signature, r); s3Error != ErrNone {
payload, e := ioutil.ReadAll(r.Body)
if e != nil {
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
return
}
// Verify Content-Md5, if payload is set.
if r.Header.Get("Content-Md5") != "" {
if r.Header.Get("Content-Md5") != base64.StdEncoding.EncodeToString(sumMD5(payload)) {
writeErrorResponse(w, r, ErrBadDigest, r.URL.Path)
return
}
}
// Populate back the payload.
r.Body = ioutil.NopCloser(bytes.NewReader(payload))
var s3Error APIErrorCode // API error code.
validateRegion := false // Validate region.
if isRequestSignatureV4(r) {
s3Error = doesSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
} else if isRequestPresignedSignatureV4(r) {
s3Error = doesPresignedSignatureMatch(hex.EncodeToString(sum256(payload)), r, validateRegion)
}
if s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
}
buckets, err := api.Filesystem.ListBuckets()
bucketsInfo, err := api.ObjectAPI.ListBuckets()
if err == nil {
// generate response
response := generateListBucketsResponse(buckets)
response := generateListBucketsResponse(bucketsInfo)
encodedSuccessResponse := encodeResponse(response)
// write headers
setCommonHeaders(w)
@@ -278,15 +326,33 @@ func (api storageAPI) ListBucketsHandler(w http.ResponseWriter, r *http.Request)
writeSuccessResponse(w, encodedSuccessResponse)
return
}
errorIf(err.Trace(), "ListBuckets failed.", nil)
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
errorIf(err, "Unable to list buckets.")
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
}
// DeleteMultipleObjectsHandler - deletes multiple objects.
func (api storageAPI) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Request) {
func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
switch getRequestAuthType(r) {
default:
// For all unknown auth types return error.
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypeAnonymous:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
if s3Error := enforceBucketPolicy("s3:DeleteObject", bucket, r.URL); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
}
// Content-Length is required and should be non-zero
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
if r.ContentLength <= 0 {
@@ -301,69 +367,20 @@ func (api storageAPI) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *htt
return
}
// Set http request for signature.
auth := api.Signature.SetHTTPRequestToVerify(r)
// Allocate incoming content length bytes.
deleteXMLBytes := make([]byte, r.ContentLength)
// Read incoming body XML bytes.
_, e := io.ReadFull(r.Body, deleteXMLBytes)
if e != nil {
errorIf(probe.NewError(e), "DeleteMultipleObjects failed.", nil)
if _, err := io.ReadFull(r.Body, deleteXMLBytes); err != nil {
errorIf(err, "Unable to read HTTP body.")
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
return
}
switch getRequestAuthType(r) {
default:
// For all unknown auth types return error.
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypeAnonymous:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
if s3Error := enforceBucketPolicy("s3:DeleteObject", bucket, r.URL); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresigned:
// Check if request is presigned.
ok, err := auth.DoesPresignedSignatureMatch()
if err != nil {
errorIf(err.Trace(r.URL.String()), "Presigned signature verification failed.", nil)
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
return
}
if !ok {
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
return
}
case authTypeSigned:
// Check if request is signed.
sha := sha256.New()
mdSh := md5.New()
sha.Write(deleteXMLBytes)
mdSh.Write(deleteXMLBytes)
ok, err := auth.DoesSignatureMatch(hex.EncodeToString(sha.Sum(nil)))
if err != nil {
errorIf(err.Trace(), "DeleteMultipleObjects failed.", nil)
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
return
}
if !ok {
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
return
}
// Verify content md5.
if r.Header.Get("Content-Md5") != base64.StdEncoding.EncodeToString(mdSh.Sum(nil)) {
writeErrorResponse(w, r, ErrBadDigest, r.URL.Path)
return
}
}
// Unmarshal list of keys to be deleted.
deleteObjects := &DeleteObjectsRequest{}
if e := xml.Unmarshal(deleteXMLBytes, deleteObjects); e != nil {
if err := xml.Unmarshal(deleteXMLBytes, deleteObjects); err != nil {
errorIf(err, "Unable to unmarshal delete objects request XML.")
writeErrorResponse(w, r, ErrMalformedXML, r.URL.Path)
return
}
@@ -372,45 +389,18 @@ func (api storageAPI) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *htt
var deletedObjects []ObjectIdentifier
// Loop through all the objects and delete them sequentially.
for _, object := range deleteObjects.Objects {
err := api.Filesystem.DeleteObject(bucket, object.ObjectName)
err := api.ObjectAPI.DeleteObject(bucket, object.ObjectName)
if err == nil {
deletedObjects = append(deletedObjects, ObjectIdentifier{
ObjectName: object.ObjectName,
})
} else {
errorIf(err.Trace(object.ObjectName), "DeleteObject failed.", nil)
switch err.ToGoError().(type) {
case fs.BucketNameInvalid:
deleteErrors = append(deleteErrors, DeleteError{
Code: errorCodeResponse[ErrInvalidBucketName].Code,
Message: errorCodeResponse[ErrInvalidBucketName].Description,
Key: object.ObjectName,
})
case fs.BucketNotFound:
deleteErrors = append(deleteErrors, DeleteError{
Code: errorCodeResponse[ErrNoSuchBucket].Code,
Message: errorCodeResponse[ErrNoSuchBucket].Description,
Key: object.ObjectName,
})
case fs.ObjectNotFound:
deleteErrors = append(deleteErrors, DeleteError{
Code: errorCodeResponse[ErrNoSuchKey].Code,
Message: errorCodeResponse[ErrNoSuchKey].Description,
Key: object.ObjectName,
})
case fs.ObjectNameInvalid:
deleteErrors = append(deleteErrors, DeleteError{
Code: errorCodeResponse[ErrNoSuchKey].Code,
Message: errorCodeResponse[ErrNoSuchKey].Description,
Key: object.ObjectName,
})
default:
deleteErrors = append(deleteErrors, DeleteError{
Code: errorCodeResponse[ErrInternalError].Code,
Message: errorCodeResponse[ErrInternalError].Description,
Key: object.ObjectName,
})
}
errorIf(err, "Unable to delete object.")
deleteErrors = append(deleteErrors, DeleteError{
Code: errorCodeResponse[toAPIErrorCode(err)].Code,
Message: errorCodeResponse[toAPIErrorCode(err)].Description,
Key: object.ObjectName,
})
}
}
// Generate response
@@ -425,68 +415,36 @@ func (api storageAPI) DeleteMultipleObjectsHandler(w http.ResponseWriter, r *htt
// PutBucketHandler - PUT Bucket
// ----------
// This implementation of the PUT operation creates a new bucket for authenticated request
func (api storageAPI) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
// Set http request for signature.
auth := api.Signature.SetHTTPRequestToVerify(r)
switch getRequestAuthType(r) {
default:
// For all unknown auth types return error.
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypeAnonymous:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
if s3Error := enforceBucketPolicy("s3:CreateBucket", bucket, r.URL); s3Error != ErrNone {
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresigned:
ok, err := auth.DoesPresignedSignatureMatch()
if err != nil {
errorIf(err.Trace(r.URL.String()), "Presigned signature verification failed.", nil)
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
return
}
if !ok {
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
return
}
case authTypeSigned:
// Verify signature for the incoming body if any.
locationBytes, e := ioutil.ReadAll(r.Body)
if e != nil {
errorIf(probe.NewError(e), "MakeBucket failed.", nil)
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
return
}
sh := sha256.New()
sh.Write(locationBytes)
ok, err := auth.DoesSignatureMatch(hex.EncodeToString(sh.Sum(nil)))
if err != nil {
errorIf(err.Trace(), "MakeBucket failed.", nil)
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
return
}
if !ok {
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
return
}
}
// the location value in the request body should match the Region in serverConfig.
// other values of location are not accepted.
// make bucket fails in such cases.
errCode := isValidLocationContraint(r.Body, serverConfig.GetRegion())
if errCode != ErrNone {
writeErrorResponse(w, r, errCode, r.URL.Path)
return
}
// Make bucket.
err := api.Filesystem.MakeBucket(bucket)
err := api.ObjectAPI.MakeBucket(bucket)
if err != nil {
errorIf(err.Trace(), "MakeBucket failed.", nil)
switch err.ToGoError().(type) {
case fs.BucketNameInvalid:
writeErrorResponse(w, r, ErrInvalidBucketName, r.URL.Path)
case fs.BucketExists:
writeErrorResponse(w, r, ErrBucketAlreadyExists, r.URL.Path)
default:
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
}
errorIf(err, "Unable to create a bucket.")
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
return
}
// Make sure to add Location information here only for bucket
@@ -494,24 +452,25 @@ func (api storageAPI) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
writeSuccessResponse(w, nil)
}
func extractHTTPFormValues(reader *multipart.Reader) (io.Reader, map[string]string, *probe.Error) {
func extractHTTPFormValues(reader *multipart.Reader) (io.Reader, map[string]string, error) {
/// HTML Form values
formValues := make(map[string]string)
filePart := new(bytes.Buffer)
var e error
for e == nil {
var err error
for err == nil {
var part *multipart.Part
part, e = reader.NextPart()
part, err = reader.NextPart()
if part != nil {
if part.FileName() == "" {
buffer, e := ioutil.ReadAll(part)
if e != nil {
return nil, nil, probe.NewError(e)
var buffer []byte
buffer, err = ioutil.ReadAll(part)
if err != nil {
return nil, nil, err
}
formValues[http.CanonicalHeaderKey(part.FormName())] = string(buffer)
} else {
if _, e := io.Copy(filePart, part); e != nil {
return nil, nil, probe.NewError(e)
if _, err = io.Copy(filePart, part); err != nil {
return nil, nil, err
}
}
}
@@ -523,72 +482,58 @@ func extractHTTPFormValues(reader *multipart.Reader) (io.Reader, map[string]stri
// ----------
// This implementation of the POST operation handles object creation with a specified
// signature policy in multipart/form-data
func (api storageAPI) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Request) {
func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Request) {
// Here the parameter is the size of the form data that should
// be loaded in memory, the remaining being put in temporary
// files
reader, e := r.MultipartReader()
if e != nil {
errorIf(probe.NewError(e), "Unable to initialize multipart reader.", nil)
// be loaded in memory, the remaining being put in temporary files.
reader, err := r.MultipartReader()
if err != nil {
errorIf(err, "Unable to initialize multipart reader.")
writeErrorResponse(w, r, ErrMalformedPOSTRequest, r.URL.Path)
return
}
fileBody, formValues, err := extractHTTPFormValues(reader)
if err != nil {
errorIf(err.Trace(), "Unable to parse form values.", nil)
errorIf(err, "Unable to parse form values.")
writeErrorResponse(w, r, ErrMalformedPOSTRequest, r.URL.Path)
return
}
bucket := mux.Vars(r)["bucket"]
formValues["Bucket"] = bucket
object := formValues["Key"]
var ok bool
// Set http request for signature.
auth := api.Signature.SetHTTPRequestToVerify(r)
// Verify policy signature.
ok, err = auth.DoesPolicySignatureMatch(formValues)
apiErr := doesPolicySignatureMatch(formValues)
if apiErr != ErrNone {
writeErrorResponse(w, r, apiErr, r.URL.Path)
return
}
if apiErr = checkPostPolicy(formValues); apiErr != ErrNone {
writeErrorResponse(w, r, apiErr, r.URL.Path)
return
}
// Save metadata.
metadata := make(map[string]string)
// Nothing to store right now.
md5Sum, err := api.ObjectAPI.PutObject(bucket, object, -1, fileBody, metadata)
if err != nil {
errorIf(err.Trace(), "Unable to verify signature.", nil)
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
errorIf(err, "Unable to create object.")
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
return
}
if !ok {
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
return
if md5Sum != "" {
w.Header().Set("ETag", "\""+md5Sum+"\"")
}
if err = signature4.ApplyPolicyCond(formValues); err != nil {
errorIf(err.Trace(), "Invalid request, policy doesn't match with the endpoint.", nil)
writeErrorResponse(w, r, ErrMalformedPOSTRequest, r.URL.Path)
return
}
metadata, err := api.Filesystem.CreateObject(bucket, object, "", -1, fileBody, nil)
if err != nil {
errorIf(err.Trace(), "CreateObject failed.", nil)
switch err.ToGoError().(type) {
case fs.RootPathFull:
writeErrorResponse(w, r, ErrRootPathFull, r.URL.Path)
case fs.BucketNotFound:
writeErrorResponse(w, r, ErrNoSuchBucket, r.URL.Path)
case fs.BucketNameInvalid:
writeErrorResponse(w, r, ErrInvalidBucketName, r.URL.Path)
case fs.BadDigest:
writeErrorResponse(w, r, ErrBadDigest, r.URL.Path)
case fs.IncompleteBody:
writeErrorResponse(w, r, ErrIncompleteBody, r.URL.Path)
case fs.InvalidDigest:
writeErrorResponse(w, r, ErrInvalidDigest, r.URL.Path)
default:
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
}
return
}
if metadata.MD5 != "" {
w.Header().Set("ETag", "\""+metadata.MD5+"\"")
}
writeSuccessResponse(w, nil)
encodedSuccessResponse := encodeResponse(PostResponse{
Location: getObjectLocation(bucket, object), // TODO Full URL is preferred
Bucket: bucket,
Key: object,
ETag: md5Sum,
})
setCommonHeaders(w)
writeSuccessResponse(w, encodedSuccessResponse)
}
// HeadBucketHandler - HEAD Bucket
@@ -597,40 +542,7 @@ func (api storageAPI) PostPolicyBucketHandler(w http.ResponseWriter, r *http.Req
// The operation returns a 200 OK if the bucket exists and you
// have permission to access it. Otherwise, the operation might
// return responses such as 404 Not Found and 403 Forbidden.
func (api storageAPI) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
switch getRequestAuthType(r) {
default:
// For all unknown auth types return error.
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(api.Signature, r); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
}
_, err := api.Filesystem.GetBucketMetadata(bucket)
if err != nil {
errorIf(err.Trace(), "GetBucketMetadata failed.", nil)
switch err.ToGoError().(type) {
case fs.BucketNotFound:
writeErrorResponse(w, r, ErrNoSuchBucket, r.URL.Path)
case fs.BucketNameInvalid:
writeErrorResponse(w, r, ErrInvalidBucketName, r.URL.Path)
default:
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
}
return
}
writeSuccessResponse(w, nil)
}
// DeleteBucketHandler - Delete bucket
func (api storageAPI) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
@@ -640,29 +552,46 @@ func (api storageAPI) DeleteBucketHandler(w http.ResponseWriter, r *http.Request
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypeAnonymous:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuAndPermissions.html
if s3Error := enforceBucketPolicy("s3:DeleteBucket", bucket, r.URL); s3Error != ErrNone {
// http://docs.aws.amazon.com/AmazonS3/latest/dev/using-with-s3-actions.html
if s3Error := enforceBucketPolicy("s3:ListBucket", bucket, r.URL); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(api.Signature, r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
}
err := api.Filesystem.DeleteBucket(bucket)
if err != nil {
errorIf(err.Trace(), "DeleteBucket failed.", nil)
switch err.ToGoError().(type) {
case fs.BucketNotFound:
writeErrorResponse(w, r, ErrNoSuchBucket, r.URL.Path)
case fs.BucketNotEmpty:
writeErrorResponse(w, r, ErrBucketNotEmpty, r.URL.Path)
default:
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
if _, err := api.ObjectAPI.GetBucketInfo(bucket); err != nil {
errorIf(err, "Unable to fetch bucket info.")
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
return
}
writeSuccessResponse(w, nil)
}
// DeleteBucketHandler - Delete bucket
func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
switch getRequestAuthType(r) {
default:
// For all unknown auth types return error.
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
}
if err := api.ObjectAPI.DeleteBucket(bucket); err != nil {
errorIf(err, "Unable to delete a bucket.")
writeErrorResponse(w, r, toAPIErrorCode(err), r.URL.Path)
return
}
+100 -86
View File
@@ -18,8 +18,6 @@ package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"io"
"io/ioutil"
"net/http"
@@ -27,9 +25,6 @@ import (
"strings"
mux "github.com/gorilla/mux"
"github.com/minio/minio/pkg/fs"
"github.com/minio/minio/pkg/probe"
"github.com/minio/minio/pkg/s3/access"
)
// maximum supported access policy size.
@@ -37,12 +32,13 @@ const maxAccessPolicySize = 20 * 1024 * 1024 // 20KiB.
// Verify if a given action is valid for the url path based on the
// existing bucket access policy.
func bucketPolicyEvalStatements(action string, resource string, conditions map[string]string, statements []accesspolicy.Statement) bool {
func bucketPolicyEvalStatements(action string, resource string, conditions map[string]string, statements []policyStatement) bool {
for _, statement := range statements {
if bucketPolicyMatchStatement(action, resource, conditions, statement) {
if statement.Effect == "Allow" {
return true
}
// Do not uncomment kept here for readability.
// else statement.Effect == "Deny"
return false
}
@@ -52,7 +48,7 @@ func bucketPolicyEvalStatements(action string, resource string, conditions map[s
}
// Verify if action, resource and conditions match input policy statement.
func bucketPolicyMatchStatement(action string, resource string, conditions map[string]string, statement accesspolicy.Statement) bool {
func bucketPolicyMatchStatement(action string, resource string, conditions map[string]string, statement policyStatement) bool {
// Verify if action matches.
if bucketPolicyActionMatch(action, statement) {
// Verify if resource matches.
@@ -67,11 +63,11 @@ func bucketPolicyMatchStatement(action string, resource string, conditions map[s
}
// Verify if given action matches with policy statement.
func bucketPolicyActionMatch(action string, statement accesspolicy.Statement) bool {
func bucketPolicyActionMatch(action string, statement policyStatement) bool {
for _, policyAction := range statement.Actions {
// Policy action can be a regex, validate the action with matching string.
matched, e := regexp.MatchString(policyAction, action)
fatalIf(probe.NewError(e), "Invalid pattern, please verify the pattern string.", nil)
matched, err := regexp.MatchString(policyAction, action)
fatalIf(err, "Invalid action \"%s\" in bucket policy.", action)
if matched {
return true
}
@@ -79,21 +75,49 @@ func bucketPolicyActionMatch(action string, statement accesspolicy.Statement) bo
return false
}
// Match function matches wild cards in 'pattern' for resource.
func resourceMatch(pattern, resource string) bool {
if pattern == "" {
return resource == pattern
}
if pattern == "*" {
return true
}
parts := strings.Split(pattern, "*")
if len(parts) == 1 {
return resource == pattern
}
tGlob := strings.HasSuffix(pattern, "*")
end := len(parts) - 1
if !strings.HasPrefix(resource, parts[0]) {
return false
}
for i := 1; i < end; i++ {
if !strings.Contains(resource, parts[i]) {
return false
}
idx := strings.Index(resource, parts[i]) + len(parts[i])
resource = resource[idx:]
}
return tGlob || strings.HasSuffix(resource, parts[end])
}
// Verify if given resource matches with policy statement.
func bucketPolicyResourceMatch(resource string, statement accesspolicy.Statement) bool {
for _, presource := range statement.Resources {
matched, e := regexp.MatchString(presource, strings.TrimPrefix(resource, "/"))
fatalIf(probe.NewError(e), "Invalid pattern, please verify the pattern string.", nil)
// For any path matches, we return quickly and the let the caller continue.
if matched {
return true
func bucketPolicyResourceMatch(resource string, statement policyStatement) bool {
for _, resourcep := range statement.Resources {
// the resource rule for object could contain "*" wild card.
// the requested object can be given access based on the already set bucket policy if
// the match is successful.
// More info: http://docs.aws.amazon.com/AmazonS3/latest/dev/s3-arn-format.html .
if matched := resourceMatch(resourcep, resource); !matched {
return false
}
}
return false
return true
}
// Verify if given condition matches with policy statement.
func bucketPolicyConditionMatch(conditions map[string]string, statement accesspolicy.Statement) bool {
func bucketPolicyConditionMatch(conditions map[string]string, statement policyStatement) bool {
// Supports following conditions.
// - StringEquals
// - StringNotEquals
@@ -130,10 +154,22 @@ func bucketPolicyConditionMatch(conditions map[string]string, statement accesspo
// -----------------
// This implementation of the PUT operation uses the policy
// subresource to add to or replace a policy on a bucket
func (api storageAPI) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
switch getRequestAuthType(r) {
default:
// For all unknown auth types return error.
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
}
// If Content-Length is unknown or zero, deny the
// request. PutBucketPolicy always needs a Content-Length if
// incoming request is not chunked.
@@ -152,65 +188,32 @@ func (api storageAPI) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Requ
// Read access policy up to maxAccessPolicySize.
// http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
// bucket policies are limited to 20KB in size, using a limit reader.
accessPolicyBytes, e := ioutil.ReadAll(io.LimitReader(r.Body, maxAccessPolicySize))
if e != nil {
errorIf(probe.NewError(e).Trace(bucket), "Reading policy failed.", nil)
bucketPolicyBuf, err := ioutil.ReadAll(io.LimitReader(r.Body, maxAccessPolicySize))
if err != nil {
errorIf(err, "Unable to read bucket policy.")
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
return
}
// Parse access access.
accessPolicy, e := accesspolicy.Validate(accessPolicyBytes)
if e != nil {
// Parse bucket policy.
bucketPolicy, err := parseBucketPolicy(bucketPolicyBuf)
if err != nil {
errorIf(err, "Unable to parse bucket policy.")
writeErrorResponse(w, r, ErrInvalidPolicyDocument, r.URL.Path)
return
}
// If the policy resource has different bucket name, reject it.
for _, statement := range accessPolicy.Statements {
for _, resource := range statement.Resources {
resourcePrefix := strings.SplitAfter(resource, accesspolicy.AWSResourcePrefix)[1]
if !strings.HasPrefix(resourcePrefix, bucket) {
writeErrorResponse(w, r, ErrMalformedPolicy, r.URL.Path)
return
}
}
}
// Set http request for signature verification.
auth := api.Signature.SetHTTPRequestToVerify(r)
if isRequestPresignedSignatureV4(r) {
ok, err := auth.DoesPresignedSignatureMatch()
if err != nil {
errorIf(err.Trace(r.URL.String()), "Presigned signature verification failed.", nil)
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
return
}
if !ok {
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
return
}
} else if isRequestSignatureV4(r) {
sh := sha256.New()
sh.Write(accessPolicyBytes)
ok, err := api.Signature.DoesSignatureMatch(hex.EncodeToString(sh.Sum(nil)))
if err != nil {
errorIf(err.Trace(string(accessPolicyBytes)), "SaveBucketPolicy failed.", nil)
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
return
}
if !ok {
writeErrorResponse(w, r, ErrSignatureDoesNotMatch, r.URL.Path)
return
}
// Parse check bucket policy.
if s3Error := checkBucketPolicyResources(bucket, bucketPolicy); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
// Save bucket policy.
err := writeBucketPolicy(bucket, accessPolicyBytes)
if err != nil {
errorIf(err.Trace(bucket), "SaveBucketPolicy failed.", nil)
switch err.ToGoError().(type) {
case fs.BucketNameInvalid:
if err := writeBucketPolicy(bucket, bucketPolicyBuf); err != nil {
errorIf(err, "Unable to write bucket policy.")
switch err.(type) {
case BucketNameInvalid:
writeErrorResponse(w, r, ErrInvalidBucketName, r.URL.Path)
default:
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
@@ -224,24 +227,29 @@ func (api storageAPI) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Requ
// -----------------
// This implementation of the DELETE operation uses the policy
// subresource to add to remove a policy on a bucket.
func (api storageAPI) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
// Validate incoming signature.
if s3Error := isReqAuthenticated(api.Signature, r); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
switch getRequestAuthType(r) {
default:
// For all unknown auth types return error.
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
}
// Delete bucket access policy.
err := removeBucketPolicy(bucket)
if err != nil {
errorIf(err.Trace(bucket), "DeleteBucketPolicy failed.", nil)
switch err.ToGoError().(type) {
case fs.BucketNameInvalid:
if err := removeBucketPolicy(bucket); err != nil {
errorIf(err, "Unable to remove bucket policy.")
switch err.(type) {
case BucketNameInvalid:
writeErrorResponse(w, r, ErrInvalidBucketName, r.URL.Path)
case fs.BucketPolicyNotFound:
case BucketPolicyNotFound:
writeErrorResponse(w, r, ErrNoSuchBucketPolicy, r.URL.Path)
default:
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
@@ -255,24 +263,30 @@ func (api storageAPI) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.R
// -----------------
// This operation uses the policy
// subresource to return the policy of a specified bucket.
func (api storageAPI) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bucket := vars["bucket"]
// Validate incoming signature.
if s3Error := isReqAuthenticated(api.Signature, r); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
switch getRequestAuthType(r) {
default:
// For all unknown auth types return error.
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
}
// Read bucket access policy.
p, err := readBucketPolicy(bucket)
if err != nil {
errorIf(err.Trace(bucket), "GetBucketPolicy failed.", nil)
switch err.ToGoError().(type) {
case fs.BucketNameInvalid:
errorIf(err, "Unable to read bucket policy.")
switch err.(type) {
case BucketNameInvalid:
writeErrorResponse(w, r, ErrInvalidBucketName, r.URL.Path)
case fs.BucketPolicyNotFound:
case BucketPolicyNotFound:
writeErrorResponse(w, r, ErrNoSuchBucketPolicy, r.URL.Path)
default:
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
+78
View File
@@ -0,0 +1,78 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* 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 main
import (
"fmt"
"testing"
)
// Tests validate Bucket policy resource matcher.
func TestBucketPolicyResourceMatch(t *testing.T) {
// generates\ statement with given resource..
generateStatement := func(resource string) policyStatement {
statement := policyStatement{}
statement.Resources = []string{resource}
return statement
}
// generates resource prefix.
generateResource := func(bucketName, objectName string) string {
return AWSResourcePrefix + bucketName + "/" + objectName
}
testCases := []struct {
resourceToMatch string
statement policyStatement
expectedResourceMatch bool
}{
// Test case 1-4.
// Policy with resource ending with bucket/* allows access to all objects inside the given bucket.
{generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/*")), true},
{generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/*")), true},
{generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/*")), true},
{generateResource("minio-bucket", ""), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/*")), true},
// Test case - 5.
// Policy with resource ending with bucket/oo* should not allow access to bucket/output.txt.
{generateResource("minio-bucket", "output.txt"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/oo*")), false},
// Test case - 6.
// Policy with resource ending with bucket/oo* should allow access to bucket/ootput.txt.
{generateResource("minio-bucket", "ootput.txt"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/oo*")), true},
// Test case - 7.
// Policy with resource ending with bucket/oo* allows access to all subfolders starting with "oo" inside given bucket.
{generateResource("minio-bucket", "oop-bucket/my-file"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/oo*")), true},
// Test case - 8.
{generateResource("minio-bucket", "Asia/India/1.pjg"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/Asia/Japan/*")), false},
// Test case - 9.
{generateResource("minio-bucket", "Asia/India/1.pjg"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix, "minio-bucket"+"/Asia/Japan/*")), false},
// Test case - 10.
// Proves that the name space is flat.
{generateResource("minio-bucket", "Africa/Bihar/India/design_info.doc/Bihar"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix,
"minio-bucket"+"/*/India/*/Bihar")), true},
// Test case - 11.
// Proves that the name space is flat.
{generateResource("minio-bucket", "Asia/China/India/States/Bihar/output.txt"), generateStatement(fmt.Sprintf("%s%s", AWSResourcePrefix,
"minio-bucket"+"/*/India/*/Bihar/*")), true},
}
for i, testCase := range testCases {
actualResourceMatch := bucketPolicyResourceMatch(testCase.resourceToMatch, testCase.statement)
if testCase.expectedResourceMatch != actualResourceMatch {
t.Errorf("Test %d: Expected Resource match to be `%v`, but instead found it to be `%v`", i+1, testCase.expectedResourceMatch, actualResourceMatch)
}
}
}
@@ -14,14 +14,16 @@
* limitations under the License.
*/
// Package accesspolicy implements AWS Access Policy Language parser in
// This file implements AWS Access Policy Language parser in
// accordance with http://docs.aws.amazon.com/AmazonS3/latest/dev/access-policy-language-overview.html
package accesspolicy
package main
import (
"encoding/json"
"errors"
"fmt"
"path"
"sort"
"strings"
)
@@ -32,30 +34,39 @@ const (
// supportedActionMap - lists all the actions supported by minio.
var supportedActionMap = map[string]struct{}{
"*": {},
"s3:*": {},
"s3:GetObject": {},
"s3:ListBucket": {},
"s3:PutObject": {},
"s3:CreateBucket": {},
"s3:GetBucketLocation": {},
"s3:DeleteBucket": {},
"s3:DeleteObject": {},
"s3:AbortMultipartUpload": {},
"s3:ListBucketMultipartUploads": {},
"s3:ListMultipartUploadParts": {},
}
// supported Conditions type.
var supportedConditionsType = map[string]struct{}{
"StringEquals": {},
"StringNotEquals": {},
}
// Validate s3:prefix, s3:max-keys are present if not
// supported keys for the conditions.
var supportedConditionsKey = map[string]struct{}{
"s3:prefix": {},
"s3:max-keys": {},
}
// User - canonical users list.
type User struct {
type policyUser struct {
AWS []string
}
// Statement - minio policy statement
type Statement struct {
type policyStatement struct {
Sid string
Effect string
Principal User
Principal policyUser `json:"Principal"`
Actions []string `json:"Action"`
Resources []string `json:"Resource"`
Conditions map[string]map[string]string `json:"Condition"`
@@ -63,8 +74,8 @@ type Statement struct {
// BucketPolicy - minio policy collection
type BucketPolicy struct {
Version string // date in 0000-00-00 format
Statements []Statement `json:"Statement"`
Version string // date in 0000-00-00 format
Statements []policyStatement `json:"Statement"`
}
// supportedEffectMap - supported effects.
@@ -132,60 +143,130 @@ func isValidPrincipals(principals []string) (err error) {
err = errors.New("Principal cannot be empty.")
return err
}
var ok bool
for _, principal := range principals {
// Minio does not support or implement IAM, "*" is the only valid value.
if principal == "*" {
ok = true
continue
}
ok = false
}
if !ok {
err = errors.New("Unsupported principal style found: " + strings.Join(principals, " ") + ", please validate your policy document.")
return err
}
return nil
}
func isValidConditions(conditions map[string]map[string]string) (err error) {
// Verify conditions should be valid.
if len(conditions) > 0 {
// Validate if stringEquals, stringNotEquals are present
// if not throw an error.
_, stringEqualsOK := conditions["StringEquals"]
_, stringNotEqualsOK := conditions["StringNotEquals"]
if !stringEqualsOK && !stringNotEqualsOK {
err = fmt.Errorf("Unsupported condition type found: %s, please validate your policy document.", conditions)
// Amazon s3 doc on principals: http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html#Principal
if principal != "*" {
err = fmt.Errorf("Unsupported principal style found: %s, please validate your policy document.", principal)
return err
}
// Validate s3:prefix, s3:max-keys are present if not
// throw an error.
if len(conditions["StringEquals"]) > 0 {
_, s3PrefixOK := conditions["StringEquals"]["s3:prefix"]
_, s3MaxKeysOK := conditions["StringEquals"]["s3:max-keys"]
if !s3PrefixOK && !s3MaxKeysOK {
err = fmt.Errorf("Unsupported condition keys found: %s, please validate your policy document.",
conditions["StringEquals"])
return err
}
return nil
}
// isValidConditions - are valid conditions.
func isValidConditions(conditions map[string]map[string]string) (err error) {
// Returns true if string 'a' is found in the list.
findString := func(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
if len(conditions["StringNotEquals"]) > 0 {
_, s3PrefixOK := conditions["StringNotEquals"]["s3:prefix"]
_, s3MaxKeysOK := conditions["StringNotEquals"]["s3:max-keys"]
if !s3PrefixOK && !s3MaxKeysOK {
err = fmt.Errorf("Unsupported condition keys found: %s, please validate your policy document.",
conditions["StringNotEquals"])
return false
}
conditionKeyVal := make(map[string][]string)
// Verify conditions should be valid.
// Validate if stringEquals, stringNotEquals are present
// if not throw an error.
for conditionType := range conditions {
_, validType := supportedConditionsType[conditionType]
if !validType {
err = fmt.Errorf("Unsupported condition type '%s', please validate your policy document.", conditionType)
return err
}
for key := range conditions[conditionType] {
_, validKey := supportedConditionsKey[key]
if !validKey {
err = fmt.Errorf("Unsupported condition key '%s', please validate your policy document.", conditionType)
return err
}
conditionArray, ok := conditionKeyVal[key]
if ok && findString(conditions[conditionType][key], conditionArray) {
err = fmt.Errorf("Ambigious condition values for key '%s', please validate your policy document.", key)
return err
}
conditionKeyVal[key] = append(conditionKeyVal[key], conditions[conditionType][key])
}
}
return nil
}
// Validate - validate if request body is of proper JSON and in
// accordance with policy standards.
func Validate(bucketPolicyBuf []byte) (policy BucketPolicy, err error) {
// List of actions for which prefixes are not allowed.
var invalidPrefixActions = map[string]struct{}{
"s3:GetBucketLocation": {},
"s3:ListBucket": {},
"s3:ListBucketMultipartUploads": {},
// Add actions which do not honor prefixes.
}
// resourcePrefix - provides the prefix removing any wildcards.
func resourcePrefix(resource string) string {
if strings.HasSuffix(resource, "*") {
resource = strings.TrimSuffix(resource, "*")
}
return path.Clean(resource)
}
// checkBucketPolicyResources validates Resources in unmarshalled bucket policy structure.
// First valation of Resources done for given set of Actions.
// Later its validated for recursive Resources.
func checkBucketPolicyResources(bucket string, bucketPolicy BucketPolicy) APIErrorCode {
// Validate statements for special actions and collect resources
// for others to validate nesting.
var resourceMap = make(map[string]struct{})
for _, statement := range bucketPolicy.Statements {
for _, action := range statement.Actions {
for _, resource := range statement.Resources {
resourcePrefix := strings.SplitAfter(resource, AWSResourcePrefix)[1]
if _, ok := invalidPrefixActions[action]; ok {
// Resource prefix is not equal to bucket for
// prefix invalid actions, reject them.
if resourcePrefix != bucket {
return ErrMalformedPolicy
}
} else {
// For all other actions validate if resourcePrefix begins
// with bucket name, if not reject them.
if strings.Split(resourcePrefix, "/")[0] != bucket {
return ErrMalformedPolicy
}
// All valid resources collect them separately to verify nesting.
resourceMap[resourcePrefix] = struct{}{}
}
}
}
}
var resources []string
for resource := range resourceMap {
resources = append(resources, resourcePrefix(resource))
}
// Sort strings as shorter first.
sort.Strings(resources)
for len(resources) > 1 {
var resource string
resource, resources = resources[0], resources[1:]
// Loop through all resources, if one of them matches with
// previous shorter one, it means we have detected
// nesting. Reject such rules.
for _, otherResource := range resources {
// Common prefix reject such rules.
if strings.HasPrefix(otherResource, resource) {
return ErrMalformedPolicy
}
}
}
// No errors found.
return ErrNone
}
// parseBucketPolicy - parses and validates if bucket policy is of
// proper JSON and follows allowed restrictions with policy standards.
func parseBucketPolicy(bucketPolicyBuf []byte) (policy BucketPolicy, err error) {
if err = json.Unmarshal(bucketPolicyBuf, &policy); err != nil {
return BucketPolicy{}, err
}
@@ -216,7 +297,7 @@ func Validate(bucketPolicyBuf []byte) (policy BucketPolicy, err error) {
if err := isValidActions(statement.Actions); err != nil {
return BucketPolicy{}, err
}
// Statment resources should be valid.
// Statement resources should be valid.
if err := isValidResources(statement.Resources); err != nil {
return BucketPolicy{}, err
}
@@ -225,6 +306,23 @@ func Validate(bucketPolicyBuf []byte) (policy BucketPolicy, err error) {
return BucketPolicy{}, err
}
}
// Separate deny and allow statements, so that we can apply deny
// statements in the beginning followed by Allow statements.
var denyStatements []policyStatement
var allowStatements []policyStatement
for _, statement := range policy.Statements {
if statement.Effect == "Deny" {
denyStatements = append(denyStatements, statement)
continue
}
// else if statement.Effect == "Allow"
allowStatements = append(allowStatements, statement)
}
// Deny statements are enforced first once matched.
policy.Statements = append(denyStatements, allowStatements...)
// Return successfully parsed policy structure.
return policy, nil
}
+661
View File
@@ -0,0 +1,661 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* 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 main
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"testing"
)
var (
readWriteBucketActions = []string{
"s3:GetBucketLocation",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
// Add more bucket level read-write actions here.
}
readWriteObjectActions = []string{
"s3:AbortMultipartUpload",
"s3:DeleteObject",
"s3:GetObject",
"s3:ListMultipartUploadParts",
"s3:PutObject",
// Add more object level read-write actions here.
}
)
// Write only actions.
var (
writeOnlyBucketActions = []string{
"s3:GetBucketLocation",
"s3:ListBucketMultipartUploads",
// Add more bucket level write actions here.
}
writeOnlyObjectActions = []string{
"s3:AbortMultipartUpload",
"s3:DeleteObject",
"s3:ListMultipartUploadParts",
"s3:PutObject",
// Add more object level write actions here.
}
)
// Read only actions.
var (
readOnlyBucketActions = []string{
"s3:GetBucketLocation",
"s3:ListBucket",
// Add more bucket level read actions here.
}
readOnlyObjectActions = []string{
"s3:GetObject",
// Add more object level read actions here.
}
)
// Obtain statements for read-write BucketPolicy.
func setReadWriteStatement(bucketName, objectPrefix string) []policyStatement {
bucketResourceStatement := policyStatement{}
objectResourceStatement := policyStatement{}
statements := []policyStatement{}
bucketResourceStatement.Effect = "Allow"
bucketResourceStatement.Principal.AWS = []string{"*"}
bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}
bucketResourceStatement.Actions = readWriteBucketActions
objectResourceStatement.Effect = "Allow"
objectResourceStatement.Principal.AWS = []string{"*"}
objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}
objectResourceStatement.Actions = readWriteObjectActions
// Save the read write policy.
statements = append(statements, bucketResourceStatement, objectResourceStatement)
return statements
}
// Obtain statements for read only BucketPolicy.
func setReadOnlyStatement(bucketName, objectPrefix string) []policyStatement {
bucketResourceStatement := policyStatement{}
objectResourceStatement := policyStatement{}
statements := []policyStatement{}
bucketResourceStatement.Effect = "Allow"
bucketResourceStatement.Principal.AWS = []string{"*"}
bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}
bucketResourceStatement.Actions = readOnlyBucketActions
objectResourceStatement.Effect = "Allow"
objectResourceStatement.Principal.AWS = []string{"*"}
objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}
objectResourceStatement.Actions = readOnlyObjectActions
// Save the read only policy.
statements = append(statements, bucketResourceStatement, objectResourceStatement)
return statements
}
// Obtain statements for write only BucketPolicy.
func setWriteOnlyStatement(bucketName, objectPrefix string) []policyStatement {
bucketResourceStatement := policyStatement{}
objectResourceStatement := policyStatement{}
statements := []policyStatement{}
// Write only policy.
bucketResourceStatement.Effect = "Allow"
bucketResourceStatement.Principal.AWS = []string{"*"}
bucketResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName)}
bucketResourceStatement.Actions = writeOnlyBucketActions
objectResourceStatement.Effect = "Allow"
objectResourceStatement.Principal.AWS = []string{"*"}
objectResourceStatement.Resources = []string{fmt.Sprintf("%s%s", AWSResourcePrefix, bucketName+"/"+objectPrefix+"*")}
objectResourceStatement.Actions = writeOnlyObjectActions
// Save the write only policy.
statements = append(statements, bucketResourceStatement, objectResourceStatement)
return statements
}
// Tests validate Action validator.
func TestIsValidActions(t *testing.T) {
testCases := []struct {
// input.
actions []string
// expected output.
err error
// flag indicating whether the test should pass.
shouldPass bool
}{
// Inputs with unsupported Action.
// Test case - 1.
// "s3:ListObject" is an invalid Action.
{[]string{"s3:GetObject", "s3:ListObject", "s3:RemoveObject"}, errors.New("Unsupported action found: s3:ListObject, please validate your policy document."), false},
// Test case - 2.
// Empty Actions.
{[]string{}, errors.New("Action list cannot be empty."), false},
// Test case - 3.
// "s3:DeleteEverything"" is an invalid Action.
{[]string{"s3:GetObject", "s3:ListBucket", "s3:PutObject", "s3:DeleteEverything"}, errors.New("Unsupported action found: s3:DeleteEverything, please validate your policy document."), false},
// Inputs with valid Action.
// Test Case - 4.
{[]string{"s3:GetObject", "s3:ListBucket", "s3:PutObject", "s3:GetBucketLocation", "s3:DeleteObject", "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts"}, nil, true},
}
for i, testCase := range testCases {
err := isValidActions(testCase.actions)
if err != nil && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
}
if err == nil && !testCase.shouldPass {
t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
}
// Failed as expected, but does it fail for the expected reason.
if err != nil && !testCase.shouldPass {
if err.Error() != testCase.err.Error() {
t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.err.Error(), err.Error())
}
}
}
}
// Tests validate Effect validator.
func TestIsValidEffect(t *testing.T) {
testCases := []struct {
// input.
effect string
// expected output.
err error
// flag indicating whether the test should pass.
shouldPass bool
}{
// Inputs with unsupported Effect.
// Test case - 1.
{"DontAllow", errors.New("Unsupported Effect found: DontAllow, please validate your policy document."), false},
// Test case - 2.
{"NeverAllow", errors.New("Unsupported Effect found: NeverAllow, please validate your policy document."), false},
// Test case - 3.
{"AllowAlways", errors.New("Unsupported Effect found: AllowAlways, please validate your policy document."), false},
// Inputs with valid Effect.
// Test Case - 4.
{"Allow", nil, true},
// Test Case - 5.
{"Deny", nil, true},
}
for i, testCase := range testCases {
err := isValidEffect(testCase.effect)
if err != nil && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
}
if err == nil && !testCase.shouldPass {
t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
}
// Failed as expected, but does it fail for the expected reason.
if err != nil && !testCase.shouldPass {
if err.Error() != testCase.err.Error() {
t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.err.Error(), err.Error())
}
}
}
}
// Tests validate Resources validator.
func TestIsValidResources(t *testing.T) {
testCases := []struct {
// input.
resources []string
// expected output.
err error
// flag indicating whether the test should pass.
shouldPass bool
}{
// Inputs with unsupported Action.
// Test case - 1.
// Empty Resources.
{[]string{}, errors.New("Resource list cannot be empty."), false},
// Test case - 2.
// A valid resource should have prefix "arn:aws:s3:::".
{[]string{"my-resource"}, errors.New("Unsupported resource style found: my-resource, please validate your policy document."), false},
// Test case - 3.
// A Valid resource should have bucket name followed by "arn:aws:s3:::".
{[]string{"arn:aws:s3:::"}, errors.New("Invalid resource style found: arn:aws:s3:::, please validate your policy document."), false},
// Test Case - 4.
// Valid resource shouldn't have slash('/') followed by "arn:aws:s3:::".
{[]string{"arn:aws:s3:::/"}, errors.New("Invalid resource style found: arn:aws:s3:::/, please validate your policy document."), false},
// Test cases with valid Resources.
{[]string{"arn:aws:s3:::my-bucket"}, nil, true},
{[]string{"arn:aws:s3:::my-bucket/Asia/*"}, nil, true},
{[]string{"arn:aws:s3:::my-bucket/Asia/India/*"}, nil, true},
}
for i, testCase := range testCases {
err := isValidResources(testCase.resources)
if err != nil && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
}
if err == nil && !testCase.shouldPass {
t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
}
// Failed as expected, but does it fail for the expected reason.
if err != nil && !testCase.shouldPass {
if err.Error() != testCase.err.Error() {
t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.err.Error(), err.Error())
}
}
}
}
// Tests validate principals validator.
func TestIsValidPrincipals(t *testing.T) {
testCases := []struct {
// input.
principals []string
// expected output.
err error
// flag indicating whether the test should pass.
shouldPass bool
}{
// Inputs with unsupported Principals.
// Test case - 1.
// Empty Principals list.
{[]string{}, errors.New("Principal cannot be empty."), false},
// Test case - 2.
// "*" is the only valid principal.
{[]string{"my-principal"}, errors.New("Unsupported principal style found: my-principal, please validate your policy document."), false},
// Test case - 3.
{[]string{"*", "111122233"}, errors.New("Unsupported principal style found: 111122233, please validate your policy document."), false},
// Test case - 4.
// Test case with valid principal value.
{[]string{"*"}, nil, true},
}
for i, testCase := range testCases {
err := isValidPrincipals(testCase.principals)
if err != nil && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
}
if err == nil && !testCase.shouldPass {
t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
}
// Failed as expected, but does it fail for the expected reason.
if err != nil && !testCase.shouldPass {
if err.Error() != testCase.err.Error() {
t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.err.Error(), err.Error())
}
}
}
}
// Tests validate policyStatement condition validator.
func TestIsValidConditions(t *testing.T) {
// returns empty conditions map.
setEmptyConditions := func() map[string]map[string]string {
return make(map[string]map[string]string)
}
// returns map with the "StringEquals" set to empty map.
setEmptyStringEquals := func() map[string]map[string]string {
emptyMap := make(map[string]string)
conditions := make(map[string]map[string]string)
conditions["StringEquals"] = emptyMap
return conditions
}
// returns map with the "StringNotEquals" set to empty map.
setEmptyStringNotEquals := func() map[string]map[string]string {
emptyMap := make(map[string]string)
conditions := make(map[string]map[string]string)
conditions["StringNotEquals"] = emptyMap
return conditions
}
// Generate conditions.
generateConditions := func(key1, key2, value string) map[string]map[string]string {
innerMap := make(map[string]string)
innerMap[key2] = value
conditions := make(map[string]map[string]string)
conditions[key1] = innerMap
return conditions
}
// generate ambigious conditions.
generateAmbigiousConditions := func() map[string]map[string]string {
innerMap := make(map[string]string)
innerMap["s3:prefix"] = "Asia/"
conditions := make(map[string]map[string]string)
conditions["StringEquals"] = innerMap
conditions["StringNotEquals"] = innerMap
return conditions
}
// generate valid and non valid type in the condition map.
generateValidInvalidConditions := func() map[string]map[string]string {
innerMap := make(map[string]string)
innerMap["s3:prefix"] = "Asia/"
conditions := make(map[string]map[string]string)
conditions["StringEquals"] = innerMap
conditions["InvalidType"] = innerMap
return conditions
}
// generate valid and invalid keys for valid types in the same condition map.
generateValidInvalidConditionKeys := func() map[string]map[string]string {
innerMapValid := make(map[string]string)
innerMapValid["s3:prefix"] = "Asia/"
innerMapInValid := make(map[string]string)
innerMapInValid["s3:invalid"] = "Asia/"
conditions := make(map[string]map[string]string)
conditions["StringEquals"] = innerMapValid
conditions["StringEquals"] = innerMapInValid
return conditions
}
// List of Conditions used for test cases.
testConditions := []map[string]map[string]string{
generateConditions("StringValues", "s3:max-keys", "100"),
generateConditions("StringEquals", "s3:Object", "100"),
generateAmbigiousConditions(),
generateValidInvalidConditions(),
generateValidInvalidConditionKeys(),
setEmptyConditions(),
setEmptyStringEquals(),
setEmptyStringNotEquals(),
generateConditions("StringEquals", "s3:prefix", "Asia/"),
generateConditions("StringEquals", "s3:max-keys", "100"),
generateConditions("StringNotEquals", "s3:prefix", "Asia/"),
generateConditions("StringNotEquals", "s3:max-keys", "100"),
}
testCases := []struct {
inputCondition map[string]map[string]string
// expected result.
expectedErr error
// flag indicating whether test should pass.
shouldPass bool
}{
// Malformed conditions.
// Test case - 1.
// "StringValues" is an invalid type.
{testConditions[0], fmt.Errorf("Unsupported condition type 'StringValues', " +
"please validate your policy document."), false},
// Test case - 2.
// "s3:Object" is an invalid key.
{testConditions[1], fmt.Errorf("Unsupported condition key " +
"'StringEquals', please validate your policy document."), false},
// Test case - 3.
// Test case with Ambigious conditions set.
{testConditions[2], fmt.Errorf("Ambigious condition values for key 's3:prefix', " +
"please validate your policy document."), false},
// Test case - 4.
// Test case with valid and invalid condition types.
{testConditions[3], fmt.Errorf("Unsupported condition type 'InvalidType', " +
"please validate your policy document."), false},
// Test case - 5.
// Test case with valid and invalid condition keys.
{testConditions[4], fmt.Errorf("Unsupported condition key 'StringEquals', " +
"please validate your policy document."), false},
// Test cases with valid conditions.
// Test case - 6.
{testConditions[5], nil, true},
// Test case - 7.
{testConditions[6], nil, true},
// Test case - 8.
{testConditions[7], nil, true},
// Test case - 9.
{testConditions[8], nil, true},
// Test case - 10.
{testConditions[9], nil, true},
// Test case - 11.
{testConditions[10], nil, true},
// Test case 10.
{testConditions[11], nil, true},
}
for i, testCase := range testCases {
actualErr := isValidConditions(testCase.inputCondition)
if actualErr != nil && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, actualErr.Error())
}
if actualErr == nil && !testCase.shouldPass {
t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.expectedErr.Error())
}
// Failed as expected, but does it fail for the expected reason.
if actualErr != nil && !testCase.shouldPass {
if actualErr.Error() != testCase.expectedErr.Error() {
t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.expectedErr.Error(), actualErr.Error())
}
}
}
}
// Tests validate Policy Action and Resource fields.
func TestCheckBucketPolicyResources(t *testing.T) {
// constructing policy statement without invalidPrefixActions (check bucket-policy-parser.go).
setValidPrefixActions := func(statements []policyStatement) []policyStatement {
statements[0].Actions = []string{"s3:DeleteObject", "s3:PutObject"}
return statements
}
// contructing policy statement with recursive resources.
// should result in ErrMalformedPolicy
setRecurseResource := func(statements []policyStatement) []policyStatement {
statements[0].Resources = []string{"arn:aws:s3:::minio-bucket/Asia/*", "arn:aws:s3:::minio-bucket/Asia/India/*"}
return statements
}
// constructing policy statement with lexically close characters.
// should not result in ErrMalformedPolicy
setResourceLexical := func(statements []policyStatement) []policyStatement {
statements[0].Resources = []string{"arn:aws:s3:::minio-bucket/op*", "arn:aws:s3:::minio-bucket/oo*"}
return statements
}
// List of BucketPolicy used for tests.
bucketAccessPolicies := []BucketPolicy{
// BucketPolicy - 1.
// Contains valid read only policy statement.
{Version: "1.0", Statements: setReadOnlyStatement("minio-bucket", "")},
// BucketPolicy - 2.
// Contains valid read-write only policy statement.
{Version: "1.0", Statements: setReadWriteStatement("minio-bucket", "Asia/")},
// BucketPolicy - 3.
// Contains valid write only policy statement.
{Version: "1.0", Statements: setWriteOnlyStatement("minio-bucket", "Asia/India/")},
// BucketPolicy - 4.
// Contains invalidPrefixActions.
// Since resourcePrefix is not to the bucket-name, it return ErrMalformedPolicy.
{Version: "1.0", Statements: setReadOnlyStatement("minio-bucket-fail", "Asia/India/")},
// BucketPolicy - 5.
// constructing policy statement without invalidPrefixActions (check bucket-policy-parser.go).
// but bucket part of the resource is not equal to the bucket name.
// this results in return of ErrMalformedPolicy.
{Version: "1.0", Statements: setValidPrefixActions(setWriteOnlyStatement("minio-bucket-fail", "Asia/India/"))},
// BucketPolicy - 6.
// contructing policy statement with recursive resources.
// should result in ErrMalformedPolicy
{Version: "1.0", Statements: setRecurseResource(setValidPrefixActions(setWriteOnlyStatement("minio-bucket", "")))},
// BucketPolciy - 7.
// constructing policy statement with non recursive but
// lexically close resources.
// should result in ErrNone.
{Version: "1.0", Statements: setResourceLexical(setValidPrefixActions(setWriteOnlyStatement("minio-bucket", "oo")))},
}
testCases := []struct {
inputPolicy BucketPolicy
// expected results.
apiErrCode APIErrorCode
// Flag indicating whether the test should pass.
shouldPass bool
}{
// Test case - 1.
{bucketAccessPolicies[0], ErrNone, true},
// Test case - 2.
{bucketAccessPolicies[1], ErrNone, true},
// Test case - 3.
{bucketAccessPolicies[2], ErrNone, true},
// Test case - 4.
// contains invalidPrefixActions (check bucket-policy-parser.go).
// Resource prefix will not be equal to the bucket name in this case.
{bucketAccessPolicies[3], ErrMalformedPolicy, false},
// Test case - 5.
// actions contain invalidPrefixActions (check bucket-policy-parser.go).
// Resource prefix bucket part is not equal to the bucket name in this case.
{bucketAccessPolicies[4], ErrMalformedPolicy, false},
// Test case - 6.
// contructing policy statement with recursive resources.
// should result in ErrMalformedPolicy.
{bucketAccessPolicies[5], ErrMalformedPolicy, false},
// Test case - 7.
// constructing policy statement with lexically close
// characters.
// should result in ErrNone.
{bucketAccessPolicies[6], ErrNone, true},
}
for i, testCase := range testCases {
apiErrCode := checkBucketPolicyResources("minio-bucket", testCase.inputPolicy)
if apiErrCode != ErrNone && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with Errocode %v", i+1, apiErrCode)
}
if apiErrCode == ErrNone && !testCase.shouldPass {
t.Errorf("Test %d: Expected to fail with ErrCode %v, but passed instead", i+1, testCase.apiErrCode)
}
// Failed as expected, but does it fail for the expected reason.
if apiErrCode != ErrNone && !testCase.shouldPass {
if testCase.apiErrCode != apiErrCode {
t.Errorf("Test %d: Expected to fail with error code %v, but instead failed with error code %v", i+1, testCase.apiErrCode, apiErrCode)
}
}
}
}
// Tests validate parsing of BucketAccessPolicy.
func TestParseBucketPolicy(t *testing.T) {
// set Unsupported Actions.
setUnsupportedActions := func(statements []policyStatement) []policyStatement {
// "s3:DeleteEverything"" is an Unsupported Action.
statements[0].Actions = []string{"s3:GetObject", "s3:ListBucket", "s3:PutObject", "s3:DeleteEverything"}
return statements
}
// set unsupported Effect.
setUnsupportedEffect := func(statements []policyStatement) []policyStatement {
// Effect "Don't allow" is Unsupported.
statements[0].Effect = "DontAllow"
return statements
}
// set unsupported principals.
setUnsupportedPrincipals := func(statements []policyStatement) []policyStatement {
// "User1111"" is an Unsupported Principal.
statements[0].Principal.AWS = []string{"*", "User1111"}
return statements
}
// set unsupported Resources.
setUnsupportedResources := func(statements []policyStatement) []policyStatement {
// "s3:DeleteEverything"" is an Unsupported Action.
statements[0].Resources = []string{"my-resource"}
return statements
}
// List of BucketPolicy used for test cases.
bucketAccesPolicies := []BucketPolicy{
// BucketPolicy - 0.
// BucketPolicy statement empty.
{Version: "1.0"},
// BucketPolicy - 1.
// BucketPolicy version empty.
{Version: "", Statements: []policyStatement{}},
// BucketPolicy - 2.
// Readonly BucketPolicy.
{Version: "1.0", Statements: setReadOnlyStatement("minio-bucket", "")},
// BucketPolicy - 3.
// Read-Write bucket policy.
{Version: "1.0", Statements: setReadWriteStatement("minio-bucket", "Asia/")},
// BucketPolicy - 4.
// Write only bucket policy.
{Version: "1.0", Statements: setWriteOnlyStatement("minio-bucket", "Asia/India/")},
// BucketPolicy - 5.
// BucketPolicy statement contains unsupported action.
{Version: "1.0", Statements: setUnsupportedActions(setReadOnlyStatement("minio-bucket", ""))},
// BucketPolicy - 6.
// BucketPolicy statement contains unsupported Effect.
{Version: "1.0", Statements: setUnsupportedEffect(setReadWriteStatement("minio-bucket", "Asia/"))},
// BucketPolicy - 7.
// BucketPolicy statement contains unsupported Principal.
{Version: "1.0", Statements: setUnsupportedPrincipals(setWriteOnlyStatement("minio-bucket", "Asia/India/"))},
// BucketPolicy - 8.
// BucketPolicy statement contains unsupported Resource.
{Version: "1.0", Statements: setUnsupportedResources(setWriteOnlyStatement("minio-bucket", "Asia/India/"))},
}
testCases := []struct {
inputPolicy BucketPolicy
// expected results.
expectedPolicy BucketPolicy
err error
// Flag indicating whether the test should pass.
shouldPass bool
}{
// Test case - 1.
// BucketPolicy statement empty.
{bucketAccesPolicies[0], BucketPolicy{}, errors.New("Policy statement cannot be empty."), false},
// Test case - 2.
// BucketPolicy version empty.
{bucketAccesPolicies[1], BucketPolicy{}, errors.New("Policy version cannot be empty."), false},
// Test case - 3.
// Readonly BucketPolicy.
{bucketAccesPolicies[2], bucketAccesPolicies[2], nil, true},
// Test case - 4.
// Read-Write bucket policy.
{bucketAccesPolicies[3], bucketAccesPolicies[3], nil, true},
// Test case - 5.
// Write only bucket policy.
{bucketAccesPolicies[4], bucketAccesPolicies[4], nil, true},
// Test case - 6.
// BucketPolicy statement contains unsupported action.
{bucketAccesPolicies[5], bucketAccesPolicies[5], fmt.Errorf("Unsupported action found: s3:DeleteEverything, please validate your policy document."), false},
// Test case - 7.
// BucketPolicy statement contains unsupported Effect.
{bucketAccesPolicies[6], bucketAccesPolicies[6], fmt.Errorf("Unsupported Effect found: DontAllow, please validate your policy document."), false},
// Test case - 8.
// BucketPolicy statement contains unsupported Principal.
{bucketAccesPolicies[7], bucketAccesPolicies[7], fmt.Errorf("Unsupported principal style found: User1111, please validate your policy document."), false},
// Test case - 9.
// BucketPolicy statement contains unsupported Resource.
{bucketAccesPolicies[8], bucketAccesPolicies[8], fmt.Errorf("Unsupported resource style found: my-resource, please validate your policy document."), false},
}
for i, testCase := range testCases {
inputPolicyBytes, e := json.Marshal(testCase.inputPolicy)
if e != nil {
t.Fatalf("Test %d: Couldn't Marshal bucket policy", i+1)
}
actualAccessPolicy, err := parseBucketPolicy(inputPolicyBytes)
if err != nil && testCase.shouldPass {
t.Errorf("Test %d: Expected to pass, but failed with: <ERROR> %s", i+1, err.Error())
}
if err == nil && !testCase.shouldPass {
t.Errorf("Test %d: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, testCase.err.Error())
}
// Failed as expected, but does it fail for the expected reason.
if err != nil && !testCase.shouldPass {
if err.Error() != testCase.err.Error() {
t.Errorf("Test %d: Expected to fail with error \"%s\", but instead failed with error \"%s\"", i+1, testCase.err.Error(), err.Error())
}
}
// Test passes as expected, but the output values are verified for correctness here.
if err == nil && testCase.shouldPass {
if !reflect.DeepEqual(testCase.expectedPolicy, actualAccessPolicy) {
t.Errorf("Test %d: The expected statements from resource statement generator doesn't match the actual statements", i+1)
}
}
}
}
+41 -60
View File
@@ -20,128 +20,109 @@ import (
"io/ioutil"
"os"
"path/filepath"
"github.com/minio/minio/pkg/fs"
"github.com/minio/minio/pkg/probe"
)
// getBucketsConfigPath - get buckets path.
func getBucketsConfigPath() (string, *probe.Error) {
func getBucketsConfigPath() (string, error) {
configPath, err := getConfigPath()
if err != nil {
return "", err.Trace()
return "", err
}
return filepath.Join(configPath, "buckets"), nil
}
// createBucketsConfigPath - create buckets directory.
func createBucketsConfigPath() *probe.Error {
// getBucketConfigPath - get bucket config path.
func getBucketConfigPath(bucket string) (string, error) {
bucketsConfigPath, err := getBucketsConfigPath()
if err != nil {
return err
}
if e := os.MkdirAll(bucketsConfigPath, 0700); e != nil {
return probe.NewError(e)
}
return nil
}
// getBucketConfigPath - get bucket path.
func getBucketConfigPath(bucket string) (string, *probe.Error) {
bucketsConfigPath, err := getBucketsConfigPath()
if err != nil {
return "", err.Trace()
return "", err
}
return filepath.Join(bucketsConfigPath, bucket), nil
}
// createBucketConfigPath - create bucket directory.
func createBucketConfigPath(bucket string) *probe.Error {
// createBucketConfigPath - create bucket config directory.
func createBucketConfigPath(bucket string) error {
bucketConfigPath, err := getBucketConfigPath(bucket)
if err != nil {
return err
}
if e := os.MkdirAll(bucketConfigPath, 0700); e != nil {
return probe.NewError(e)
}
return nil
return os.MkdirAll(bucketConfigPath, 0700)
}
// readBucketPolicy - read bucket policy.
func readBucketPolicy(bucket string) ([]byte, *probe.Error) {
func readBucketPolicy(bucket string) ([]byte, error) {
// Verify bucket is valid.
if !fs.IsValidBucketName(bucket) {
return nil, probe.NewError(fs.BucketNameInvalid{Bucket: bucket})
if !IsValidBucketName(bucket) {
return nil, BucketNameInvalid{Bucket: bucket}
}
bucketConfigPath, err := getBucketConfigPath(bucket)
if err != nil {
return nil, err.Trace()
return nil, err
}
// Get policy file.
bucketPolicyFile := filepath.Join(bucketConfigPath, "access-policy.json")
if _, e := os.Stat(bucketPolicyFile); e != nil {
if os.IsNotExist(e) {
return nil, probe.NewError(fs.BucketPolicyNotFound{Bucket: bucket})
if _, err = os.Stat(bucketPolicyFile); err != nil {
if os.IsNotExist(err) {
return nil, BucketPolicyNotFound{Bucket: bucket}
}
return nil, probe.NewError(e)
return nil, err
}
accessPolicyBytes, e := ioutil.ReadFile(bucketPolicyFile)
if e != nil {
return nil, probe.NewError(e)
}
return accessPolicyBytes, nil
return ioutil.ReadFile(bucketPolicyFile)
}
// removeBucketPolicy - remove bucket policy.
func removeBucketPolicy(bucket string) *probe.Error {
func removeBucketPolicy(bucket string) error {
// Verify bucket is valid.
if !fs.IsValidBucketName(bucket) {
return probe.NewError(fs.BucketNameInvalid{Bucket: bucket})
if !IsValidBucketName(bucket) {
return BucketNameInvalid{Bucket: bucket}
}
bucketConfigPath, err := getBucketConfigPath(bucket)
if err != nil {
return err.Trace(bucket)
return err
}
// Get policy file.
bucketPolicyFile := filepath.Join(bucketConfigPath, "access-policy.json")
if _, e := os.Stat(bucketPolicyFile); e != nil {
if os.IsNotExist(e) {
return probe.NewError(fs.BucketPolicyNotFound{Bucket: bucket})
if _, err = os.Stat(bucketPolicyFile); err != nil {
if os.IsNotExist(err) {
return BucketPolicyNotFound{Bucket: bucket}
}
return probe.NewError(e)
return err
}
if err := os.Remove(bucketPolicyFile); err != nil {
return err
}
return nil
}
// writeBucketPolicy - save bucket policy.
func writeBucketPolicy(bucket string, accessPolicyBytes []byte) *probe.Error {
func writeBucketPolicy(bucket string, accessPolicyBytes []byte) error {
// Verify if bucket path legal
if !fs.IsValidBucketName(bucket) {
return probe.NewError(fs.BucketNameInvalid{Bucket: bucket})
if !IsValidBucketName(bucket) {
return BucketNameInvalid{Bucket: bucket}
}
// Create bucket config path.
if err := createBucketConfigPath(bucket); err != nil {
return err
}
bucketConfigPath, err := getBucketConfigPath(bucket)
if err != nil {
return err.Trace()
return err
}
// Get policy file.
bucketPolicyFile := filepath.Join(bucketConfigPath, "access-policy.json")
if _, e := os.Stat(bucketPolicyFile); e != nil {
if !os.IsNotExist(e) {
return probe.NewError(e)
if _, err := os.Stat(bucketPolicyFile); err != nil {
if !os.IsNotExist(err) {
return err
}
}
// Write bucket policy.
if e := ioutil.WriteFile(bucketPolicyFile, accessPolicyBytes, 0600); e != nil {
return probe.NewError(e)
}
return nil
return ioutil.WriteFile(bucketPolicyFile, accessPolicyBytes, 0600)
}
+6 -1
View File
@@ -16,13 +16,18 @@
package main
// DO NOT EDIT THIS FILE DIRECTLY. These are build-time constants
// set through buildscripts/gen-ldflags.go.
var (
// minioGOPATH - GOPATH value at the time of build.
minioGOPATH = ""
// minioVersion - version time.RFC3339.
minioVersion = "DEVELOPMENT.GOGET"
// minioReleaseTag - release tag in TAG.%Y-%m-%dT%H-%M-%SZ.
minioReleaseTag = "DEVELOPMENT.GOGET"
// minioCommitID - latest commit id.
minioCommitID = "DEVELOPMENT.GOGET"
// minioShortCommitID - first 12 characters from mcCommitID
// minioShortCommitID - first 12 characters from minioCommitID.
minioShortCommitID = minioCommitID[:12]
)
+39 -3
View File
@@ -23,7 +23,12 @@ _init() {
fi
# List of supported architectures
SUPPORTED_OSARCH='linux/386 linux/amd64 linux/arm windows/386 windows/amd64 darwin/amd64'
SUPPORTED_OSARCH='linux/386 linux/amd64 linux/arm windows/386 windows/amd64 darwin/amd64 freebsd/amd64'
## System binaries
CP=`which cp`
SHASUM=`which shasum`
SED=`which sed`
}
go_build() {
@@ -33,15 +38,46 @@ go_build() {
package=$(go list -f '{{.ImportPath}}')
echo -n "-->"
printf "%15s:%s\n" "${osarch}" "${package}"
GO15VENDOREXPERIMENT=1 GOOS=$os GOARCH=$arch go build --ldflags "${LDFLAGS}" -o $release_str/$os-$arch/$(basename $package).$release_tag
# Release binary name
release_bin="$release_str/$os-$arch/$(basename $package).$release_tag"
# Release binary downloadable name
release_real_bin="$release_str/$os-$arch/$(basename $package)"
# Release shasum name
release_shasum="$release_str/$os-$arch/$(basename $package).shasum"
# Go build to build the binary.
GOOS=$os GOARCH=$arch go build --ldflags "${LDFLAGS}" -o $release_bin
# Create copy
if [ $os == "windows" ]; then
$CP -p $release_bin ${release_real_bin}.exe
else
$CP -p $release_bin $release_real_bin
fi
# Calculate shasum
shasum_str=$(${SHASUM} ${release_bin})
echo ${shasum_str} | $SED "s/$release_str\/$os-$arch\///g" > $release_shasum
}
main() {
# Build releases.
echo "Executing $release_str builds for OS: ${SUPPORTED_OSARCH}"
echo "Choose an OS Arch from the below"
for osarch in ${SUPPORTED_OSARCH}; do
go_build ${osarch}
echo ${osarch}
done
read -p "If you want to build for all, Just press Enter: " chosen_osarch
if [ "$chosen_osarch" = "" ]; then
for each_osarch in ${SUPPORTED_OSARCH}; do
go_build ${each_osarch}
done
else
go_build ${chosen_osarch}
fi
}
# Run main.
+4 -7
View File
@@ -21,7 +21,6 @@ _init() {
## Minimum required versions for build dependencies
GIT_VERSION="1.0"
YASM_VERSION="1.2.0"
GO_VERSION="1.6"
OSX_VERSION="10.8"
UNAME=$(uname -sm)
@@ -141,6 +140,9 @@ is_supported_os() {
"Linux")
os="linux"
;;
"FreeBSD")
os="freebsd"
;;
"Darwin")
osx_host_version=$(env sw_vers -productVersion)
check_version "${osx_host_version}" "${OSX_VERSION}"
@@ -155,7 +157,7 @@ is_supported_os() {
is_supported_arch() {
local supported
case ${UNAME##* } in
"x86_64")
"x86_64" | "amd64")
supported=1
;;
"arm"*)
@@ -181,11 +183,6 @@ check_deps() {
if [ $? -ge 2 ]; then
MISSING="${MISSING} git"
fi
check_version "$(env yasm --version 2>/dev/null | sed 's/^.* \([0-9.]*\).*$/\1/' | head -1)" "${YASM_VERSION}"
if [ $? -ge 2 ]; then
MISSING="${MISSING} yasm(${YASM_VERSION})"
fi
}
main() {
+2 -2
View File
@@ -19,8 +19,8 @@ _init() {
shopt -s extglob
PWD=$(pwd)
GOPATH=$(go env GOPATH)
PWD=$(pwd -P)
GOPATH=$(cd "$(go env GOPATH)" ; env pwd -P)
}
main() {
+5 -4
View File
@@ -28,10 +28,11 @@ import (
func genLDFlags(version string) string {
var ldflagsStr string
ldflagsStr = "-X main.minioVersion=" + version + " "
ldflagsStr = ldflagsStr + "-X main.minioReleaseTag=" + releaseTag(version) + " "
ldflagsStr = ldflagsStr + "-X main.minioCommitID=" + commitID() + " "
ldflagsStr = ldflagsStr + "-X main.minioShortCommitID=" + commitID()[:12]
ldflagsStr = "-X main.minioVersion=" + version
ldflagsStr += " -X main.minioReleaseTag=" + releaseTag(version)
ldflagsStr += " -X main.minioCommitID=" + commitID()
ldflagsStr += " -X main.minioShortCommitID=" + commitID()[:12]
ldflagsStr += " -X main.minioGOPATH=" + os.Getenv("GOPATH")
return ldflagsStr
}
+90
View File
@@ -0,0 +1,90 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* 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 main
import (
"os"
"path/filepath"
"github.com/minio/go-homedir"
)
// createCertsPath create certs path.
func createCertsPath() error {
certsPath, err := getCertsPath()
if err != nil {
return err
}
return os.MkdirAll(certsPath, 0700)
}
// getCertsPath get certs path.
func getCertsPath() (string, error) {
homeDir, err := homedir.Dir()
if err != nil {
return "", err
}
certsPath := filepath.Join(homeDir, globalMinioCertsDir)
return certsPath, nil
}
// mustGetCertsPath must get certs path.
func mustGetCertsPath() string {
certsPath, err := getCertsPath()
fatalIf(err, "Failed to get certificate path.")
return certsPath
}
// mustGetCertFile must get cert file.
func mustGetCertFile() string {
return filepath.Join(mustGetCertsPath(), globalMinioCertFile)
}
// mustGetKeyFile must get key file.
func mustGetKeyFile() string {
return filepath.Join(mustGetCertsPath(), globalMinioKeyFile)
}
// isCertFileExists verifies if cert file exists, returns true if
// found, false otherwise.
func isCertFileExists() bool {
st, e := os.Stat(filepath.Join(mustGetCertsPath(), globalMinioCertFile))
// If file exists and is regular return true.
if e == nil && st.Mode().IsRegular() {
return true
}
return false
}
// isKeyFileExists verifies if key file exists, returns true if found,
// false otherwise.
func isKeyFileExists() bool {
st, e := os.Stat(filepath.Join(mustGetCertsPath(), globalMinioKeyFile))
// If file exists and is regular return true.
if e == nil && st.Mode().IsRegular() {
return true
}
return false
}
// isSSL - returns true with both cert and key exists.
func isSSL() bool {
if isCertFileExists() && isKeyFileExists() {
return true
}
return false
}
+16 -3
View File
@@ -18,13 +18,26 @@ package main
import "github.com/minio/cli"
// Collection of minio commands currently supported are
// Collection of minio commands currently supported are.
var commands = []cli.Command{}
// Collection of minio commands currently supported in a trie tree
// Collection of minio commands currently supported in a trie tree.
var commandsTree = newTrie()
// registerCommand registers a cli command
// Collection of minio flags currently supported.
var globalFlags = []cli.Flag{
cli.StringFlag{
Name: "config-dir, C",
Value: mustGetConfigPath(),
Usage: "Path to configuration folder.",
},
cli.BoolFlag{
Name: "quiet",
Usage: "Suppress chatty output.",
},
}
// registerCommand registers a cli command.
func registerCommand(command cli.Command) {
commands = append(commands, command)
commandsTree.Insert(command.Name)
-164
View File
@@ -1,164 +0,0 @@
/*
* Minio Cloud Storage, (C) 2015 Minio, Inc.
*
* 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 main
import (
"runtime"
"github.com/minio/cli"
"github.com/minio/mc/pkg/console"
"github.com/minio/minio/pkg/probe"
)
// Configure logger
var configLoggerCmd = cli.Command{
Name: "logger",
Usage: "Configure logger.",
Action: mainConfigLogger,
CustomHelpTemplate: `NAME:
minio config {{.Name}} - {{.Usage}}
USAGE:
minio config {{.Name}} OPERATION [ARGS...]
OPERATION = add | list | remove
EXAMPLES:
1. Configure new mongo logger.
$ minio config {{.Name}} add mongo localhost:28710 mydb mylogger
2. Configure new syslog logger. NOTE: syslog logger is not supported on windows.
$ minio config {{.Name}} add syslog localhost:554 udp
3. Configure new file logger. "/var/log" should be writable by user.
$ minio config {{.Name}} add file /var/log/minio.log
4. List currently configured logger.
$ minio config {{.Name}} list
5. Remove/Reset a configured logger.
$ minio config {{.Name}} remove mongo
`,
}
// Inherit at one place
type config struct {
*configV2
}
func mainConfigLogger(ctx *cli.Context) {
if !ctx.Args().Present() || ctx.Args().First() == "help" {
cli.ShowCommandHelpAndExit(ctx, "logger", 1) // last argument is exit code
}
conf, err := loadConfigV2()
fatalIf(err.Trace(), "Unable to load config", nil)
if ctx.Args().Get(0) == "add" {
args := ctx.Args().Tail()
if args.Get(0) == "mongo" {
enableLog2Mongo(&config{conf}, args.Tail())
}
if args.Get(0) == "syslog" {
if runtime.GOOS == "windows" {
fatalIf(probe.NewError(errInvalidArgument), "Syslog is not supported on windows.", nil)
}
enableLog2Syslog(&config{conf}, args.Tail())
}
if args.Get(0) == "file" {
enableLog2File(&config{conf}, args.Tail())
}
}
if ctx.Args().Get(0) == "remove" {
args := ctx.Args().Tail()
if args.Get(0) == "mongo" {
conf.MongoLogger.Addr = ""
conf.MongoLogger.DB = ""
conf.MongoLogger.Collection = ""
err := saveConfig(conf)
fatalIf(err.Trace(), "Unable to save config.", nil)
}
if args.Get(0) == "syslog" {
if runtime.GOOS == "windows" {
fatalIf(probe.NewError(errInvalidArgument), "Syslog is not supported on windows.", nil)
}
conf.SyslogLogger.Network = ""
conf.SyslogLogger.Addr = ""
err := saveConfig(conf)
fatalIf(err.Trace(), "Unable to save config.", nil)
}
if args.Get(0) == "file" {
conf.FileLogger.Filename = ""
err := saveConfig(conf)
fatalIf(err.Trace(), "Unable to save config.", nil)
}
}
if ctx.Args().Get(0) == "list" {
console.Println(conf)
}
}
func enableLog2Mongo(conf *config, args cli.Args) {
if conf.IsFileLoggingEnabled() {
console.Infoln("File logging already enabled. Removing automatically by enabling mongo.")
conf.FileLogger.Filename = ""
}
if conf.IsSysloggingEnabled() {
console.Infoln("Syslog logging already enabled. Removing automatically by enabling mongo.")
conf.SyslogLogger.Addr = ""
conf.SyslogLogger.Network = ""
}
conf.MongoLogger.Addr = args.Get(0)
conf.MongoLogger.DB = args.Get(1)
conf.MongoLogger.Collection = args.Get(2)
err := saveConfig(conf.configV2)
fatalIf(err.Trace(), "Unable to save mongo logging config.", nil)
}
func enableLog2Syslog(conf *config, args cli.Args) {
if conf.IsFileLoggingEnabled() {
console.Infoln("File logging already enabled. Removing automatically by enabling syslog.")
conf.FileLogger.Filename = ""
}
if conf.IsMongoLoggingEnabled() {
console.Infoln("Mongo logging already enabled. Removing automatically by enabling syslog.")
conf.MongoLogger.Addr = ""
conf.MongoLogger.DB = ""
conf.MongoLogger.Collection = ""
}
conf.SyslogLogger.Addr = args.Get(0)
conf.SyslogLogger.Network = args.Get(1)
err := saveConfig(conf.configV2)
fatalIf(err.Trace(), "Unable to save syslog config.", nil)
}
func enableLog2File(conf *config, args cli.Args) {
if conf.IsSysloggingEnabled() {
console.Infoln("Syslog logging already enabled. Removing automatically by enabling file logging.")
conf.SyslogLogger.Addr = ""
conf.SyslogLogger.Network = ""
}
if conf.IsMongoLoggingEnabled() {
console.Infoln("Mongo logging already enabled. Removing automatically by enabling file logging.")
conf.MongoLogger.Addr = ""
conf.MongoLogger.DB = ""
conf.MongoLogger.Collection = ""
}
conf.FileLogger.Filename = args.Get(0)
err := saveConfig(conf.configV2)
fatalIf(err.Trace(), "Unable to save file logging config.", nil)
}
-53
View File
@@ -1,53 +0,0 @@
/*
* Minio Cloud Storage, (C) 2015 Minio, Inc.
*
* 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 main
import "github.com/minio/cli"
// Configure minio server
//
// ----
// NOTE: that the configure command only writes values to the config file.
// It does not use any configuration values from the environment variables.
// ----
//
var configCmd = cli.Command{
Name: "config",
Usage: "Collection of config management commands.",
Action: mainConfig,
Subcommands: []cli.Command{
configLoggerCmd,
configVersionCmd,
},
CustomHelpTemplate: `NAME:
{{.Name}} - {{.Usage}}
USAGE:
{{.Name}} {{if .Flags}}[global flags] {{end}}command{{if .Flags}} [command flags]{{end}} [arguments...]
COMMANDS:
{{range .Commands}}{{ .Name }}{{ "\t" }}{{.Usage}}
{{end}}
`,
}
// mainConfig is the handle for "minio config" command. provides sub-commands which write configuration data in json format to config file.
func mainConfig(ctx *cli.Context) {
if !ctx.Args().Present() || ctx.Args().First() == "help" {
cli.ShowAppHelp(ctx)
}
}
+148
View File
@@ -0,0 +1,148 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import (
"errors"
"os"
"path/filepath"
"github.com/minio/mc/pkg/console"
"github.com/minio/minio/pkg/quick"
)
func migrateConfig() {
// Purge all configs with version '1'.
purgeV1()
// Migrate version '2' to '3'.
migrateV2ToV3()
// Migrate version '3' to '4'.
migrateV3ToV4()
}
// Version '1' is not supported anymore and deprecated, safe to delete.
func purgeV1() {
cv1, err := loadConfigV1()
if err != nil && os.IsNotExist(err) {
return
}
fatalIf(err, "Unable to load config version 1.")
if cv1.Version == "1" {
console.Println("Removed unsupported config version 1.")
/// Purge old fsUsers.json file
configPath, err := getConfigPath()
fatalIf(err, "Unable to retrieve config path.")
configFile := filepath.Join(configPath, "fsUsers.json")
os.RemoveAll(configFile)
}
fatalIf(errors.New(""), "Failed to migrate unrecognized config version "+cv1.Version+".")
}
// Version '2' to '3' config migration adds new fields and re-orders
// previous fields. Simplifies config for future additions.
func migrateV2ToV3() {
cv2, err := loadConfigV2()
if err != nil && os.IsNotExist(err) {
return
}
fatalIf(err, "Unable to load config version 2.")
if cv2.Version != "2" {
return
}
srvConfig := &configV3{}
srvConfig.Version = "3"
srvConfig.Addr = ":9000"
srvConfig.Credential = credential{
AccessKeyID: cv2.Credentials.AccessKeyID,
SecretAccessKey: cv2.Credentials.SecretAccessKey,
}
srvConfig.Region = cv2.Credentials.Region
if srvConfig.Region == "" {
// Region needs to be set for AWS Signature V4.
srvConfig.Region = "us-east-1"
}
srvConfig.Logger.Console = consoleLogger{
Enable: true,
Level: "fatal",
}
flogger := fileLogger{}
flogger.Level = "error"
if cv2.FileLogger.Filename != "" {
flogger.Enable = true
flogger.Filename = cv2.FileLogger.Filename
}
srvConfig.Logger.File = flogger
slogger := syslogLogger{}
slogger.Level = "debug"
if cv2.SyslogLogger.Addr != "" {
slogger.Enable = true
slogger.Addr = cv2.SyslogLogger.Addr
}
srvConfig.Logger.Syslog = slogger
qc, err := quick.New(srvConfig)
fatalIf(err, "Unable to initialize config.")
configFile, err := getConfigFile()
fatalIf(err, "Unable to get config file.")
// Migrate the config.
err = qc.Save(configFile)
fatalIf(err, "Failed to migrate config from "+cv2.Version+" to "+srvConfig.Version+" failed.")
console.Println("Migration from version " + cv2.Version + " to " + srvConfig.Version + " completed successfully.")
}
// Version '3' to '4' migrates config, removes previous fields related
// to backend types and server address. This change further simplifies
// the config for future additions.
func migrateV3ToV4() {
cv3, err := loadConfigV3()
if err != nil && os.IsNotExist(err) {
return
}
fatalIf(err, "Unable to load config version 3.")
if cv3.Version != "3" {
return
}
// Save only the new fields, ignore the rest.
srvConfig := &serverConfigV4{}
srvConfig.Version = globalMinioConfigVersion
srvConfig.Credential = cv3.Credential
srvConfig.Region = cv3.Region
if srvConfig.Region == "" {
// Region needs to be set for AWS Signature Version 4.
srvConfig.Region = "us-east-1"
}
srvConfig.Logger.Console = cv3.Logger.Console
srvConfig.Logger.File = cv3.Logger.File
srvConfig.Logger.Syslog = cv3.Logger.Syslog
qc, err := quick.New(srvConfig)
fatalIf(err, "Unable to initialize the quick config.")
configFile, err := getConfigFile()
fatalIf(err, "Unable to get config file.")
err = qc.Save(configFile)
fatalIf(err, "Failed to migrate config from "+cv3.Version+" to "+srvConfig.Version+" failed.")
console.Println("Migration from version " + cv3.Version + " to " + srvConfig.Version + " completed successfully.")
}
+147
View File
@@ -0,0 +1,147 @@
package main
import (
"os"
"path/filepath"
"github.com/minio/minio/pkg/quick"
)
/////////////////// Config V1 ///////////////////
type configV1 struct {
Version string `json:"version"`
AccessKeyID string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
}
// loadConfigV1 load config
func loadConfigV1() (*configV1, error) {
configPath, err := getConfigPath()
if err != nil {
return nil, err
}
configFile := filepath.Join(configPath, "fsUsers.json")
if _, err = os.Stat(configFile); err != nil {
return nil, err
}
c := &configV1{}
c.Version = "1"
qc, err := quick.New(c)
if err != nil {
return nil, err
}
if err := qc.Load(configFile); err != nil {
return nil, err
}
return c, nil
}
/////////////////// Config V2 ///////////////////
type configV2 struct {
Version string `json:"version"`
Credentials struct {
AccessKeyID string `json:"accessKeyId"`
SecretAccessKey string `json:"secretAccessKey"`
Region string `json:"region"`
} `json:"credentials"`
MongoLogger struct {
Addr string `json:"addr"`
DB string `json:"db"`
Collection string `json:"collection"`
} `json:"mongoLogger"`
SyslogLogger struct {
Network string `json:"network"`
Addr string `json:"addr"`
} `json:"syslogLogger"`
FileLogger struct {
Filename string `json:"filename"`
} `json:"fileLogger"`
}
// loadConfigV2 load config version '2'.
func loadConfigV2() (*configV2, error) {
configFile, err := getConfigFile()
if err != nil {
return nil, err
}
if _, err = os.Stat(configFile); err != nil {
return nil, err
}
c := &configV2{}
c.Version = "2"
qc, err := quick.New(c)
if err != nil {
return nil, err
}
if err := qc.Load(configFile); err != nil {
return nil, err
}
return c, nil
}
/////////////////// Config V3 ///////////////////
// backendV3 type.
type backendV3 struct {
Type string `json:"type"`
Disk string `json:"disk,omitempty"`
Disks []string `json:"disks,omitempty"`
}
// loggerV3 type.
type loggerV3 struct {
Console struct {
Enable bool `json:"enable"`
Level string `json:"level"`
}
File struct {
Enable bool `json:"enable"`
Filename string `json:"fileName"`
Level string `json:"level"`
}
Syslog struct {
Enable bool `json:"enable"`
Addr string `json:"address"`
Level string `json:"level"`
} `json:"syslog"`
// Add new loggers here.
}
// configV3 server configuration version '3'.
type configV3 struct {
Version string `json:"version"`
// Backend configuration.
Backend backendV3 `json:"backend"`
// http Server configuration.
Addr string `json:"address"`
// S3 API configuration.
Credential credential `json:"credential"`
Region string `json:"region"`
// Additional error logging configuration.
Logger loggerV3 `json:"logger"`
}
// loadConfigV3 load config version '3'.
func loadConfigV3() (*configV3, error) {
configFile, err := getConfigFile()
if err != nil {
return nil, err
}
if _, err = os.Stat(configFile); err != nil {
return nil, err
}
c := &configV3{}
c.Version = "3"
qc, err := quick.New(c)
if err != nil {
return nil, err
}
if err := qc.Load(configFile); err != nil {
return nil, err
}
return c, nil
}
+197
View File
@@ -0,0 +1,197 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import (
"os"
"sync"
"github.com/minio/minio/pkg/quick"
)
// serverConfigV4 server configuration version '4'.
type serverConfigV4 struct {
Version string `json:"version"`
// S3 API configuration.
Credential credential `json:"credential"`
Region string `json:"region"`
// Additional error logging configuration.
Logger logger `json:"logger"`
// Read Write mutex.
rwMutex *sync.RWMutex
}
// initConfig - initialize server config. config version (called only once).
func initConfig() error {
if !isConfigFileExists() {
srvCfg := &serverConfigV4{}
srvCfg.Version = globalMinioConfigVersion
srvCfg.Region = "us-east-1"
srvCfg.Credential = mustGenAccessKeys()
// Enable console logger by default on a fresh run.
srvCfg.Logger.Console = consoleLogger{
Enable: true,
Level: "fatal",
}
srvCfg.rwMutex = &sync.RWMutex{}
// Create config path.
err := createConfigPath()
if err != nil {
return err
}
// Create certs path.
err = createCertsPath()
if err != nil {
return err
}
// Save the new config globally.
serverConfig = srvCfg
// Save config into file.
return serverConfig.Save()
}
configFile, err := getConfigFile()
if err != nil {
return err
}
if _, err = os.Stat(configFile); err != nil {
return err
}
srvCfg := &serverConfigV4{}
srvCfg.Version = globalMinioConfigVersion
srvCfg.rwMutex = &sync.RWMutex{}
qc, err := quick.New(srvCfg)
if err != nil {
return err
}
if err := qc.Load(configFile); err != nil {
return err
}
// Save the loaded config globally.
serverConfig = srvCfg
// Set the version properly after the unmarshalled json is loaded.
serverConfig.Version = globalMinioConfigVersion
return nil
}
// serverConfig server config.
var serverConfig *serverConfigV4
// GetVersion get current config version.
func (s serverConfigV4) GetVersion() string {
s.rwMutex.RLock()
defer s.rwMutex.RUnlock()
return s.Version
}
/// Logger related.
// SetFileLogger set new file logger.
func (s *serverConfigV4) SetFileLogger(flogger fileLogger) {
s.rwMutex.Lock()
defer s.rwMutex.Unlock()
s.Logger.File = flogger
}
// GetFileLogger get current file logger.
func (s serverConfigV4) GetFileLogger() fileLogger {
s.rwMutex.RLock()
defer s.rwMutex.RUnlock()
return s.Logger.File
}
// SetConsoleLogger set new console logger.
func (s *serverConfigV4) SetConsoleLogger(clogger consoleLogger) {
s.rwMutex.Lock()
defer s.rwMutex.Unlock()
s.Logger.Console = clogger
}
// GetConsoleLogger get current console logger.
func (s serverConfigV4) GetConsoleLogger() consoleLogger {
s.rwMutex.RLock()
defer s.rwMutex.RUnlock()
return s.Logger.Console
}
// SetSyslogLogger set new syslog logger.
func (s *serverConfigV4) SetSyslogLogger(slogger syslogLogger) {
s.rwMutex.Lock()
defer s.rwMutex.Unlock()
s.Logger.Syslog = slogger
}
// GetSyslogLogger get current syslog logger.
func (s *serverConfigV4) GetSyslogLogger() syslogLogger {
s.rwMutex.RLock()
defer s.rwMutex.RUnlock()
return s.Logger.Syslog
}
// SetRegion set new region.
func (s *serverConfigV4) SetRegion(region string) {
s.rwMutex.Lock()
defer s.rwMutex.Unlock()
s.Region = region
}
// GetRegion get current region.
func (s serverConfigV4) GetRegion() string {
s.rwMutex.RLock()
defer s.rwMutex.RUnlock()
return s.Region
}
// SetCredentials set new credentials.
func (s *serverConfigV4) SetCredential(creds credential) {
s.rwMutex.Lock()
defer s.rwMutex.Unlock()
s.Credential = creds
}
// GetCredentials get current credentials.
func (s serverConfigV4) GetCredential() credential {
s.rwMutex.RLock()
defer s.rwMutex.RUnlock()
return s.Credential
}
// Save config.
func (s serverConfigV4) Save() error {
s.rwMutex.RLock()
defer s.rwMutex.RUnlock()
// get config file.
configFile, err := getConfigFile()
if err != nil {
return err
}
// initialize quick.
qc, err := quick.New(&s)
if err != nil {
return err
}
// Save config file.
return qc.Save(configFile)
}
-50
View File
@@ -1,50 +0,0 @@
/*
* Minio Cloud Storage, (C) 2015 Minio, Inc.
*
* 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 main
import (
"github.com/minio/cli"
"github.com/minio/mc/pkg/console"
)
// Print config version.
var configVersionCmd = cli.Command{
Name: "version",
Usage: "Print config version.",
Action: mainConfigVersion,
CustomHelpTemplate: `NAME:
minio config {{.Name}} - {{.Usage}}
USAGE:
minio config {{.Name}}
`,
}
func mainConfigVersion(ctx *cli.Context) {
if ctx.Args().First() == "help" {
cli.ShowCommandHelpAndExit(ctx, "version", 1) // last argument is exit code
}
config, err := loadConfigV2()
fatalIf(err.Trace(), "Unable to load config", nil)
// convert interface{} back to its original struct
newConf := config
type Version string
console.Println(newConf.Version)
}
+88
View File
@@ -0,0 +1,88 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* 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 main
import (
"os"
"path/filepath"
"github.com/minio/go-homedir"
)
// configPath for custom config path only for testing purposes
var customConfigPath string
// Sets a new config path.
func setGlobalConfigPath(configPath string) {
customConfigPath = configPath
}
// getConfigPath get server config path
func getConfigPath() (string, error) {
if customConfigPath != "" {
return customConfigPath, nil
}
homeDir, err := homedir.Dir()
if err != nil {
return "", err
}
configPath := filepath.Join(homeDir, globalMinioConfigDir)
return configPath, nil
}
// mustGetConfigPath must get server config path.
func mustGetConfigPath() string {
configPath, err := getConfigPath()
fatalIf(err, "Unable to get config path.")
return configPath
}
// createConfigPath create server config path.
func createConfigPath() error {
configPath, err := getConfigPath()
if err != nil {
return err
}
return os.MkdirAll(configPath, 0700)
}
// isConfigFileExists - returns true if config file exists.
func isConfigFileExists() bool {
st, err := os.Stat(mustGetConfigFile())
// If file exists and is regular return true.
if err == nil && st.Mode().IsRegular() {
return true
}
return false
}
// mustGetConfigFile must get server config file.
func mustGetConfigFile() string {
configFile, err := getConfigFile()
fatalIf(err, "Unable to get config file.")
return configFile
}
// getConfigFile get server config file.
func getConfigFile() (string, error) {
configPath, err := getConfigPath()
if err != nil {
return "", err
}
return filepath.Join(configPath, globalMinioConfigFile), nil
}
+10
View File
@@ -0,0 +1,10 @@
# use "make dockerimage" to build
FROM alpine:3.3
RUN apk add --no-cache ca-certificates bash
ADD minio.dockerimage /minio
ADD export /export
EXPOSE 9000
COPY start.sh /start.sh
ENV ALLOW_CONTAINER_ROOT=1
ENTRYPOINT ["/start.sh"]
CMD ["/export"]
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
## Fix this check when we arrive at XL.
if [ -z "$1" ]; then
echo "Invalid arguments"
echo "Usage: <export_dir>"
exit 1
else
/bin/mkdir -p "$1" && /minio server "$1"
fi
+40
View File
@@ -0,0 +1,40 @@
# Server command line SPEC - Fri Apr 1 19:49:56 PDT 2016
## Single disk
Regular single server, single disk mode.
```
$ minio server <disk>
```
## Multi disk
```
$ minio controller start - Start minio controller.
```
Prints a secret token to be used by all parties.
Start all servers without disk with `MINIO_CONTROLLER` env set, start in cluster mode.
```
$ MINIO_CONTROLLER=<host>:<token> minio server
```
## Minio Controller cli.
Set controller host and token.
```
$ export MINIO_CONTROLLER=<host>:<token>
```
Create a cluster from the pool of nodes.
```
$ minioctl new <clustername> <ip1>:/mnt/disk1 .. <ip16>:/mnt/disk1
```
Start the cluster.
```
$ minioctl start <clustername>
```
Stop the cluster
```
$ minioctl stop <clustername>
```
@@ -9,14 +9,10 @@ This package implements parsing and validating bucket access policies based on A
### Supports following set of operations.
*
s3:*
s3:GetObject
s3:ListBucket
s3:PutObject
s3:CreateBucket
s3:GetBucketLocation
s3:DeleteBucket
s3:DeleteObject
s3:AbortMultipartUpload
s3:ListBucketMultipartUploads
@@ -31,3 +27,7 @@ Supported applicable condition keys for each conditions.
s3:prefix
s3:max-keys
### Nested policy support.
Nested policies are not allowed.
+33
View File
@@ -0,0 +1,33 @@
## Multipart
Each incoming part is uploaded in the following format.
Placeholder uploads.json to indicate a leaf.
```EXPORT_DIR/.minio/multipart/BUCKET/PATH/TO/OBJECT/uploads.json```
Incomplete file
```EXPORT_DIR/.minio/multipart/BUCKET/PATH/TO/OBJECT/UPLOADID/00000.incomplete```
Actual parts
```EXPORT_DIR/.minio/multipart/BUCKET/PATH/TO/OBJECT/UPLOADID/PART_NUMBER.MD5SUM_STRING```
## FS Format.
Each of these parts are concatenated back to a single contigous file.
```EXPORT_DIR/BUCKET/PATH/TO/OBJECT```
## XL Format.
Each of these parts are kept as is in the following format.
Special json file indicate the metata multipart information, essentially list of parts etc.
```EXPORT_DIR/BUCKET/PATH/TO/OBJECT/00000.minio.multipart```
All the parts that were uploaded.
```EXPORT_DIR/BUCKET/PATH/TO/OBJECT/PART_NUMBER.minio.multipart```
View File
+4
View File
@@ -0,0 +1,4 @@
{
"format": "fs",
"version": "1"
}
+15
View File
@@ -0,0 +1,15 @@
{
"version": "1",
"format": "fs",
"minio": {
"release": "DEVELOPMENT.GOGET"
},
"parts": [
{
"number": 1,
"name": "object1",
"size": 29,
"eTag": "",
},
]
}
+10
View File
@@ -0,0 +1,10 @@
{
"version": "1",
"format": "fs",
"uploadIds": [
{
"uploadID": "id",
"startTime": "time",
}
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"xl": {
"jbod": [
"8aa2b1bc-0e5a-49e0-8221-05228336b040",
"3467a69b-0266-478a-9e10-e819447e4545",
"d4a4505b-4e4f-4864-befd-4f36adb0bc66",
"592b6583-ca26-47af-b991-ba6d097e34e8",
"c7ef69f0-dbf5-4c0e-b167-d30a441bad7e",
"f0b36ea3-fe96-4f2b-bced-22c7f33e0e0c",
"b83abf39-e39d-4e7b-8e16-6f9953455a48",
"7d63dfc9-5441-4243-bd36-de8db0691982",
"c1bbffc5-81f9-4251-9398-33a959b3ce37",
"64408f94-26e0-4277-9593-2d703f4d5a91"
],
"disk": "8aa2b1bc-0e5a-49e0-8221-05228336b040",
"version": "1"
},
"format": "xl",
"version": "1"
}
+10
View File
@@ -0,0 +1,10 @@
{
"version": "1",
"format": "xl",
"uploadIds": [
{
"uploadID": "id",
"startTime": "time",
}
]
}
+57
View File
@@ -0,0 +1,57 @@
{
"parts": [
{
"number": 1,
"size": 5242880,
"etag": "3565c6e741e69a007a5ac7db893a62b5",
"name": "object1"
},
{
"number": 2,
"size": 5242880,
"etag": "d416712335c280ab1e39498552937764",
"name": "object2"
},
{
"number": 3,
"size": 4338324,
"etag": "8a98c5c54d81c6c95ed9bdcaeb941aaf",
"name": "object3"
}
],
"meta": {
"md5Sum": "97586a5290d4f5a41328062d6a7da593-3",
"content-type": "application\/octet-stream",
"content-encoding": ""
},
"minio": {
"release": "DEVELOPMENT.GOGET"
},
"erasure": {
"algorithm": "klauspost/reedsolomon/vandermonde",
"index": 2,
"distribution": [ 1, 3, 4, 2, 5, 8, 7, 6, 9 ],
"blockSize": 4194304,
"parity": 5,
"data": 5,
"checksum": [
{
"name": "object1",
"algorithm": "sha512",
"hash": "d9910e1492446389cfae6fe979db0245f96ca97ca2c7a25cab45805882004479320d866a47ea1f7be6a62625dd4de6caf7816009ef9d62779346d01a221b335c",
},
{
"name": "object2",
"algorithm": "sha512",
"hash": "d9910e1492446389cfae6fe979db0245f96ca97ca2c7a25cab45805882004479320d866a47ea1f7be6a62625dd4de6caf7816009ef9d62779346d01a221b335c",
},
],
},
"stat": {
"version": 0,
"modTime": "2016-05-24T00:09:40.122390255Z",
"size": 14824084
},
"format": "xl",
"version": "1"
}
+28
View File
@@ -0,0 +1,28 @@
### Logging.
- `fatalIf` - wrapper function which takes error and prints jsonic error messages.
- `errorIf` - similar to fatalIf but doesn't exit on err != nil.
Supported logging targets.
- console
- file
- syslog
Sample logger section from `~/.minio/config.json`
```
"console": {
"enable": true,
"level": "error"
},
"file": {
"enable": false,
"fileName": "",
"level": "error"
},
"syslog": {
"enable": false,
"address": "",
"level": "error"
}
```
+12 -11
View File
@@ -19,21 +19,22 @@ minio server <testdir>
JSON RPC namespace is `Web`.
#### Auth Operations
#### Server operations.
* ServerInfo - fetches current server information, includes memory statistics, minio binary
version, golang runtime version and more.
#### Auth operations
* Login - waits for 'username, password' and on success replies a new Json Web Token (JWT).
* ResetToken - resets token, requires password and token.
* Logout - currently a dummy operation.
* SetAuth - change access credentials with new 'username, password'.
* GetAuth - fetch the current auth from the server.
#### Bucket/Object Operations.
#### Bucket/Object operations.
* ListBuckets - lists buckets, requires a valid token.
* ListObjects - lists objects, requires a valid token.
* MakeBucket - make a new bucket, requires a valid token.
* GetObjectURL - generates a URL for download access, requires a valid token.
(generated URL is valid for 1hr)
* PutObjectURL - generates a URL for upload access, requies a valid token.
(generated URL is valid for 1hr)
#### Server Operations.
* DiskInfo - get backend disk statistics.
* RemoveObject - removes an object from a bucket, requires a valid token.
* Upload - uploads a new object from the browser, requires a valid token.
* Download - downloads an object from a bucket, requires a valid token.
+41
View File
@@ -0,0 +1,41 @@
### file.json
``file.json`` is a special file captured and written by XL storage API layer
to interpret, manage and extract erasured data from multiple disks.
```json
{
"version": "1.0.0",
"stat": {
"size": 24256,
"modTime": "2016-04-28T00:11:37.843Z",
"delete": false,
"version": 0
},
"erasure": {
"data": 5,
"parity": 5,
"blockSize": 4194304
],
"minio": {
"release": "RELEASE.2016-04-28T00-09-47Z"
}
}
```
#### JSON meaning.
- "version" // Version of the meta json file.
- "stat" // Stat value of written file.
- "size" // Size of the file.
- "modTime" // Modified time of the file.
- "deleted" // Field to track if the file is deleted when disks are down.
- "version" // File version tracked when disks are down.
- "erasure" // Erasure metadata for the written file.
- "data" // Data blocks parts of the file.
- "parity" // Parity blocks parts of the file.
- "blockSize" // BlockSize read/write chunk size.
+149
View File
@@ -0,0 +1,149 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import (
"encoding/hex"
"hash"
"io"
"sync"
"github.com/klauspost/reedsolomon"
)
// erasureCreateFile - writes an entire stream by erasure coding to
// all the disks, writes also calculate individual block's checksum
// for future bit-rot protection.
func erasureCreateFile(disks []StorageAPI, volume string, path string, partName string, data io.Reader, eInfos []erasureInfo) (newEInfos []erasureInfo, size int64, err error) {
// Allocated blockSized buffer for reading.
buf := make([]byte, blockSizeV1)
hashWriters := newHashWriters(len(disks))
// Just pick one eInfo.
eInfo := pickValidErasureInfo(eInfos)
// Read until io.EOF, erasure codes data and writes to all disks.
for {
var n int
n, err = io.ReadFull(data, buf)
if err == io.EOF {
break
}
if err != nil && err != io.ErrUnexpectedEOF {
return nil, 0, err
}
size += int64(n)
var blocks [][]byte
// Returns encoded blocks.
blocks, err = encodeData(buf[:n], eInfo.DataBlocks, eInfo.ParityBlocks)
if err != nil {
return nil, 0, err
}
err = appendFile(disks, volume, path, blocks, eInfo.Distribution, hashWriters)
if err != nil {
return nil, 0, err
}
}
// Save the checksums.
checkSums := make([]checkSumInfo, len(disks))
for index := range disks {
blockIndex := eInfo.Distribution[index] - 1
checkSums[blockIndex] = checkSumInfo{
Name: partName,
Algorithm: "sha512",
Hash: hex.EncodeToString(hashWriters[blockIndex].Sum(nil)),
}
}
// Erasure info update for checksum for each disks.
newEInfos = make([]erasureInfo, len(disks))
for index, eInfo := range eInfos {
if eInfo.IsValid() {
blockIndex := eInfo.Distribution[index] - 1
newEInfos[index] = eInfo
newEInfos[index].Checksum = append(newEInfos[index].Checksum, checkSums[blockIndex])
}
}
// Return newEInfos.
return newEInfos, size, nil
}
// encodeData - encodes incoming data buffer into
// dataBlocks+parityBlocks returns a 2 dimensional byte array.
func encodeData(dataBuffer []byte, dataBlocks, parityBlocks int) ([][]byte, error) {
rs, err := reedsolomon.New(dataBlocks, parityBlocks)
if err != nil {
return nil, err
}
// Split the input buffer into data and parity blocks.
var blocks [][]byte
blocks, err = rs.Split(dataBuffer)
if err != nil {
return nil, err
}
// Encode parity blocks using data blocks.
err = rs.Encode(blocks)
if err != nil {
return nil, err
}
// Return encoded blocks.
return blocks, nil
}
// appendFile - append data buffer at path.
func appendFile(disks []StorageAPI, volume, path string, enBlocks [][]byte, distribution []int, hashWriters []hash.Hash) (err error) {
var wg = &sync.WaitGroup{}
var wErrs = make([]error, len(disks))
// Write encoded data to quorum disks in parallel.
for index, disk := range disks {
if disk == nil {
continue
}
wg.Add(1)
// Write encoded data in routine.
go func(index int, disk StorageAPI) {
defer wg.Done()
// Pick the block from the distribution.
blockIndex := distribution[index] - 1
n, wErr := disk.AppendFile(volume, path, enBlocks[blockIndex])
if wErr != nil {
wErrs[index] = wErr
return
}
if n != int64(len(enBlocks[blockIndex])) {
wErrs[index] = errUnexpected
return
}
// Calculate hash for each blocks.
hashWriters[blockIndex].Write(enBlocks[blockIndex])
// Successfully wrote.
wErrs[index] = nil
}(index, disk)
}
// Wait for all the appends to finish.
wg.Wait()
// Return success.
return nil
}
+208
View File
@@ -0,0 +1,208 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import (
"encoding/hex"
"errors"
"github.com/klauspost/reedsolomon"
)
// erasureReadFile - read an entire erasure coded file at into a byte
// array. Erasure coded parts are often few mega bytes in size and it
// is convenient to return them as byte slice. This function also
// supports bit-rot detection by verifying checksum of individual
// block's checksum.
func erasureReadFile(disks []StorageAPI, volume string, path string, partName string, size int64, eInfos []erasureInfo) ([]byte, error) {
// Return data buffer.
var buffer []byte
// Total size left
totalSizeLeft := size
// Starting offset for reading.
startOffset := int64(0)
// Gather previously calculated block checksums.
blockCheckSums := metaPartBlockChecksums(disks, eInfos, partName)
// Pick one erasure info.
eInfo := pickValidErasureInfo(eInfos)
// Write until each parts are read and exhausted.
for totalSizeLeft > 0 {
// Calculate the proper block size.
var curBlockSize int64
if eInfo.BlockSize < totalSizeLeft {
curBlockSize = eInfo.BlockSize
} else {
curBlockSize = totalSizeLeft
}
// Calculate the current encoded block size.
curEncBlockSize := getEncodedBlockLen(curBlockSize, eInfo.DataBlocks)
offsetEncOffset := getEncodedBlockLen(startOffset, eInfo.DataBlocks)
// Allocate encoded blocks up to storage disks.
enBlocks := make([][]byte, len(disks))
// Counter to keep success data blocks.
var successDataBlocksCount = 0
var noReconstruct bool // Set for no reconstruction.
// Read from all the disks.
for index, disk := range disks {
blockIndex := eInfo.Distribution[index] - 1
if !isValidBlock(disks, volume, path, toDiskIndex(blockIndex, eInfo.Distribution), blockCheckSums) {
continue
}
if disk == nil {
continue
}
// Initialize shard slice and fill the data from each parts.
enBlocks[blockIndex] = make([]byte, curEncBlockSize)
// Read the necessary blocks.
_, err := disk.ReadFile(volume, path, offsetEncOffset, enBlocks[blockIndex])
if err != nil {
enBlocks[blockIndex] = nil
}
// Verify if we have successfully read all the data blocks.
if blockIndex < eInfo.DataBlocks && enBlocks[blockIndex] != nil {
successDataBlocksCount++
// Set when we have all the data blocks and no
// reconstruction is needed, so that we can avoid
// erasure reconstruction.
noReconstruct = successDataBlocksCount == eInfo.DataBlocks
if noReconstruct {
// Break out we have read all the data blocks.
break
}
}
}
// Check blocks if they are all zero in length, we have corruption return error.
if checkBlockSize(enBlocks) == 0 {
return nil, errXLDataCorrupt
}
// Verify if reconstruction is needed, proceed with reconstruction.
if !noReconstruct {
err := decodeData(enBlocks, eInfo.DataBlocks, eInfo.ParityBlocks)
if err != nil {
return nil, err
}
}
// Get data blocks from encoded blocks.
dataBlocks, err := getDataBlocks(enBlocks, eInfo.DataBlocks, int(curBlockSize))
if err != nil {
return nil, err
}
// Copy data blocks.
buffer = append(buffer, dataBlocks...)
// Negate the 'n' size written to client.
totalSizeLeft -= int64(len(dataBlocks))
// Increase the offset to move forward.
startOffset += int64(len(dataBlocks))
// Relenquish memory.
dataBlocks = nil
}
return buffer, nil
}
// PartObjectChecksum - returns the checksum for the part name from the checksum slice.
func (e erasureInfo) PartObjectChecksum(partName string) checkSumInfo {
for _, checksum := range e.Checksum {
if checksum.Name == partName {
return checksum
}
}
return checkSumInfo{}
}
// xlMetaPartBlockChecksums - get block checksums for a given part.
func metaPartBlockChecksums(disks []StorageAPI, eInfos []erasureInfo, partName string) (blockCheckSums []checkSumInfo) {
for index := range disks {
if eInfos[index].IsValid() {
// Save the read checksums for a given part.
blockCheckSums = append(blockCheckSums, eInfos[index].PartObjectChecksum(partName))
} else {
blockCheckSums = append(blockCheckSums, checkSumInfo{})
}
}
return blockCheckSums
}
// Takes block index and block distribution to get the disk index.
func toDiskIndex(blockIdx int, distribution []int) (diskIndex int) {
diskIndex = -1
// Find out the right disk index for the input block index.
for index, blockIndex := range distribution {
if blockIndex == blockIdx {
diskIndex = index
}
}
return diskIndex
}
// isValidBlock - calculates the checksum hash for the block and
// validates if its correct returns true for valid cases, false otherwise.
func isValidBlock(disks []StorageAPI, volume, path string, diskIndex int, blockCheckSums []checkSumInfo) bool {
// Unknown block index requested, treat it as error.
if diskIndex == -1 {
return false
}
// Disk is not present, treat entire block to be non existent.
if disks[diskIndex] == nil {
return false
}
// Read everything for a given block and calculate hash.
hashWriter := newHash(blockCheckSums[diskIndex].Algorithm)
hashBytes, err := hashSum(disks[diskIndex], volume, path, hashWriter)
if err != nil {
return false
}
return hex.EncodeToString(hashBytes) == blockCheckSums[diskIndex].Hash
}
// decodeData - decode encoded blocks.
func decodeData(enBlocks [][]byte, dataBlocks, parityBlocks int) error {
rs, err := reedsolomon.New(dataBlocks, parityBlocks)
if err != nil {
return err
}
err = rs.Reconstruct(enBlocks)
if err != nil {
return err
}
// Verify reconstructed blocks (parity).
ok, err := rs.Verify(enBlocks)
if err != nil {
return err
}
if !ok {
// Blocks cannot be reconstructed, corrupted data.
err = errors.New("Verification failed after reconstruction, data likely corrupted.")
return err
}
return nil
}
+108
View File
@@ -0,0 +1,108 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import (
"crypto/sha512"
"hash"
"io"
"github.com/klauspost/reedsolomon"
)
// newHashWriters - inititialize a slice of hashes for the disk count.
func newHashWriters(diskCount int) []hash.Hash {
hashWriters := make([]hash.Hash, diskCount)
for index := range hashWriters {
hashWriters[index] = newHash("sha512")
}
return hashWriters
}
// newHash - gives you a newly allocated hash depending on the input algorithm.
func newHash(algo string) hash.Hash {
switch algo {
case "sha512":
return sha512.New()
// Add new hashes here.
default:
return sha512.New()
}
}
func hashSum(disk StorageAPI, volume, path string, writer hash.Hash) ([]byte, error) {
startOffset := int64(0)
// Read until io.EOF.
for {
buf := make([]byte, blockSizeV1)
n, err := disk.ReadFile(volume, path, startOffset, buf)
if err == io.EOF {
break
}
if err != nil && err != io.EOF {
return nil, err
}
writer.Write(buf[:n])
startOffset += n
}
return writer.Sum(nil), nil
}
// getDataBlocks - fetches the data block only part of the input encoded blocks.
func getDataBlocks(enBlocks [][]byte, dataBlocks int, curBlockSize int) (data []byte, err error) {
if len(enBlocks) < dataBlocks {
return nil, reedsolomon.ErrTooFewShards
}
size := 0
blocks := enBlocks[:dataBlocks]
for _, block := range blocks {
size += len(block)
}
if size < curBlockSize {
return nil, reedsolomon.ErrShortData
}
write := curBlockSize
for _, block := range blocks {
if write < len(block) {
data = append(data, block[:write]...)
return data, nil
}
data = append(data, block...)
write -= len(block)
}
return data, nil
}
// checkBlockSize return the size of a single block.
// The first non-zero size is returned,
// or 0 if all blocks are size 0.
func checkBlockSize(blocks [][]byte) int {
for _, block := range blocks {
if len(block) != 0 {
return len(block)
}
}
return 0
}
// calculate the blockSize based on input length and total number of
// data blocks.
func getEncodedBlockLen(inputLen int64, dataBlocks int) (curEncBlockSize int64) {
curEncBlockSize = (inputLen + int64(dataBlocks) - 1) / int64(dataBlocks)
return curEncBlockSize
}
+74
View File
@@ -0,0 +1,74 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import (
"io"
"sync"
)
// waitCloser implements a Closer that blocks until released, this
// prevents the writer closing one end of a pipe while making sure
// that all data is written and committed to disk on the end.
// Additionally this also implements Write().
type waitCloser struct {
wg *sync.WaitGroup // Waitgroup for atomicity.
writer io.WriteCloser // Embedded writer.
}
// Write to the underlying writer.
func (b *waitCloser) Write(data []byte) (int, error) {
return b.writer.Write(data)
}
// Close blocks until another goroutine calls Release(error). Returns
// error code if either writer fails or Release is called with an error.
func (b *waitCloser) Close() error {
err := b.writer.Close()
b.wg.Wait()
return err
}
// CloseWithError closes the writer; subsequent read to the read
// half of the pipe will return the error err.
func (b *waitCloser) CloseWithError(err error) error {
w, ok := b.writer.(*io.PipeWriter)
if ok {
return w.CloseWithError(err)
}
return err
}
// release the Close, causing it to unblock. Only call this
// once. Calling it multiple times results in a panic.
func (b *waitCloser) release() {
b.wg.Done()
return
}
// newWaitCloser creates a new write closer that must be
// released by the read consumer.
func newWaitCloser(writer io.WriteCloser) *waitCloser {
// Wait group for the go-routine.
wg := &sync.WaitGroup{}
// Add to the wait group to wait for.
wg.Add(1)
return &waitCloser{
wg: wg,
writer: writer,
}
}
@@ -1,5 +1,3 @@
// +build !cgo arm
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
@@ -16,8 +14,4 @@
* limitations under the License.
*/
package sha1
func block(dig *digest, p []byte) {
blockGeneric(dig, p)
}
package main
-57
View File
@@ -1,57 +0,0 @@
/*
* Minio Cloud Storage, (C) 2015 Minio, Inc.
*
* 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 main
import "github.com/minio/cli"
// Collection of minio flags currently supported
var flags = []cli.Flag{}
var (
configFolderFlag = cli.StringFlag{
Name: "config-folder, C",
Value: mustGetConfigPath(),
Usage: "Path to configuration folder.",
}
addressFlag = cli.StringFlag{
Name: "address",
Value: ":9000",
Usage: "ADDRESS:PORT for cloud storage access.",
}
accessLogFlag = cli.BoolFlag{
Name: "enable-accesslog",
Hide: true,
Usage: "Enable access logs for all incoming HTTP request.",
}
certFlag = cli.StringFlag{
Name: "cert",
Usage: "Provide your domain certificate.",
}
keyFlag = cli.StringFlag{
Name: "key",
Usage: "Provide your domain private key.",
}
)
// registerFlag registers a cli flag
func registerFlag(flag cli.Flag) {
flags = append(flags, flag)
}
+588
View File
@@ -0,0 +1,588 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import (
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
"github.com/skyrings/skyring-common/tools/uuid"
)
// fsFormat - structure holding 'fs' format.
type fsFormat struct {
Version string `json:"version"`
}
// xlFormat - structure holding 'xl' format.
type xlFormat struct {
Version string `json:"version"` // Version of 'xl' format.
Disk string `json:"disk"` // Disk field carries assigned disk uuid.
// JBOD field carries the input disk order generated the first
// time when fresh disks were supplied.
JBOD []string `json:"jbod"`
}
// formatConfigV1 - structure holds format config version '1'.
type formatConfigV1 struct {
Version string `json:"version"` // Version of the format config.
// Format indicates the backend format type, supports two values 'xl' and 'fs'.
Format string `json:"format"`
FS *fsFormat `json:"fs,omitempty"` // FS field holds fs format.
XL *xlFormat `json:"xl,omitempty"` // XL field holds xl format.
}
/*
All disks online
-----------------
- All Unformatted - format all and return success.
- Some Unformatted - format all and return success.
- Any JBOD inconsistent - return failure // Requires deep inspection, phase2.
- Some are corrupt (missing format.json) - return failure // Requires deep inspection, phase2.
- Any unrecognized disks - return failure
Some disks are offline and we have quorum.
-----------------
- Some unformatted - no heal, return success.
- Any JBOD inconsistent - return failure // Requires deep inspection, phase2.
- Some are corrupt (missing format.json) - return failure // Requires deep inspection, phase2.
- Any unrecognized disks - return failure
No read quorum
-----------------
failure for all cases.
// Pseudo code for managing `format.json`.
// Generic checks.
if (no quorum) return error
if (any disk is corrupt) return error // phase2
if (jbod inconsistent) return error // phase2
if (disks not recognized) // Always error.
// Specific checks.
if (all disks online)
if (all disks return format.json)
if (jbod consistent)
if (all disks recognized)
return
else
if (all disks return format.json not found)
(initialize format)
return
else (some disks return format.json not found)
(heal format)
return
fi
fi
else // No healing at this point forward, some disks are offline or dead.
if (some disks return format.json not found)
if (with force)
// Offline disks are marked as dead.
(heal format) // Offline disks should be marked as dead.
return success
else (without force)
// --force is necessary to heal few drives, because some drives
// are offline. Offline disks will be marked as dead.
return error
fi
fi
*/
var errSomeDiskUnformatted = errors.New("some disks are found to be unformatted")
var errSomeDiskOffline = errors.New("some disks are offline")
// Returns error slice into understandable errors.
func reduceFormatErrs(errs []error, diskCount int) error {
var errUnformattedDiskCount = 0
var errDiskNotFoundCount = 0
for _, err := range errs {
if err == errUnformattedDisk {
errUnformattedDiskCount++
} else if err == errDiskNotFound {
errDiskNotFoundCount++
}
}
// Returns errUnformattedDisk if all disks report unFormattedDisk.
if errUnformattedDiskCount == diskCount {
return errUnformattedDisk
} else if errUnformattedDiskCount < diskCount && errDiskNotFoundCount == 0 {
// Only some disks return unFormattedDisk and all disks are online.
return errSomeDiskUnformatted
} else if errUnformattedDiskCount < diskCount && errDiskNotFoundCount > 0 {
// Only some disks return unFormattedDisk and some disks are
// offline as well.
return errSomeDiskOffline
}
return nil
}
// loadAllFormats - load all format config from all input disks in parallel.
func loadAllFormats(bootstrapDisks []StorageAPI) ([]*formatConfigV1, []error) {
// Initialize sync waitgroup.
var wg = &sync.WaitGroup{}
// Initialize list of errors.
var sErrs = make([]error, len(bootstrapDisks))
// Initialize format configs.
var formatConfigs = make([]*formatConfigV1, len(bootstrapDisks))
// Make a volume entry on all underlying storage disks.
for index, disk := range bootstrapDisks {
wg.Add(1)
// Make a volume inside a go-routine.
go func(index int, disk StorageAPI) {
defer wg.Done()
formatConfig, lErr := loadFormat(disk)
if lErr != nil {
sErrs[index] = lErr
return
}
formatConfigs[index] = formatConfig
}(index, disk)
}
// Wait for all make vol to finish.
wg.Wait()
for _, err := range sErrs {
if err != nil {
// Return all formats and errors.
return formatConfigs, sErrs
}
}
// Return all formats and nil
return formatConfigs, nil
}
// genericFormatCheck - validates and returns error.
// if (no quorum) return error
// if (any disk is corrupt) return error // phase2
// if (jbod inconsistent) return error // phase2
// if (disks not recognized) // Always error.
func genericFormatCheck(formatConfigs []*formatConfigV1, sErrs []error) (err error) {
// Calculate the errors.
var (
errCorruptFormatCount = 0
errCount = 0
)
// Through all errors calculate the actual errors.
for _, lErr := range sErrs {
if lErr == nil {
continue
}
// These errors are good conditions, means disk is online.
if lErr == errUnformattedDisk || lErr == errVolumeNotFound {
continue
}
if lErr == errCorruptedFormat {
errCorruptFormatCount++
} else {
errCount++
}
}
// Calculate read quorum.
readQuorum := len(formatConfigs)/2 + 1
// Validate the err count under tolerant limit.
if errCount > len(formatConfigs)-readQuorum {
return errXLReadQuorum
}
// One of the disk has corrupt format, return error.
if errCorruptFormatCount > 0 {
return errCorruptedFormat
}
// Validates if format and JBOD are consistent across all disks.
if err = checkFormatXL(formatConfigs); err != nil {
return err
}
// Success..
return nil
}
// checkDisksConsistency - checks if all disks are consistent with all JBOD entries on all disks.
func checkDisksConsistency(formatConfigs []*formatConfigV1) error {
var disks = make([]string, len(formatConfigs))
var disksFound = make(map[string]bool)
// Collect currently available disk uuids.
for index, formatConfig := range formatConfigs {
if formatConfig == nil {
continue
}
disks[index] = formatConfig.XL.Disk
}
// Validate collected uuids and verify JBOD.
for index, uuid := range disks {
if uuid == "" {
continue
}
var formatConfig = formatConfigs[index]
for _, savedUUID := range formatConfig.XL.JBOD {
if savedUUID == uuid {
disksFound[uuid] = true
}
}
}
// Check if all disks are found.
for _, value := range disksFound {
if !value {
return errors.New("Some disks not found in JBOD.")
}
}
return nil
}
// checkJBODConsistency - validate xl jbod order if they are consistent.
func checkJBODConsistency(formatConfigs []*formatConfigV1) error {
var firstJBOD []string
// Extract first valid JBOD.
for _, format := range formatConfigs {
if format == nil {
continue
}
firstJBOD = format.XL.JBOD
break
}
jbodStr := strings.Join(firstJBOD, ".")
for _, format := range formatConfigs {
if format == nil {
continue
}
savedJBODStr := strings.Join(format.XL.JBOD, ".")
if jbodStr != savedJBODStr {
return errors.New("Inconsistent JBOD found.")
}
}
return nil
}
func findIndex(disk string, jbod []string) int {
for index, uuid := range jbod {
if uuid == disk {
return index
}
}
return -1
}
// reorderDisks - reorder disks in JBOD order.
func reorderDisks(bootstrapDisks []StorageAPI, formatConfigs []*formatConfigV1) ([]StorageAPI, error) {
var savedJBOD []string
for _, format := range formatConfigs {
if format == nil {
continue
}
savedJBOD = format.XL.JBOD
break
}
// Pick the first JBOD list to verify the order and construct new set of disk slice.
var newDisks = make([]StorageAPI, len(bootstrapDisks))
for fIndex, format := range formatConfigs {
if format == nil {
continue
}
jIndex := findIndex(format.XL.Disk, savedJBOD)
if jIndex == -1 {
return nil, errors.New("Unrecognized uuid " + format.XL.Disk + " found")
}
newDisks[jIndex] = bootstrapDisks[fIndex]
}
return newDisks, nil
}
// loadFormat - load format from disk.
func loadFormat(disk StorageAPI) (format *formatConfigV1, err error) {
var buffer []byte
buffer, err = readAll(disk, minioMetaBucket, formatConfigFile)
if err != nil {
// 'file not found' and 'volume not found' as
// same. 'volume not found' usually means its a fresh disk.
if err == errFileNotFound || err == errVolumeNotFound {
var vols []VolInfo
vols, err = disk.ListVols()
if err != nil {
return nil, err
}
if len(vols) > 1 {
// 'format.json' not found, but we found user data.
return nil, errCorruptedFormat
}
// No other data found, its a fresh disk.
return nil, errUnformattedDisk
}
return nil, err
}
format = &formatConfigV1{}
err = json.Unmarshal(buffer, format)
if err != nil {
return nil, err
}
return format, nil
}
// Heals any missing format.json on the drives. Returns error only for unexpected errors
// as regular errors can be ignored since there might be enough quorum to be operational.
func healFormatXL(bootstrapDisks []StorageAPI) error {
needHeal := make([]bool, len(bootstrapDisks)) // Slice indicating which drives needs healing.
formatConfigs := make([]*formatConfigV1, len(bootstrapDisks))
var referenceConfig *formatConfigV1
successCount := 0 // Tracks if we have successfully loaded all `format.json` from all disks.
formatNotFoundCount := 0 // Tracks if we `format.json` is not found on all disks.
// Loads `format.json` from all disks.
for index, disk := range bootstrapDisks {
formatXL, err := loadFormat(disk)
if err != nil {
if err == errUnformattedDisk {
// format.json is missing, should be healed.
needHeal[index] = true
formatNotFoundCount++
continue
} else if err == errDiskNotFound { // Is a valid case we
// can proceed without healing.
return nil
}
// Return error for unsupported errors.
return err
} // Success.
formatConfigs[index] = formatXL
successCount++
}
// All `format.json` has been read successfully, previously completed.
if successCount == len(bootstrapDisks) {
// Return success.
return nil
}
// All disks are fresh, format.json will be written by initFormatXL()
if formatNotFoundCount == len(bootstrapDisks) {
return initFormatXL(bootstrapDisks)
}
// Validate format configs for consistency in JBOD and disks.
if err := checkFormatXL(formatConfigs); err != nil {
return err
}
if referenceConfig == nil {
// This config will be used to update the drives missing format.json.
for _, formatConfig := range formatConfigs {
if formatConfig == nil {
continue
}
referenceConfig = formatConfig
break
}
}
uuidUsage := make([]struct {
uuid string // Disk uuid
inUse bool // indicates if the uuid is used by
// any disk
}, len(bootstrapDisks))
// Returns any unused drive UUID.
getUnusedUUID := func() string {
for index := range uuidUsage {
if !uuidUsage[index].inUse {
uuidUsage[index].inUse = true
return uuidUsage[index].uuid
}
}
return ""
}
// From reference config update UUID's not be in use.
for index, diskUUID := range referenceConfig.XL.JBOD {
uuidUsage[index].uuid = diskUUID
uuidUsage[index].inUse = false
}
// For all config formats validate if they are in use and
// update the uuidUsage values.
for _, config := range formatConfigs {
if config == nil {
continue
}
for index := range uuidUsage {
if config.XL.Disk == uuidUsage[index].uuid {
uuidUsage[index].inUse = true
break
}
}
}
// This section heals the format.json and updates the fresh disks
// by apply a new UUID for all the fresh disks.
for index, heal := range needHeal {
if !heal {
continue
}
config := &formatConfigV1{}
*config = *referenceConfig
config.XL.Disk = getUnusedUUID()
if config.XL.Disk == "" {
// getUnusedUUID() should have
// returned an unused uuid, it
// is an unexpected error.
return errUnexpected
}
formatBytes, err := json.Marshal(config)
if err != nil {
return err
}
// Fresh disk without format.json
_, _ = bootstrapDisks[index].AppendFile(minioMetaBucket, formatConfigFile, formatBytes)
// Ignore any error from AppendFile() as
// quorum might still be there to be operational.
}
return nil
}
// loadFormatXL - loads XL `format.json` and returns back properly
// ordered storage slice based on `format.json`.
func loadFormatXL(bootstrapDisks []StorageAPI) (disks []StorageAPI, err error) {
var unformattedDisksFoundCnt = 0
var diskNotFoundCount = 0
formatConfigs := make([]*formatConfigV1, len(bootstrapDisks))
// Try to load `format.json` bootstrap disks.
for index, disk := range bootstrapDisks {
var formatXL *formatConfigV1
formatXL, err = loadFormat(disk)
if err != nil {
if err == errUnformattedDisk {
unformattedDisksFoundCnt++
continue
} else if err == errDiskNotFound {
diskNotFoundCount++
continue
}
return nil, err
}
// Save valid formats.
formatConfigs[index] = formatXL
}
// If all disks indicate that 'format.json' is not available
// return 'errUnformattedDisk'.
if unformattedDisksFoundCnt == len(bootstrapDisks) {
return nil, errUnformattedDisk
} else if diskNotFoundCount == len(bootstrapDisks) {
return nil, errDiskNotFound
} else if diskNotFoundCount > len(bootstrapDisks)-(len(bootstrapDisks)/2+1) {
return nil, errXLReadQuorum
} else if unformattedDisksFoundCnt > len(bootstrapDisks)-(len(bootstrapDisks)/2+1) {
return nil, errXLReadQuorum
}
// Validate the format configs read are correct.
if err = checkFormatXL(formatConfigs); err != nil {
return nil, err
}
// Erasure code requires disks to be presented in the same order each time.
return reorderDisks(bootstrapDisks, formatConfigs)
}
// checkFormatXL - verifies if format.json format is intact.
func checkFormatXL(formatConfigs []*formatConfigV1) error {
for _, formatXL := range formatConfigs {
if formatXL == nil {
continue
}
// Validate format version and format type.
if formatXL.Version != "1" {
return fmt.Errorf("Unsupported version of backend format [%s] found.", formatXL.Version)
}
if formatXL.Format != "xl" {
return fmt.Errorf("Unsupported backend format [%s] found.", formatXL.Format)
}
if formatXL.XL.Version != "1" {
return fmt.Errorf("Unsupported XL backend format found [%s]", formatXL.XL.Version)
}
if len(formatConfigs) != len(formatXL.XL.JBOD) {
return fmt.Errorf("Number of disks %d did not match the backend format %d", len(formatConfigs), len(formatXL.XL.JBOD))
}
}
if err := checkJBODConsistency(formatConfigs); err != nil {
return err
}
return checkDisksConsistency(formatConfigs)
}
// initFormatXL - save XL format configuration on all disks.
func initFormatXL(storageDisks []StorageAPI) (err error) {
var (
jbod = make([]string, len(storageDisks))
formats = make([]*formatConfigV1, len(storageDisks))
saveFormatErrCnt = 0
)
for index, disk := range storageDisks {
if err = disk.MakeVol(minioMetaBucket); err != nil {
if err != errVolumeExists {
saveFormatErrCnt++
// Check for write quorum.
if saveFormatErrCnt <= len(storageDisks)-(len(storageDisks)/2+3) {
continue
}
return errXLWriteQuorum
}
}
var u *uuid.UUID
u, err = uuid.New()
if err != nil {
saveFormatErrCnt++
// Check for write quorum.
if saveFormatErrCnt <= len(storageDisks)-(len(storageDisks)/2+3) {
continue
}
return err
}
formats[index] = &formatConfigV1{
Version: "1",
Format: "xl",
XL: &xlFormat{
Version: "1",
Disk: u.String(),
},
}
jbod[index] = formats[index].XL.Disk
}
for index, disk := range storageDisks {
formats[index].XL.JBOD = jbod
formatBytes, err := json.Marshal(formats[index])
if err != nil {
return err
}
n, err := disk.AppendFile(minioMetaBucket, formatConfigFile, formatBytes)
if err != nil {
return err
}
if n != int64(len(formatBytes)) {
return errUnexpected
}
}
return nil
}
+95
View File
@@ -0,0 +1,95 @@
package main
import (
"encoding/json"
"path"
"sort"
)
const (
fsMetaJSONFile = "fs.json"
)
// A fsMetaV1 represents a metadata header mapping keys to sets of values.
type fsMetaV1 struct {
Version string `json:"version"`
Format string `json:"format"`
Minio struct {
Release string `json:"release"`
} `json:"minio"`
Parts []objectPartInfo `json:"parts,omitempty"`
}
// ObjectPartIndex - returns the index of matching object part number.
func (m fsMetaV1) ObjectPartIndex(partNumber int) (partIndex int) {
for i, part := range m.Parts {
if partNumber == part.Number {
partIndex = i
return partIndex
}
}
return -1
}
// AddObjectPart - add a new object part in order.
func (m *fsMetaV1) AddObjectPart(partNumber int, partName string, partETag string, partSize int64) {
partInfo := objectPartInfo{
Number: partNumber,
Name: partName,
ETag: partETag,
Size: partSize,
}
// Update part info if it already exists.
for i, part := range m.Parts {
if partNumber == part.Number {
m.Parts[i] = partInfo
return
}
}
// Proceed to include new part info.
m.Parts = append(m.Parts, partInfo)
// Parts in fsMeta should be in sorted order by part number.
sort.Sort(byObjectPartNumber(m.Parts))
}
// readFSMetadata - returns the object metadata `fs.json` content.
func (fs fsObjects) readFSMetadata(bucket, object string) (fsMeta fsMetaV1, err error) {
var buffer []byte
buffer, err = readAll(fs.storage, bucket, path.Join(object, fsMetaJSONFile))
if err != nil {
return fsMetaV1{}, err
}
err = json.Unmarshal(buffer, &fsMeta)
if err != nil {
return fsMetaV1{}, err
}
return fsMeta, nil
}
// newFSMetaV1 - initializes new fsMetaV1.
func newFSMetaV1() (fsMeta fsMetaV1) {
fsMeta = fsMetaV1{}
fsMeta.Version = "1"
fsMeta.Format = "fs"
fsMeta.Minio.Release = minioReleaseTag
return fsMeta
}
// writeFSMetadata - writes `fs.json` metadata.
func (fs fsObjects) writeFSMetadata(bucket, prefix string, fsMeta fsMetaV1) error {
metadataBytes, err := json.Marshal(fsMeta)
if err != nil {
return err
}
n, err := fs.storage.AppendFile(bucket, path.Join(prefix, fsMetaJSONFile), metadataBytes)
if err != nil {
return err
}
if n != int64(len(metadataBytes)) {
return errUnexpected
}
return nil
}
+70
View File
@@ -0,0 +1,70 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import (
"path"
"strings"
)
// Returns if the prefix is a multipart upload.
func (fs fsObjects) isMultipartUpload(bucket, prefix string) bool {
_, err := fs.storage.StatFile(bucket, pathJoin(prefix, uploadsJSONFile))
return err == nil
}
// listUploadsInfo - list all uploads info.
func (fs fsObjects) listUploadsInfo(prefixPath string) (uploads []uploadInfo, err error) {
splitPrefixes := strings.SplitN(prefixPath, "/", 3)
uploadIDs, err := readUploadsJSON(splitPrefixes[1], splitPrefixes[2], fs.storage)
if err != nil {
if err == errFileNotFound {
return []uploadInfo{}, nil
}
return nil, err
}
uploads = uploadIDs.Uploads
return uploads, nil
}
// Checks whether bucket exists.
func (fs fsObjects) isBucketExist(bucket string) bool {
// Check whether bucket exists.
_, err := fs.storage.StatVol(bucket)
if err != nil {
if err == errVolumeNotFound {
return false
}
errorIf(err, "Stat failed on bucket "+bucket+".")
return false
}
return true
}
// isUploadIDExists - verify if a given uploadID exists and is valid.
func (fs fsObjects) isUploadIDExists(bucket, object, uploadID string) bool {
uploadIDPath := path.Join(mpartMetaPrefix, bucket, object, uploadID)
_, err := fs.storage.StatFile(minioMetaBucket, path.Join(uploadIDPath, fsMetaJSONFile))
if err != nil {
if err == errFileNotFound {
return false
}
errorIf(err, "Unable to access upload id"+uploadIDPath)
return false
}
return true
}
+648
View File
@@ -0,0 +1,648 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"path"
"strconv"
"strings"
"time"
"github.com/skyrings/skyring-common/tools/uuid"
)
// listMultipartUploads - lists all multipart uploads.
func (fs fsObjects) listMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, error) {
result := ListMultipartsInfo{}
recursive := true
if delimiter == slashSeparator {
recursive = false
}
result.IsTruncated = true
result.MaxUploads = maxUploads
result.KeyMarker = keyMarker
result.Prefix = prefix
result.Delimiter = delimiter
// Not using path.Join() as it strips off the trailing '/'.
multipartPrefixPath := pathJoin(mpartMetaPrefix, bucket, prefix)
if prefix == "" {
// Should have a trailing "/" if prefix is ""
// For ex. multipartPrefixPath should be "multipart/bucket/" if prefix is ""
multipartPrefixPath += slashSeparator
}
multipartMarkerPath := ""
if keyMarker != "" {
multipartMarkerPath = pathJoin(mpartMetaPrefix, bucket, keyMarker)
}
var uploads []uploadMetadata
var err error
var eof bool
if uploadIDMarker != "" {
nsMutex.RLock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, keyMarker))
uploads, _, err = listMultipartUploadIDs(bucket, keyMarker, uploadIDMarker, maxUploads, fs.storage)
nsMutex.RUnlock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, keyMarker))
if err != nil {
return ListMultipartsInfo{}, err
}
maxUploads = maxUploads - len(uploads)
}
if maxUploads > 0 {
walker := fs.lookupTreeWalk(listParams{minioMetaBucket, recursive, multipartMarkerPath, multipartPrefixPath})
if walker == nil {
walker = fs.startTreeWalk(minioMetaBucket, multipartPrefixPath, multipartMarkerPath, recursive, func(bucket, object string) bool {
return fs.isMultipartUpload(bucket, object)
})
}
for maxUploads > 0 {
walkResult, ok := <-walker.ch
if !ok {
// Closed channel.
eof = true
break
}
// For any walk error return right away.
if walkResult.err != nil {
// File not found or Disk not found is a valid case.
if walkResult.err == errFileNotFound || walkResult.err == errDiskNotFound {
eof = true
break
}
return ListMultipartsInfo{}, err
}
entry := strings.TrimPrefix(walkResult.entry, retainSlash(pathJoin(mpartMetaPrefix, bucket)))
if strings.HasSuffix(walkResult.entry, slashSeparator) {
uploads = append(uploads, uploadMetadata{
Object: entry,
})
maxUploads--
if maxUploads == 0 {
if walkResult.end {
eof = true
break
}
}
continue
}
var tmpUploads []uploadMetadata
var end bool
uploadIDMarker = ""
nsMutex.RLock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, entry))
tmpUploads, end, err = listMultipartUploadIDs(bucket, entry, uploadIDMarker, maxUploads, fs.storage)
nsMutex.RUnlock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, entry))
if err != nil {
return ListMultipartsInfo{}, err
}
uploads = append(uploads, tmpUploads...)
maxUploads -= len(tmpUploads)
if walkResult.end && end {
eof = true
break
}
}
}
// Loop through all the received uploads fill in the multiparts result.
for _, upload := range uploads {
var objectName string
var uploadID string
if strings.HasSuffix(upload.Object, slashSeparator) {
// All directory entries are common prefixes.
uploadID = "" // Upload ids are empty for CommonPrefixes.
objectName = upload.Object
result.CommonPrefixes = append(result.CommonPrefixes, objectName)
} else {
uploadID = upload.UploadID
objectName = upload.Object
result.Uploads = append(result.Uploads, upload)
}
result.NextKeyMarker = objectName
result.NextUploadIDMarker = uploadID
}
result.IsTruncated = !eof
if !result.IsTruncated {
result.NextKeyMarker = ""
result.NextUploadIDMarker = ""
}
return result, nil
}
// ListMultipartUploads - lists all the pending multipart uploads on a
// bucket. Additionally takes 'prefix, keyMarker, uploadIDmarker and a
// delimiter' which allows us to list uploads match a particular
// prefix or lexically starting from 'keyMarker' or delimiting the
// output to get a directory like listing.
//
// Implements S3 compatible ListMultipartUploads API. The resulting
// ListMultipartsInfo structure is unmarshalled directly into XML and
// replied back to the client.
func (fs fsObjects) ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, error) {
// Validate input arguments.
if !IsValidBucketName(bucket) {
return ListMultipartsInfo{}, BucketNameInvalid{Bucket: bucket}
}
if !fs.isBucketExist(bucket) {
return ListMultipartsInfo{}, BucketNotFound{Bucket: bucket}
}
if !IsValidObjectPrefix(prefix) {
return ListMultipartsInfo{}, ObjectNameInvalid{Bucket: bucket, Object: prefix}
}
// Verify if delimiter is anything other than '/', which we do not support.
if delimiter != "" && delimiter != slashSeparator {
return ListMultipartsInfo{}, UnsupportedDelimiter{
Delimiter: delimiter,
}
}
// Verify if marker has prefix.
if keyMarker != "" && !strings.HasPrefix(keyMarker, prefix) {
return ListMultipartsInfo{}, InvalidMarkerPrefixCombination{
Marker: keyMarker,
Prefix: prefix,
}
}
if uploadIDMarker != "" {
if strings.HasSuffix(keyMarker, slashSeparator) {
return ListMultipartsInfo{}, InvalidUploadIDKeyCombination{
UploadIDMarker: uploadIDMarker,
KeyMarker: keyMarker,
}
}
id, err := uuid.Parse(uploadIDMarker)
if err != nil {
return ListMultipartsInfo{}, err
}
if id.IsZero() {
return ListMultipartsInfo{}, MalformedUploadID{
UploadID: uploadIDMarker,
}
}
}
return fs.listMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads)
}
// newMultipartUpload - wrapper for initializing a new multipart
// request, returns back a unique upload id.
//
// Internally this function creates 'uploads.json' associated for the
// incoming object at '.minio/multipart/bucket/object/uploads.json' on
// all the disks. `uploads.json` carries metadata regarding on going
// multipart operation on the object.
func (fs fsObjects) newMultipartUpload(bucket string, object string, meta map[string]string) (uploadID string, err error) {
// No metadata is set, allocate a new one.
if meta == nil {
meta = make(map[string]string)
}
// Initialize `fs.json` values.
fsMeta := newFSMetaV1()
// This lock needs to be held for any changes to the directory contents of ".minio/multipart/object/"
nsMutex.Lock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, object))
defer nsMutex.Unlock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, object))
uploadID = getUUID()
initiated := time.Now().UTC()
// Create 'uploads.json'
if err = writeUploadJSON(bucket, object, uploadID, initiated, fs.storage); err != nil {
return "", err
}
uploadIDPath := path.Join(mpartMetaPrefix, bucket, object, uploadID)
tempUploadIDPath := path.Join(tmpMetaPrefix, uploadID)
if err = fs.writeFSMetadata(minioMetaBucket, tempUploadIDPath, fsMeta); err != nil {
return "", toObjectErr(err, minioMetaBucket, tempUploadIDPath)
}
err = fs.storage.RenameFile(minioMetaBucket, path.Join(tempUploadIDPath, fsMetaJSONFile), minioMetaBucket, path.Join(uploadIDPath, fsMetaJSONFile))
if err != nil {
if dErr := fs.storage.DeleteFile(minioMetaBucket, path.Join(tempUploadIDPath, fsMetaJSONFile)); dErr != nil {
return "", toObjectErr(dErr, minioMetaBucket, tempUploadIDPath)
}
return "", toObjectErr(err, minioMetaBucket, uploadIDPath)
}
// Return success.
return uploadID, nil
}
// NewMultipartUpload - initialize a new multipart upload, returns a
// unique id. The unique id returned here is of UUID form, for each
// subsequent request each UUID is unique.
//
// Implements S3 compatible initiate multipart API.
func (fs fsObjects) NewMultipartUpload(bucket, object string, meta map[string]string) (string, error) {
meta = make(map[string]string) // Reset the meta value, we are not going to save headers for fs.
// Verify if bucket name is valid.
if !IsValidBucketName(bucket) {
return "", BucketNameInvalid{Bucket: bucket}
}
// Verify whether the bucket exists.
if !fs.isBucketExist(bucket) {
return "", BucketNotFound{Bucket: bucket}
}
// Verify if object name is valid.
if !IsValidObjectName(object) {
return "", ObjectNameInvalid{Bucket: bucket, Object: object}
}
return fs.newMultipartUpload(bucket, object, meta)
}
// PutObjectPart - reads incoming data until EOF for the part file on
// an ongoing multipart transaction. Internally incoming data is
// written to '.minio/tmp' location and safely renamed to
// '.minio/multipart' for reach parts.
func (fs fsObjects) PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (string, error) {
// Verify if bucket is valid.
if !IsValidBucketName(bucket) {
return "", BucketNameInvalid{Bucket: bucket}
}
// Verify whether the bucket exists.
if !fs.isBucketExist(bucket) {
return "", BucketNotFound{Bucket: bucket}
}
if !IsValidObjectName(object) {
return "", ObjectNameInvalid{Bucket: bucket, Object: object}
}
uploadIDPath := path.Join(mpartMetaPrefix, bucket, object, uploadID)
nsMutex.RLock(minioMetaBucket, uploadIDPath)
// Just check if the uploadID exists to avoid copy if it doesn't.
uploadIDExists := fs.isUploadIDExists(bucket, object, uploadID)
nsMutex.RUnlock(minioMetaBucket, uploadIDPath)
if !uploadIDExists {
return "", InvalidUploadID{UploadID: uploadID}
}
// Hold write lock on the part so that there is no parallel upload on the part.
nsMutex.Lock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, object, uploadID, strconv.Itoa(partID)))
defer nsMutex.Unlock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, object, uploadID, strconv.Itoa(partID)))
partSuffix := fmt.Sprintf("object%d", partID)
tmpPartPath := path.Join(tmpMetaPrefix, uploadID, partSuffix)
// Initialize md5 writer.
md5Writer := md5.New()
var buf = make([]byte, blockSizeV1)
for {
n, err := io.ReadFull(data, buf)
if err == io.EOF {
break
}
if err != nil && err != io.ErrUnexpectedEOF {
return "", toObjectErr(err, bucket, object)
}
// Update md5 writer.
md5Writer.Write(buf[:n])
m, err := fs.storage.AppendFile(minioMetaBucket, tmpPartPath, buf[:n])
if err != nil {
return "", toObjectErr(err, bucket, object)
}
if m != int64(len(buf[:n])) {
return "", toObjectErr(errUnexpected, bucket, object)
}
}
newMD5Hex := hex.EncodeToString(md5Writer.Sum(nil))
if md5Hex != "" {
if newMD5Hex != md5Hex {
return "", BadDigest{md5Hex, newMD5Hex}
}
}
// Hold write lock as we are updating fs.json
nsMutex.Lock(minioMetaBucket, uploadIDPath)
defer nsMutex.Unlock(minioMetaBucket, uploadIDPath)
// Just check if the uploadID exists to avoid copy if it doesn't.
if !fs.isUploadIDExists(bucket, object, uploadID) {
return "", InvalidUploadID{UploadID: uploadID}
}
fsMeta, err := fs.readFSMetadata(minioMetaBucket, uploadIDPath)
if err != nil {
return "", toObjectErr(err, minioMetaBucket, uploadIDPath)
}
fsMeta.AddObjectPart(partID, partSuffix, newMD5Hex, size)
partPath := path.Join(mpartMetaPrefix, bucket, object, uploadID, partSuffix)
err = fs.storage.RenameFile(minioMetaBucket, tmpPartPath, minioMetaBucket, partPath)
if err != nil {
if dErr := fs.storage.DeleteFile(minioMetaBucket, tmpPartPath); dErr != nil {
return "", toObjectErr(dErr, minioMetaBucket, tmpPartPath)
}
return "", toObjectErr(err, minioMetaBucket, partPath)
}
uploadIDPath = path.Join(mpartMetaPrefix, bucket, object, uploadID)
tempUploadIDPath := path.Join(tmpMetaPrefix, uploadID)
if err = fs.writeFSMetadata(minioMetaBucket, tempUploadIDPath, fsMeta); err != nil {
return "", toObjectErr(err, minioMetaBucket, tempUploadIDPath)
}
err = fs.storage.RenameFile(minioMetaBucket, path.Join(tempUploadIDPath, fsMetaJSONFile), minioMetaBucket, path.Join(uploadIDPath, fsMetaJSONFile))
if err != nil {
if dErr := fs.storage.DeleteFile(minioMetaBucket, path.Join(tempUploadIDPath, fsMetaJSONFile)); dErr != nil {
return "", toObjectErr(dErr, minioMetaBucket, tempUploadIDPath)
}
return "", toObjectErr(err, minioMetaBucket, uploadIDPath)
}
return newMD5Hex, nil
}
// listObjectParts - wrapper scanning through
// '.minio/multipart/bucket/object/UPLOADID'. Lists all the parts
// saved inside '.minio/multipart/bucket/object/UPLOADID'.
func (fs fsObjects) listObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (ListPartsInfo, error) {
result := ListPartsInfo{}
uploadIDPath := path.Join(mpartMetaPrefix, bucket, object, uploadID)
fsMeta, err := fs.readFSMetadata(minioMetaBucket, uploadIDPath)
if err != nil {
return ListPartsInfo{}, toObjectErr(err, minioMetaBucket, uploadIDPath)
}
// Only parts with higher part numbers will be listed.
partIdx := fsMeta.ObjectPartIndex(partNumberMarker)
parts := fsMeta.Parts
if partIdx != -1 {
parts = fsMeta.Parts[partIdx+1:]
}
count := maxParts
for _, part := range parts {
var fi FileInfo
partNamePath := path.Join(mpartMetaPrefix, bucket, object, uploadID, part.Name)
fi, err = fs.storage.StatFile(minioMetaBucket, partNamePath)
if err != nil {
return ListPartsInfo{}, toObjectErr(err, minioMetaBucket, partNamePath)
}
result.Parts = append(result.Parts, partInfo{
PartNumber: part.Number,
ETag: part.ETag,
LastModified: fi.ModTime,
Size: fi.Size,
})
count--
if count == 0 {
break
}
}
// If listed entries are more than maxParts, we set IsTruncated as true.
if len(parts) > len(result.Parts) {
result.IsTruncated = true
// Make sure to fill next part number marker if IsTruncated is
// true for subsequent listing.
nextPartNumberMarker := result.Parts[len(result.Parts)-1].PartNumber
result.NextPartNumberMarker = nextPartNumberMarker
}
result.Bucket = bucket
result.Object = object
result.UploadID = uploadID
result.MaxParts = maxParts
return result, nil
}
// ListObjectParts - lists all previously uploaded parts for a given
// object and uploadID. Takes additional input of part-number-marker
// to indicate where the listing should begin from.
//
// Implements S3 compatible ListObjectParts API. The resulting
// ListPartsInfo structure is unmarshalled directly into XML and
// replied back to the client.
func (fs fsObjects) ListObjectParts(bucket, object, uploadID string, partNumberMarker, maxParts int) (ListPartsInfo, error) {
// Verify if bucket is valid.
if !IsValidBucketName(bucket) {
return ListPartsInfo{}, BucketNameInvalid{Bucket: bucket}
}
// Verify whether the bucket exists.
if !fs.isBucketExist(bucket) {
return ListPartsInfo{}, BucketNotFound{Bucket: bucket}
}
if !IsValidObjectName(object) {
return ListPartsInfo{}, ObjectNameInvalid{Bucket: bucket, Object: object}
}
// Hold lock so that there is no competing abort-multipart-upload or complete-multipart-upload.
nsMutex.Lock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, object, uploadID))
defer nsMutex.Unlock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, object, uploadID))
if !fs.isUploadIDExists(bucket, object, uploadID) {
return ListPartsInfo{}, InvalidUploadID{UploadID: uploadID}
}
return fs.listObjectParts(bucket, object, uploadID, partNumberMarker, maxParts)
}
// CompleteMultipartUpload - completes an ongoing multipart
// transaction after receiving all the parts indicated by the client.
// Returns an md5sum calculated by concatenating all the individual
// md5sums of all the parts.
//
// Implements S3 compatible Complete multipart API.
func (fs fsObjects) CompleteMultipartUpload(bucket string, object string, uploadID string, parts []completePart) (string, error) {
// Verify if bucket is valid.
if !IsValidBucketName(bucket) {
return "", BucketNameInvalid{Bucket: bucket}
}
// Verify whether the bucket exists.
if !fs.isBucketExist(bucket) {
return "", BucketNotFound{Bucket: bucket}
}
if !IsValidObjectName(object) {
return "", ObjectNameInvalid{
Bucket: bucket,
Object: object,
}
}
uploadIDPath := path.Join(mpartMetaPrefix, bucket, object, uploadID)
// Hold lock so that
// 1) no one aborts this multipart upload
// 2) no one does a parallel complete-multipart-upload on this
// multipart upload
nsMutex.Lock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, object, uploadID))
defer nsMutex.Unlock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, object, uploadID))
if !fs.isUploadIDExists(bucket, object, uploadID) {
return "", InvalidUploadID{UploadID: uploadID}
}
// Read saved fs metadata for ongoing multipart.
fsMeta, err := fs.readFSMetadata(minioMetaBucket, uploadIDPath)
if err != nil {
return "", toObjectErr(err, minioMetaBucket, uploadIDPath)
}
// Calculate s3 compatible md5sum for complete multipart.
s3MD5, err := completeMultipartMD5(parts...)
if err != nil {
return "", err
}
tempObj := path.Join(tmpMetaPrefix, uploadID, "object1")
var buffer = make([]byte, blockSizeV1)
// Loop through all parts, validate them and then commit to disk.
for i, part := range parts {
partIdx := fsMeta.ObjectPartIndex(part.PartNumber)
if partIdx == -1 {
return "", InvalidPart{}
}
if fsMeta.Parts[partIdx].ETag != part.ETag {
return "", BadDigest{}
}
// All parts except the last part has to be atleast 5MB.
if (i < len(parts)-1) && !isMinAllowedPartSize(fsMeta.Parts[partIdx].Size) {
return "", PartTooSmall{}
}
// Construct part suffix.
partSuffix := fmt.Sprintf("object%d", part.PartNumber)
multipartPartFile := path.Join(mpartMetaPrefix, bucket, object, uploadID, partSuffix)
offset := int64(0)
totalLeft := fsMeta.Parts[partIdx].Size
for totalLeft > 0 {
var n int64
n, err = fs.storage.ReadFile(minioMetaBucket, multipartPartFile, offset, buffer)
if err != nil {
if err == errFileNotFound {
return "", InvalidPart{}
}
return "", toObjectErr(err, minioMetaBucket, multipartPartFile)
}
n, err = fs.storage.AppendFile(minioMetaBucket, tempObj, buffer[:n])
if err != nil {
return "", toObjectErr(err, minioMetaBucket, tempObj)
}
offset += n
totalLeft -= n
}
}
// Rename the file back to original location, if not delete the temporary object.
err = fs.storage.RenameFile(minioMetaBucket, tempObj, bucket, object)
if err != nil {
if dErr := fs.storage.DeleteFile(minioMetaBucket, tempObj); dErr != nil {
return "", toObjectErr(dErr, minioMetaBucket, tempObj)
}
return "", toObjectErr(err, bucket, object)
}
// Cleanup all the parts if everything else has been safely committed.
if err = cleanupUploadedParts(bucket, object, uploadID, fs.storage); err != nil {
return "", err
}
// Hold the lock so that two parallel complete-multipart-uploads do not
// leave a stale uploads.json behind.
nsMutex.Lock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, object))
defer nsMutex.Unlock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, object))
// Validate if there are other incomplete upload-id's present for
// the object, if yes do not attempt to delete 'uploads.json'.
uploadsJSON, err := readUploadsJSON(bucket, object, fs.storage)
if err != nil {
return "", toObjectErr(err, minioMetaBucket, object)
}
// If we have successfully read `uploads.json`, then we proceed to
// purge or update `uploads.json`.
uploadIDIdx := uploadsJSON.Index(uploadID)
if uploadIDIdx != -1 {
uploadsJSON.Uploads = append(uploadsJSON.Uploads[:uploadIDIdx], uploadsJSON.Uploads[uploadIDIdx+1:]...)
}
if len(uploadsJSON.Uploads) > 0 {
if err = updateUploadsJSON(bucket, object, uploadsJSON, fs.storage); err != nil {
return "", toObjectErr(err, minioMetaBucket, path.Join(mpartMetaPrefix, bucket, object))
}
// Return success.
return s3MD5, nil
}
if err = fs.storage.DeleteFile(minioMetaBucket, path.Join(mpartMetaPrefix, bucket, object, uploadsJSONFile)); err != nil {
return "", toObjectErr(err, minioMetaBucket, path.Join(mpartMetaPrefix, bucket, object))
}
// Return md5sum.
return s3MD5, nil
}
// abortMultipartUpload - wrapper for purging an ongoing multipart
// transaction, deletes uploadID entry from `uploads.json` and purges
// the directory at '.minio/multipart/bucket/object/uploadID' holding
// all the upload parts.
func (fs fsObjects) abortMultipartUpload(bucket, object, uploadID string) error {
// Cleanup all uploaded parts.
if err := cleanupUploadedParts(bucket, object, uploadID, fs.storage); err != nil {
return err
}
// Validate if there are other incomplete upload-id's present for
// the object, if yes do not attempt to delete 'uploads.json'.
uploadsJSON, err := readUploadsJSON(bucket, object, fs.storage)
if err == nil {
uploadIDIdx := uploadsJSON.Index(uploadID)
if uploadIDIdx != -1 {
uploadsJSON.Uploads = append(uploadsJSON.Uploads[:uploadIDIdx], uploadsJSON.Uploads[uploadIDIdx+1:]...)
}
// There are pending uploads for the same object, preserve
// them update 'uploads.json' in-place.
if len(uploadsJSON.Uploads) > 0 {
err = updateUploadsJSON(bucket, object, uploadsJSON, fs.storage)
if err != nil {
return toObjectErr(err, bucket, object)
}
return nil
}
} // No more pending uploads for the object, we purge the entire
// entry at '.minio/multipart/bucket/object'.
if err = fs.storage.DeleteFile(minioMetaBucket, path.Join(mpartMetaPrefix, bucket, object, uploadsJSONFile)); err != nil {
return toObjectErr(err, minioMetaBucket, path.Join(mpartMetaPrefix, bucket, object))
}
return nil
}
// AbortMultipartUpload - aborts an ongoing multipart operation
// signified by the input uploadID. This is an atomic operation
// doesn't require clients to initiate multiple such requests.
//
// All parts are purged from all disks and reference to the uploadID
// would be removed from the system, rollback is not possible on this
// operation.
//
// Implements S3 compatible Abort multipart API, slight difference is
// that this is an atomic idempotent operation. Subsequent calls have
// no affect and further requests to the same uploadID would not be
// honored.
func (fs fsObjects) AbortMultipartUpload(bucket, object, uploadID string) error {
// Verify if bucket is valid.
if !IsValidBucketName(bucket) {
return BucketNameInvalid{Bucket: bucket}
}
if !fs.isBucketExist(bucket) {
return BucketNotFound{Bucket: bucket}
}
if !IsValidObjectName(object) {
return ObjectNameInvalid{Bucket: bucket, Object: object}
}
// Hold lock so that there is no competing complete-multipart-upload or put-object-part.
nsMutex.Lock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, object, uploadID))
defer nsMutex.Unlock(minioMetaBucket, pathJoin(mpartMetaPrefix, bucket, object, uploadID))
if !fs.isUploadIDExists(bucket, object, uploadID) {
return InvalidUploadID{UploadID: uploadID}
}
err := fs.abortMultipartUpload(bucket, object, uploadID)
return err
}
+435
View File
@@ -0,0 +1,435 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import (
"crypto/md5"
"encoding/hex"
"io"
"os"
"path"
"path/filepath"
"sort"
"strings"
"sync"
"github.com/minio/minio/pkg/disk"
"github.com/minio/minio/pkg/mimedb"
)
// fsObjects - Implements fs object layer.
type fsObjects struct {
storage StorageAPI
physicalDisk string
listObjectMap map[listParams][]*treeWalkerFS
listObjectMapMutex *sync.Mutex
}
// newFSObjects - initialize new fs object layer.
func newFSObjects(disk string) (ObjectLayer, error) {
storage, err := newStorageAPI(disk)
if err != nil {
return nil, err
}
// Runs house keeping code, like creating minioMetaBucket, cleaning up tmp files etc.
fsHouseKeeping(storage)
// Return successfully initialized object layer.
return fsObjects{
storage: storage,
physicalDisk: disk,
listObjectMap: make(map[listParams][]*treeWalkerFS),
listObjectMapMutex: &sync.Mutex{},
}, nil
}
// StorageInfo - returns underlying storage statistics.
func (fs fsObjects) StorageInfo() StorageInfo {
info, err := disk.GetInfo(fs.physicalDisk)
fatalIf(err, "Unable to get disk info "+fs.physicalDisk)
return StorageInfo{
Total: info.Total,
Free: info.Free,
}
}
/// Bucket operations
// MakeBucket - make a bucket.
func (fs fsObjects) MakeBucket(bucket string) error {
// Verify if bucket is valid.
if !IsValidBucketName(bucket) {
return BucketNameInvalid{Bucket: bucket}
}
if err := fs.storage.MakeVol(bucket); err != nil {
return toObjectErr(err, bucket)
}
return nil
}
// GetBucketInfo - get bucket info.
func (fs fsObjects) GetBucketInfo(bucket string) (BucketInfo, error) {
// Verify if bucket is valid.
if !IsValidBucketName(bucket) {
return BucketInfo{}, BucketNameInvalid{Bucket: bucket}
}
vi, err := fs.storage.StatVol(bucket)
if err != nil {
return BucketInfo{}, toObjectErr(err, bucket)
}
return BucketInfo{
Name: bucket,
Created: vi.Created,
}, nil
}
// ListBuckets - list buckets.
func (fs fsObjects) ListBuckets() ([]BucketInfo, error) {
var bucketInfos []BucketInfo
vols, err := fs.storage.ListVols()
if err != nil {
return nil, toObjectErr(err)
}
for _, vol := range vols {
// StorageAPI can send volume names which are incompatible
// with buckets, handle it and skip them.
if !IsValidBucketName(vol.Name) {
continue
}
bucketInfos = append(bucketInfos, BucketInfo{
Name: vol.Name,
Created: vol.Created,
})
}
sort.Sort(byBucketName(bucketInfos))
return bucketInfos, nil
}
// DeleteBucket - delete a bucket.
func (fs fsObjects) DeleteBucket(bucket string) error {
// Verify if bucket is valid.
if !IsValidBucketName(bucket) {
return BucketNameInvalid{Bucket: bucket}
}
if err := fs.storage.DeleteVol(bucket); err != nil {
return toObjectErr(err, bucket)
}
return nil
}
/// Object Operations
// GetObject - get an object.
func (fs fsObjects) GetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) error {
// Verify if bucket is valid.
if !IsValidBucketName(bucket) {
return BucketNameInvalid{Bucket: bucket}
}
// Verify if object is valid.
if !IsValidObjectName(object) {
return ObjectNameInvalid{Bucket: bucket, Object: object}
}
var totalLeft = length
for totalLeft > 0 {
// Figure out the right blockSize as it was encoded before.
var curBlockSize int64
if blockSizeV1 < totalLeft {
curBlockSize = blockSizeV1
} else {
curBlockSize = totalLeft
}
buf := make([]byte, curBlockSize)
n, err := fs.storage.ReadFile(bucket, object, startOffset, buf)
if err != nil {
return toObjectErr(err, bucket, object)
}
_, err = writer.Write(buf[:n])
if err != nil {
return toObjectErr(err, bucket, object)
}
totalLeft -= n
startOffset += n
}
return nil
}
// GetObjectInfo - get object info.
func (fs fsObjects) GetObjectInfo(bucket, object string) (ObjectInfo, error) {
// Verify if bucket is valid.
if !IsValidBucketName(bucket) {
return ObjectInfo{}, (BucketNameInvalid{Bucket: bucket})
}
// Verify if object is valid.
if !IsValidObjectName(object) {
return ObjectInfo{}, (ObjectNameInvalid{Bucket: bucket, Object: object})
}
fi, err := fs.storage.StatFile(bucket, object)
if err != nil {
return ObjectInfo{}, toObjectErr(err, bucket, object)
}
contentType := "application/octet-stream"
if objectExt := filepath.Ext(object); objectExt != "" {
content, ok := mimedb.DB[strings.ToLower(strings.TrimPrefix(objectExt, "."))]
if ok {
contentType = content.ContentType
}
}
return ObjectInfo{
Bucket: bucket,
Name: object,
ModTime: fi.ModTime,
Size: fi.Size,
IsDir: fi.Mode.IsDir(),
ContentType: contentType,
MD5Sum: "", // Read from metadata.
}, nil
}
// PutObject - create an object.
func (fs fsObjects) PutObject(bucket string, object string, size int64, data io.Reader, metadata map[string]string) (string, error) {
// Verify if bucket is valid.
if !IsValidBucketName(bucket) {
return "", BucketNameInvalid{Bucket: bucket}
}
if !IsValidObjectName(object) {
return "", ObjectNameInvalid{
Bucket: bucket,
Object: object,
}
}
uniqueID := getUUID()
// Temporary object.
tempObj := path.Join(tmpMetaPrefix, uniqueID)
// Initialize md5 writer.
md5Writer := md5.New()
if size == 0 {
// For size 0 we write a 0byte file.
_, err := fs.storage.AppendFile(minioMetaBucket, tempObj, []byte(""))
if err != nil {
return "", toObjectErr(err, bucket, object)
}
} else {
// Allocate buffer.
buf := make([]byte, blockSizeV1)
for {
n, rErr := data.Read(buf)
if rErr == io.EOF {
break
}
if rErr != nil {
return "", toObjectErr(rErr, bucket, object)
}
// Update md5 writer.
md5Writer.Write(buf[:n])
m, wErr := fs.storage.AppendFile(minioMetaBucket, tempObj, buf[:n])
if wErr != nil {
return "", toObjectErr(wErr, bucket, object)
}
if m != int64(len(buf[:n])) {
return "", toObjectErr(errUnexpected, bucket, object)
}
}
}
newMD5Hex := hex.EncodeToString(md5Writer.Sum(nil))
// md5Hex representation.
var md5Hex string
if len(metadata) != 0 {
md5Hex = metadata["md5Sum"]
}
if md5Hex != "" {
if newMD5Hex != md5Hex {
return "", BadDigest{md5Hex, newMD5Hex}
}
}
err := fs.storage.RenameFile(minioMetaBucket, tempObj, bucket, object)
if err != nil {
return "", toObjectErr(err, bucket, object)
}
// Return md5sum, successfully wrote object.
return newMD5Hex, nil
}
func (fs fsObjects) DeleteObject(bucket, object string) error {
// Verify if bucket is valid.
if !IsValidBucketName(bucket) {
return BucketNameInvalid{Bucket: bucket}
}
if !IsValidObjectName(object) {
return ObjectNameInvalid{Bucket: bucket, Object: object}
}
if err := fs.storage.DeleteFile(bucket, object); err != nil {
return toObjectErr(err, bucket, object)
}
return nil
}
// Checks whether bucket exists.
func isBucketExist(storage StorageAPI, bucketName string) bool {
// Check whether bucket exists.
_, err := storage.StatVol(bucketName)
if err != nil {
if err == errVolumeNotFound {
return false
}
errorIf(err, "Stat failed on bucket "+bucketName+".")
return false
}
return true
}
func (fs fsObjects) listObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) {
// Convert entry to FileInfo
entryToFileInfo := func(entry string) (fileInfo FileInfo, err error) {
if strings.HasSuffix(entry, slashSeparator) {
// Object name needs to be full path.
fileInfo.Name = entry
fileInfo.Mode = os.ModeDir
return
}
if fileInfo, err = fs.storage.StatFile(bucket, entry); err != nil {
return
}
// Object name needs to be full path.
fileInfo.Name = entry
return
}
// Verify if bucket is valid.
if !IsValidBucketName(bucket) {
return ListObjectsInfo{}, BucketNameInvalid{Bucket: bucket}
}
// Verify if bucket exists.
if !isBucketExist(fs.storage, bucket) {
return ListObjectsInfo{}, BucketNotFound{Bucket: bucket}
}
if !IsValidObjectPrefix(prefix) {
return ListObjectsInfo{}, ObjectNameInvalid{Bucket: bucket, Object: prefix}
}
// Verify if delimiter is anything other than '/', which we do not support.
if delimiter != "" && delimiter != slashSeparator {
return ListObjectsInfo{}, UnsupportedDelimiter{
Delimiter: delimiter,
}
}
// Verify if marker has prefix.
if marker != "" {
if !strings.HasPrefix(marker, prefix) {
return ListObjectsInfo{}, InvalidMarkerPrefixCombination{
Marker: marker,
Prefix: prefix,
}
}
}
// With max keys of zero we have reached eof, return right here.
if maxKeys == 0 {
return ListObjectsInfo{}, nil
}
// For delimiter and prefix as '/' we do not list anything at all
// since according to s3 spec we stop at the 'delimiter'
// along // with the prefix. On a flat namespace with 'prefix'
// as '/' we don't have any entries, since all the keys are
// of form 'keyName/...'
if delimiter == slashSeparator && prefix == slashSeparator {
return ListObjectsInfo{}, nil
}
// Over flowing count - reset to maxObjectList.
if maxKeys < 0 || maxKeys > maxObjectList {
maxKeys = maxObjectList
}
// Default is recursive, if delimiter is set then list non recursive.
recursive := true
if delimiter == slashSeparator {
recursive = false
}
walker := fs.lookupTreeWalk(listParams{bucket, recursive, marker, prefix})
if walker == nil {
walker = fs.startTreeWalk(bucket, prefix, marker, recursive, func(bucket, object string) bool {
return !strings.HasSuffix(object, slashSeparator)
})
}
var fileInfos []FileInfo
var eof bool
var nextMarker string
for i := 0; i < maxKeys; {
walkResult, ok := <-walker.ch
if !ok {
// Closed channel.
eof = true
break
}
// For any walk error return right away.
if walkResult.err != nil {
// File not found is a valid case.
if walkResult.err == errFileNotFound {
return ListObjectsInfo{}, nil
}
return ListObjectsInfo{}, toObjectErr(walkResult.err, bucket, prefix)
}
fileInfo, err := entryToFileInfo(walkResult.entry)
if err != nil {
return ListObjectsInfo{}, nil
}
nextMarker = fileInfo.Name
fileInfos = append(fileInfos, fileInfo)
if walkResult.end {
eof = true
break
}
i++
}
params := listParams{bucket, recursive, nextMarker, prefix}
if !eof {
fs.saveTreeWalk(params, walker)
}
result := ListObjectsInfo{IsTruncated: !eof}
for _, fileInfo := range fileInfos {
// With delimiter set we fill in NextMarker and Prefixes.
if delimiter == slashSeparator {
result.NextMarker = fileInfo.Name
if fileInfo.Mode.IsDir() {
result.Prefixes = append(result.Prefixes, fileInfo.Name)
continue
}
}
result.Objects = append(result.Objects, ObjectInfo{
Name: fileInfo.Name,
ModTime: fileInfo.ModTime,
Size: fileInfo.Size,
IsDir: false,
})
}
return result, nil
}
// ListObjects - list all objects.
func (fs fsObjects) ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) {
return fs.listObjects(bucket, prefix, marker, delimiter, maxKeys)
}
+41 -49
View File
@@ -17,7 +17,6 @@
package main
import (
"errors"
"net/http"
"path"
"regexp"
@@ -28,11 +27,6 @@ import (
"github.com/rs/cors"
)
const (
iso8601Format = "20060102T150405Z"
privateBucket = "/minio"
)
// HandlerFunc - useful to chain different middleware http.Handler
type HandlerFunc func(http.Handler) http.Handler
@@ -51,18 +45,23 @@ type redirectHandler struct {
locationPrefix string
}
// Reserved bucket.
const (
reservedBucket = "/minio"
)
func setBrowserRedirectHandler(h http.Handler) http.Handler {
return redirectHandler{handler: h, locationPrefix: privateBucket}
return redirectHandler{handler: h, locationPrefix: reservedBucket}
}
func (h redirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Re-direction handled specifically for browsers.
if strings.Contains(r.Header.Get("User-Agent"), "Mozilla") {
// '/' is redirected to 'locationPrefix/'
// '/rpc' is redirected to 'locationPrefix/rpc'
// '/webrpc' is redirected to 'locationPrefix/webrpc'
// '/login' is redirected to 'locationPrefix/login'
switch r.URL.Path {
case "/", "/rpc", "/login", "/favicon.ico":
case "/", "/webrpc", "/login", "/favicon.ico":
location := h.locationPrefix + r.URL.Path
// Redirect to new location.
http.Redirect(w, r, location, http.StatusTemporaryRedirect)
@@ -84,7 +83,7 @@ func setBrowserCacheControlHandler(h http.Handler) http.Handler {
func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" && strings.Contains(r.Header.Get("User-Agent"), "Mozilla") {
// For all browser requests set appropriate Cache-Control policies
match, e := regexp.MatchString(privateBucket+`/([^/]+\.js|favicon.ico)`, r.URL.Path)
match, e := regexp.MatchString(reservedBucket+`/([^/]+\.js|favicon.ico)`, r.URL.Path)
if e != nil {
writeErrorResponse(w, r, ErrInternalError, r.URL.Path)
return
@@ -93,7 +92,7 @@ func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// For assets set cache expiry of one year. For each release, the name
// of the asset name will change and hence it can not be served from cache.
w.Header().Set("Cache-Control", "max-age=31536000")
} else if strings.HasPrefix(r.URL.Path, privateBucket+"/") {
} else if strings.HasPrefix(r.URL.Path, reservedBucket+"/") {
// For non asset requests we serve index.html which will never be cached.
w.Header().Set("Cache-Control", "no-store")
}
@@ -103,68 +102,60 @@ func (h cacheControlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Adds verification for incoming paths.
type minioPrivateBucketHandler struct {
handler http.Handler
privateBucket string
handler http.Handler
reservedBucket string
}
func setPrivateBucketHandler(h http.Handler) http.Handler {
return minioPrivateBucketHandler{handler: h, privateBucket: privateBucket}
return minioPrivateBucketHandler{handler: h, reservedBucket: reservedBucket}
}
func (h minioPrivateBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// For all non browser requests, reject access to 'privateBucket'.
if !strings.Contains(r.Header.Get("User-Agent"), "Mozilla") && path.Clean(r.URL.Path) == privateBucket {
// For all non browser requests, reject access to 'reservedBucket'.
if !strings.Contains(r.Header.Get("User-Agent"), "Mozilla") && path.Clean(r.URL.Path) == reservedBucket {
writeErrorResponse(w, r, ErrAllAccessDisabled, r.URL.Path)
return
}
h.handler.ServeHTTP(w, r)
}
// Supported incoming date formats.
var timeFormats = []string{
// Supported Amz date formats.
var amzDateFormats = []string{
time.RFC1123,
time.RFC1123Z,
iso8601Format,
// Add new AMZ date formats here.
}
// Attempts to parse date string into known date layouts. Date layouts
// currently supported are
// - ``time.RFC1123``
// - ``time.RFC1123Z``
// - ``iso8601Format``
func parseDate(date string) (parsedTime time.Time, e error) {
for _, layout := range timeFormats {
parsedTime, e = time.Parse(layout, date)
// parseAmzDate - parses date string into supported amz date formats.
func parseAmzDate(amzDateStr string) (amzDate time.Time, apiErr APIErrorCode) {
for _, dateFormat := range amzDateFormats {
amzDate, e := time.Parse(dateFormat, amzDateStr)
if e == nil {
return parsedTime, nil
return amzDate, ErrNone
}
}
return time.Time{}, e
return time.Time{}, ErrMalformedDate
}
// Parse date string from incoming header, current supports and verifies
// follow HTTP headers.
//
// - X-Amz-Date
// - X-Minio-Date
// - Date
//
// In following time layouts ``time.RFC1123``, ``time.RFC1123Z`` and
// ``iso8601Format``.
var dateHeaders = []string{
// Supported Amz date headers.
var amzDateHeaders = []string{
"x-amz-date",
"x-minio-date",
"date",
}
func parseDateHeader(req *http.Request) (time.Time, error) {
for _, dateHeader := range dateHeaders {
date := req.Header.Get(http.CanonicalHeaderKey(dateHeader))
if date != "" {
return parseDate(date)
// parseAmzDateHeader - parses supported amz date headers, in
// supported amz date formats.
func parseAmzDateHeader(req *http.Request) (time.Time, APIErrorCode) {
for _, amzDateHeader := range amzDateHeaders {
amzDateStr := req.Header.Get(http.CanonicalHeaderKey(amzDateHeader))
if amzDateStr != "" {
return parseAmzDate(amzDateStr)
}
}
return time.Time{}, errors.New("Date header missing, invalid request.")
// Date header missing.
return time.Time{}, ErrMissingDateHeader
}
type timeHandler struct {
@@ -178,18 +169,18 @@ func setTimeValidityHandler(h http.Handler) http.Handler {
func (h timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Verify if date headers are set, if not reject the request
if r.Header.Get("Authorization") != "" {
date, e := parseDateHeader(r)
if e != nil {
if _, ok := r.Header["Authorization"]; ok {
amzDate, apiErr := parseAmzDateHeader(r)
if apiErr != ErrNone {
// All our internal APIs are sensitive towards Date
// header, for all requests where Date header is not
// present we will reject such clients.
writeErrorResponse(w, r, ErrRequestTimeTooSkewed, r.URL.Path)
writeErrorResponse(w, r, apiErr, r.URL.Path)
return
}
// Verify if the request date header is more than 5minutes
// late, reject such clients.
if time.Now().UTC().Sub(date)/time.Minute > time.Duration(5)*time.Minute {
if time.Now().UTC().Sub(amzDate)/time.Minute > time.Duration(5)*time.Minute {
writeErrorResponse(w, r, ErrRequestTimeTooSkewed, r.URL.Path)
return
}
@@ -207,6 +198,7 @@ func setCorsHandler(h http.Handler) http.Handler {
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "HEAD", "POST", "PUT"},
AllowedHeaders: []string{"*"},
ExposedHeaders: []string{"ETag"},
})
return c.Handler(h)
}
+27
View File
@@ -16,7 +16,34 @@
package main
import "github.com/fatih/color"
// Global constants for Minio.
const (
minGoVersion = ">= 1.6" // Minio requires at least Go v1.6
)
// minio configuration related constants.
const (
globalMinioConfigVersion = "4"
globalMinioConfigDir = ".minio"
globalMinioCertsDir = ".minio/certs"
globalMinioCertFile = "public.crt"
globalMinioKeyFile = "private.key"
globalMinioConfigFile = "config.json"
globalMinioProfilePath = "profile"
// Add new global values here.
)
var (
globalQuiet = false // Quiet flag set via command line
globalTrace = false // Trace flag set via environment setting.
// Add new global flags here.
)
// global colors.
var (
colorMagenta = color.New(color.FgMagenta, color.Bold).SprintfFunc()
colorWhite = color.New(color.FgWhite, color.Bold).SprintfFunc()
colorGreen = color.New(color.FgGreen, color.Bold).SprintfFunc()
)
+54
View File
@@ -0,0 +1,54 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* 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 main
import (
"io"
)
// validates location constraint from the request body.
// the location value in the request body should match the Region in serverConfig.
// other values of location are not accepted.
// make bucket fails in such cases.
func isValidLocationContraint(reqBody io.Reader, serverRegion string) APIErrorCode {
var locationContraint createBucketLocationConfiguration
var errCode APIErrorCode
errCode = ErrNone
e := xmlDecoder(reqBody, &locationContraint)
if e != nil {
if e == io.EOF {
// Do nothing.
// failed due to empty body. The location will be set to default value from the serverConfig.
// this is valid.
errCode = ErrNone
} else {
// Failed due to malformed configuration.
errCode = ErrMalformedXML
//writeErrorResponse(w, r, ErrMalformedXML, r.URL.Path)
}
} else {
// Region obtained from the body.
// It should be equal to Region in serverConfig.
// Else ErrInvalidRegion returned.
// For empty value location will be to set to default value from the serverConfig.
if locationContraint.Location != "" && serverRegion != locationContraint.Location {
//writeErrorResponse(w, r, ErrInvalidRegion, r.URL.Path)
errCode = ErrInvalidRegion
}
}
return errCode
}
+66
View File
@@ -0,0 +1,66 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* 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 main
import (
"bytes"
"encoding/xml"
"io/ioutil"
"net/http"
"testing"
)
// Tests validate bucket LocationConstraint.
func TestIsValidLocationContraint(t *testing.T) {
// generates the input request with XML bucket configuration set to the request body.
createExpectedRequest := func(req *http.Request, location string) (*http.Request, error) {
createBucketConfig := createBucketLocationConfiguration{}
createBucketConfig.Location = location
var createBucketConfigBytes []byte
createBucketConfigBytes, e := xml.Marshal(createBucketConfig)
if e != nil {
return nil, e
}
createBucketConfigBuffer := bytes.NewBuffer(createBucketConfigBytes)
req.Body = ioutil.NopCloser(createBucketConfigBuffer)
return req, nil
}
testCases := []struct {
locationForInputRequest string
serverConfigRegion string
expectedCode APIErrorCode
}{
// Test case - 1.
{"us-east-1", "us-east-1", ErrNone},
// Test case - 2.
// In case of empty request body ErrNone is returned.
{"", "us-east-1", ErrNone},
// Test case - 3.
{"eu-central-1", "us-east-1", ErrInvalidRegion},
}
for i, testCase := range testCases {
inputRequest, e := createExpectedRequest(&http.Request{}, testCase.locationForInputRequest)
if e != nil {
t.Fatalf("Test %d: Failed to Marshal bucket configuration", i+1)
}
actualCode := isValidLocationContraint(inputRequest.Body, testCase.serverConfigRegion)
if testCase.expectedCode != actualCode {
t.Errorf("Test %d: Expected the APIErrCode to be %d, but instead found %d", i+1, testCase.expectedCode, actualCode)
}
}
}
+20 -16
View File
@@ -21,15 +21,19 @@ import (
"fmt"
"strconv"
"strings"
"github.com/minio/minio/pkg/fs"
"github.com/minio/minio/pkg/probe"
)
const (
b = "bytes="
)
// InvalidRange - invalid range
type InvalidRange struct{}
func (e InvalidRange) Error() string {
return "The requested range is not satisfiable"
}
// HttpRange specifies the byte range to be sent to the client.
type httpRange struct {
start, length, size int64
@@ -41,7 +45,7 @@ func (r *httpRange) String() string {
}
// Grab new range from request header
func getRequestedRange(hrange string, size int64) (*httpRange, *probe.Error) {
func getRequestedRange(hrange string, size int64) (*httpRange, error) {
r := &httpRange{
start: 0,
length: 0,
@@ -51,16 +55,16 @@ func getRequestedRange(hrange string, size int64) (*httpRange, *probe.Error) {
if hrange != "" {
err := r.parseRange(hrange)
if err != nil {
return nil, err.Trace()
return nil, err
}
}
return r, nil
}
func (r *httpRange) parse(ra string) *probe.Error {
func (r *httpRange) parse(ra string) error {
i := strings.Index(ra, "-")
if i < 0 {
return probe.NewError(fs.InvalidRange{})
return InvalidRange{}
}
start, end := strings.TrimSpace(ra[:i]), strings.TrimSpace(ra[i+1:])
if start == "" {
@@ -68,7 +72,7 @@ func (r *httpRange) parse(ra string) *probe.Error {
// range start relative to the end of the file.
i, err := strconv.ParseInt(end, 10, 64)
if err != nil {
return probe.NewError(fs.InvalidRange{})
return InvalidRange{}
}
if i > r.size {
i = r.size
@@ -78,7 +82,7 @@ func (r *httpRange) parse(ra string) *probe.Error {
} else {
i, err := strconv.ParseInt(start, 10, 64)
if err != nil || i > r.size || i < 0 {
return probe.NewError(fs.InvalidRange{})
return InvalidRange{}
}
r.start = i
if end == "" {
@@ -87,7 +91,7 @@ func (r *httpRange) parse(ra string) *probe.Error {
} else {
i, err := strconv.ParseInt(end, 10, 64)
if err != nil || r.start > i {
return probe.NewError(fs.InvalidRange{})
return InvalidRange{}
}
if i >= r.size {
i = r.size - 1
@@ -99,26 +103,26 @@ func (r *httpRange) parse(ra string) *probe.Error {
}
// parseRange parses a Range header string as per RFC 2616.
func (r *httpRange) parseRange(s string) *probe.Error {
func (r *httpRange) parseRange(s string) error {
if s == "" {
return probe.NewError(errors.New("header not present"))
return errors.New("header not present")
}
if !strings.HasPrefix(s, b) {
return probe.NewError(fs.InvalidRange{})
return InvalidRange{}
}
ras := strings.Split(s[len(b):], ",")
if len(ras) == 0 {
return probe.NewError(errors.New("invalid request"))
return errors.New("invalid request")
}
// Just pick the first one and ignore the rest, we only support one range per object
if len(ras) > 1 {
return probe.NewError(errors.New("multiple ranges specified"))
return errors.New("multiple ranges specified")
}
ra := strings.TrimSpace(ras[0])
if ra == "" {
return probe.NewError(fs.InvalidRange{})
return InvalidRange{}
}
return r.parse(ra)
}
+46
View File
@@ -0,0 +1,46 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* 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 main
import (
"io/ioutil"
"github.com/Sirupsen/logrus"
)
// consoleLogger - default logger if not other logging is enabled.
type consoleLogger struct {
Enable bool `json:"enable"`
Level string `json:"level"`
}
// enable console logger.
func enableConsoleLogger() {
clogger := serverConfig.GetConsoleLogger()
if !clogger.Enable {
// Disable console logger if asked for.
log.Out = ioutil.Discard
return
}
// log.Out and log.Formatter use the default versions.
// Only set specific log level.
lvl, err := logrus.ParseLevel(clogger.Level)
fatalIf(err, "Unknown log level found in the config file.")
log.Level = lvl
}
+26 -22
View File
@@ -1,5 +1,5 @@
/*
* Minio Cloud Storage, (C) 2015 Minio, Inc.
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,32 +21,39 @@ import (
"os"
"github.com/Sirupsen/logrus"
"github.com/minio/minio/pkg/probe"
)
type fileLogger struct {
Enable bool `json:"enable"`
Filename string `json:"fileName"`
Level string `json:"level"`
}
type localFile struct {
*os.File
}
func log2File(filename string) *probe.Error {
fileHook, e := newFile(filename)
if e != nil {
return probe.NewError(e)
func enableFileLogger() {
flogger := serverConfig.GetFileLogger()
if !flogger.Enable || flogger.Filename == "" {
return
}
log.Hooks.Add(fileHook) // Add a local file hook.
log.Formatter = &logrus.JSONFormatter{} // JSON formatted log.
log.Level = logrus.InfoLevel // Minimum log level.
return nil
}
func newFile(filename string) (*localFile, error) {
file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if err != nil {
return nil, err
}
return &localFile{file}, nil
file, err := os.OpenFile(flogger.Filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
fatalIf(err, "Unable to open log file.")
// Add a local file hook.
log.Hooks.Add(&localFile{file})
lvl, err := logrus.ParseLevel(flogger.Level)
fatalIf(err, "Unknown log level found in the config file.")
// Set default JSON formatter.
log.Formatter = new(logrus.JSONFormatter)
log.Level = lvl // Minimum log level.
}
// Fire fires the file logger hook and logs to the file.
func (l *localFile) Fire(entry *logrus.Entry) error {
line, err := entry.String()
if err != nil {
@@ -57,14 +64,11 @@ func (l *localFile) Fire(entry *logrus.Entry) error {
return nil
}
// Levels -
// Levels - indicate log levels supported.
func (l *localFile) Levels() []logrus.Level {
return []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
logrus.InfoLevel,
logrus.DebugLevel,
}
}
-75
View File
@@ -1,75 +0,0 @@
/*
* Minio Cloud Storage, (C) 2015 Minio, Inc.
*
* 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 main
import (
"fmt"
"github.com/Sirupsen/logrus"
"github.com/minio/minio/pkg/probe"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
// mongoDB collection
type mongoDB struct {
c *mgo.Collection
}
func log2Mongo(url, db, collection string) *probe.Error {
mongoHook, e := newMongo(url, db, collection)
if e != nil {
return probe.NewError(e)
}
log.Hooks.Add(mongoHook) // Add mongodb hook.
log.Formatter = &logrus.JSONFormatter{} // JSON formatted log.
log.Level = logrus.InfoLevel // Minimum log level.
return nil
}
// newMongo -
func newMongo(mgoEndpoint, db, collection string) (*mongoDB, error) {
session, err := mgo.Dial(mgoEndpoint)
if err != nil {
return nil, err
}
return &mongoDB{c: session.DB(db).C(collection)}, nil
}
// Fire - the log event
func (h *mongoDB) Fire(entry *logrus.Entry) error {
entry.Data["Level"] = entry.Level.String()
entry.Data["Time"] = entry.Time
entry.Data["Message"] = entry.Message
mgoErr := h.c.Insert(bson.M(entry.Data))
if mgoErr != nil {
return fmt.Errorf("Failed to send log entry to mongodb: %s", mgoErr)
}
return nil
}
// Levels -
func (h *mongoDB) Levels() []logrus.Level {
return []logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
logrus.InfoLevel,
logrus.DebugLevel,
}
}
+13 -18
View File
@@ -1,7 +1,7 @@
// +build !windows
/*
* Minio Cloud Storage, (C) 2015 Minio, Inc.
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,9 +23,14 @@ import (
"log/syslog"
"github.com/Sirupsen/logrus"
"github.com/minio/minio/pkg/probe"
)
type syslogLogger struct {
Enable bool `json:"enable"`
Addr string `json:"address"`
Level string `json:"level"`
}
// syslogHook to send logs via syslog.
type syslogHook struct {
writer *syslog.Writer
@@ -33,15 +38,14 @@ type syslogHook struct {
syslogRaddr string
}
func log2Syslog(network, raddr string) *probe.Error {
syslogHook, e := newSyslog(network, raddr, syslog.LOG_ERR, "MINIO")
if e != nil {
return probe.NewError(e)
}
// enableSyslogLogger - enable logger at raddr.
func enableSyslogLogger(raddr string) {
syslogHook, err := newSyslog("udp", raddr, syslog.LOG_ERR, "MINIO")
fatalIf(err, "Unable to initialize syslog logger.")
log.Hooks.Add(syslogHook) // Add syslog hook.
log.Formatter = &logrus.JSONFormatter{} // JSON formatted log.
log.Level = logrus.InfoLevel // Minimum log level.
return nil
log.Level = logrus.ErrorLevel // Minimum log level.
}
// newSyslog - Creates a hook to be added to an instance of logger.
@@ -63,12 +67,6 @@ func (hook *syslogHook) Fire(entry *logrus.Entry) error {
return hook.writer.Crit(line)
case logrus.ErrorLevel:
return hook.writer.Err(line)
case logrus.WarnLevel:
return hook.writer.Warning(line)
case logrus.InfoLevel:
return hook.writer.Info(line)
case logrus.DebugLevel:
return hook.writer.Debug(line)
default:
return nil
}
@@ -80,8 +78,5 @@ func (hook *syslogHook) Levels() []logrus.Level {
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
logrus.WarnLevel,
logrus.InfoLevel,
logrus.DebugLevel,
}
}
+10 -5
View File
@@ -1,7 +1,7 @@
// +build windows
/*
* Minio Cloud Storage, (C) 2015 Minio, Inc.
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,8 +18,13 @@
package main
import "github.com/minio/minio/pkg/probe"
func log2Syslog(network, raddr string) *probe.Error {
return probe.NewError(errSysLogNotSupported)
type syslogLogger struct {
Enable bool `json:"enable"`
Addr string `json:"address"`
Level string `json:"level"`
}
// enableSyslogLogger - unsupported on windows.
func enableSyslogLogger(raddr string) {
fatalIf(errSyslogNotSupported, "Unable to enable syslog.")
}
+81 -25
View File
@@ -1,5 +1,5 @@
/*
* Minio Cloud Storage, (C) 2015 Minio, Inc.
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,49 +17,105 @@
package main
import (
"encoding/json"
"bufio"
"bytes"
"os"
"reflect"
"runtime"
"runtime/debug"
"strconv"
"strings"
"github.com/Sirupsen/logrus"
"github.com/minio/minio/pkg/probe"
"github.com/dustin/go-humanize"
)
type fields map[string]interface{}
var log = logrus.New() // Default console logger.
func errorIf(err *probe.Error, msg string, fields map[string]interface{}) {
// logger carries logging configuration for various supported loggers.
// Currently supported loggers are
//
// - console [default]
// - file
// - syslog
//
type logger struct {
Console consoleLogger `json:"console"`
File fileLogger `json:"file"`
Syslog syslogLogger `json:"syslog"`
// Add new loggers here.
}
// sysInfo returns useful system statistics.
func sysInfo() map[string]string {
host, err := os.Hostname()
if err != nil {
host = ""
}
memstats := &runtime.MemStats{}
runtime.ReadMemStats(memstats)
return map[string]string{
"host.name": host,
"host.os": runtime.GOOS,
"host.arch": runtime.GOARCH,
"host.lang": runtime.Version(),
"host.cpus": strconv.Itoa(runtime.NumCPU()),
"mem.used": humanize.Bytes(memstats.Alloc),
"mem.total": humanize.Bytes(memstats.Sys),
"mem.heap.used": humanize.Bytes(memstats.HeapAlloc),
"mem.heap.total": humanize.Bytes(memstats.HeapSys),
}
}
// stackInfo returns printable stack trace.
func stackInfo() string {
// Convert stack-trace bytes to io.Reader.
rawStack := bufio.NewReader(bytes.NewBuffer(debug.Stack()))
// Skip stack trace lines until our real caller.
for i := 0; i <= 4; i++ {
rawStack.ReadLine()
}
// Read the rest of useful stack trace.
stackBuf := new(bytes.Buffer)
stackBuf.ReadFrom(rawStack)
// Strip GOPATH of the build system and return.
return strings.Replace(stackBuf.String(), minioGOPATH+"/src/", "", -1)
}
// errorIf synonymous with fatalIf but doesn't exit on error != nil
func errorIf(err error, msg string, data ...interface{}) {
if err == nil {
return
}
if fields == nil {
fields = make(map[string]interface{})
sysInfo := sysInfo()
fields := logrus.Fields{
"cause": err.Error(),
"type": reflect.TypeOf(err),
"sysInfo": sysInfo,
}
fields["Error"] = struct {
Cause string `json:"cause,omitempty"`
Type string `json:"type,omitempty"`
CallTrace []probe.TracePoint `json:"trace,omitempty"`
SysInfo map[string]string `json:"sysinfo,omitempty"`
}{
err.Cause.Error(),
reflect.TypeOf(err.Cause).String(),
err.CallTrace,
err.SysInfo,
if globalTrace {
fields["stack"] = "\n" + stackInfo()
}
log.WithFields(fields).Error(msg)
log.WithFields(fields).Errorf(msg, data...)
}
func fatalIf(err *probe.Error, msg string, fields map[string]interface{}) {
// fatalIf wrapper function which takes error and prints jsonic error messages.
func fatalIf(err error, msg string, data ...interface{}) {
if err == nil {
return
}
if fields == nil {
fields = make(map[string]interface{})
sysInfo := sysInfo()
fields := logrus.Fields{
"cause": err.Error(),
"type": reflect.TypeOf(err),
"sysInfo": sysInfo,
}
fields["error"] = err.ToGoError()
if jsonErr, e := json.Marshal(err); e == nil {
fields["probe"] = string(jsonErr)
if globalTrace {
fields["stack"] = "\n" + stackInfo()
}
log.WithFields(fields).Fatal(msg)
log.WithFields(fields).Fatalf(msg, data...)
}
+3 -4
View File
@@ -22,7 +22,6 @@ import (
"errors"
"github.com/Sirupsen/logrus"
"github.com/minio/minio/pkg/probe"
. "gopkg.in/check.v1"
)
@@ -37,12 +36,12 @@ func (s *LoggerSuite) TestLogger(c *C) {
log.Out = &buffer
log.Formatter = new(logrus.JSONFormatter)
errorIf(probe.NewError(errors.New("Fake error")), "Failed with error.", nil)
errorIf(errors.New("Fake error"), "Failed with error.")
err := json.Unmarshal(buffer.Bytes(), &fields)
c.Assert(err, IsNil)
c.Assert(fields["level"], Equals, "error")
msg, ok := fields["Error"]
msg, ok := fields["cause"]
c.Assert(ok, Equals, true)
c.Assert(msg.(map[string]interface{})["cause"], Equals, "Fake error")
c.Assert(msg, Equals, "Fake error")
}
+72 -55
View File
@@ -1,5 +1,5 @@
/*
* Minio Cloud Storage, (C) 2015 Minio, Inc.
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,14 +19,22 @@ package main
import (
"fmt"
"os"
"runtime"
"path/filepath"
"sort"
"strconv"
"github.com/dustin/go-humanize"
"github.com/minio/cli"
"github.com/minio/mc/pkg/console"
"github.com/minio/minio/pkg/probe"
"github.com/pkg/profile"
)
var (
// global flags for minio.
minioFlags = []cli.Flag{
cli.BoolFlag{
Name: "help, h",
Usage: "Show help.",
},
}
)
// Help template for minio.
@@ -47,53 +55,36 @@ FLAGS:
{{end}}{{end}}
VERSION:
` + minioVersion +
`{{ "\n"}}{{range $key, $value := ExtraInfo}}
{{$key}}:
{{$value}}
{{end}}`
`{{ "\n"}}`
// init - check the environment before main starts
func init() {
// Check if minio was compiled using a supported version of Golang.
checkGoVersion()
// Set global trace flag.
globalTrace = os.Getenv("MINIO_TRACE") == "1"
// It is an unsafe practice to run network services as
// root. Containers are an exception.
if !isContainerized() && os.Geteuid() == 0 {
console.Fatalln("Please run minio as a non-root user.")
}
}
func migrate() {
// Migrate config file
migrateConfig()
// Migrate other configs here.
}
// Tries to get os/arch/platform specific information
// Returns a map of current os/arch/platform/memstats
func getSystemData() map[string]string {
host, err := os.Hostname()
if err != nil {
host = ""
}
memstats := &runtime.MemStats{}
runtime.ReadMemStats(memstats)
mem := fmt.Sprintf("Used: %s | Allocated: %s | Used-Heap: %s | Allocated-Heap: %s",
humanize.Bytes(memstats.Alloc),
humanize.Bytes(memstats.TotalAlloc),
humanize.Bytes(memstats.HeapAlloc),
humanize.Bytes(memstats.HeapSys))
platform := fmt.Sprintf("Host: %s | OS: %s | Arch: %s",
host,
runtime.GOOS,
runtime.GOARCH)
goruntime := fmt.Sprintf("Version: %s | CPUs: %s", runtime.Version(), strconv.Itoa(runtime.NumCPU()))
return map[string]string{
"PLATFORM": platform,
"RUNTIME": goruntime,
"MEM": mem,
}
func enableLoggers() {
// Enable all loggers here.
enableConsoleLogger()
enableFileLogger()
// Add your logger here.
}
func findClosestCommands(command string) []string {
@@ -118,26 +109,18 @@ func findClosestCommands(command string) []string {
}
func registerApp() *cli.App {
// register all commands
// Register all commands.
registerCommand(serverCmd)
registerCommand(configCmd)
registerCommand(versionCmd)
registerCommand(updateCmd)
// register all flags
registerFlag(configFolderFlag)
registerFlag(addressFlag)
registerFlag(accessLogFlag)
registerFlag(certFlag)
registerFlag(keyFlag)
// set up app
// Set up app.
app := cli.NewApp()
app.Name = "Minio"
app.Author = "Minio.io"
app.Usage = "Cloud Storage Server for Micro Services."
app.Description = `Micro services environment provisions one Minio server per application instance. Scalability is achieved through large number of smaller personalized instances. This version of the Minio binary is built using Filesystem storage backend for magnetic and solid state disks.`
app.Flags = flags
app.Usage = "Cloud Storage Server."
app.Description = `Minio is an Amazon S3 compatible object storage server. Use it to store photos, videos, VMs, containers, log files, or any blob of data as objects.`
app.Flags = append(minioFlags, globalFlags...)
app.Commands = commands
app.CustomAppHelpTemplate = minioHelpTemplate
app.CommandNotFound = func(ctx *cli.Context, command string) {
@@ -160,19 +143,20 @@ func checkMainSyntax(c *cli.Context) {
console.Fatalf("Unable to obtain user's home directory. \nError: %s\n", err)
}
if configPath == "" {
console.Fatalln("Config folder cannot be empty, please specify --config-folder <foldername>.")
console.Fatalln("Config folder cannot be empty, please specify --config-dir <foldername>.")
}
}
func main() {
probe.Init() // Set project's root source path.
probe.SetAppInfo("Release-Tag", minioReleaseTag)
probe.SetAppInfo("Commit-ID", minioShortCommitID)
// mustGetProfilePath must get location that the profile will be written to.
func mustGetProfilePath() string {
return filepath.Join(mustGetConfigPath(), globalMinioProfilePath)
}
func main() {
app := registerApp()
app.Before = func(c *cli.Context) error {
// Sets new config folder.
setGlobalConfigPath(c.GlobalString("config-folder"))
setGlobalConfigPath(c.GlobalString("config-dir"))
// Valid input arguments to main.
checkMainSyntax(c)
@@ -180,11 +164,44 @@ func main() {
// Migrate any old version of config / state files to newer format.
migrate()
// Initialize config.
err := initConfig()
fatalIf(err, "Unable to initialize minio config.")
// Enable all loggers by now.
enableLoggers()
// Initialize name space lock.
initNSLock()
// Set global quiet flag.
globalQuiet = c.Bool("quiet") || c.GlobalBool("quiet")
// Do not print update messages, if quiet flag is set.
if !globalQuiet {
// Do not print any errors in release update function.
noError := true
updateMsg := getReleaseUpdate(minioUpdateStableURL, noError)
if updateMsg.Update {
console.Println(updateMsg)
}
}
return nil
}
app.ExtraInfo = func() map[string]string {
return getSystemData()
// Set ``MINIO_PROFILE_DIR`` to the directory where profiling information should be persisted
profileDir := os.Getenv("MINIO_PROFILE_DIR")
// Enable profiler if ``MINIO_PROFILER`` is set. Supported options are [cpu, mem, block].
switch os.Getenv("MINIO_PROFILER") {
case "cpu":
defer profile.Start(profile.CPUProfile, profile.ProfilePath(profileDir)).Stop()
case "mem":
defer profile.Start(profile.MemProfile, profile.ProfilePath(profileDir)).Stop()
case "block":
defer profile.Start(profile.BlockProfile, profile.ProfilePath(profileDir)).Stop()
}
// Run the app - exit on error.
app.RunAndExitOnError()
}
+128
View File
@@ -0,0 +1,128 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import (
"errors"
"sync"
)
// nsParam - carries name space resource.
type nsParam struct {
volume string
path string
}
// nsLock - provides primitives for locking critical namespace regions.
type nsLock struct {
*sync.RWMutex
ref uint
}
// nsLockMap - namespace lock map, provides primitives to Lock,
// Unlock, RLock and RUnlock.
type nsLockMap struct {
lockMap map[nsParam]*nsLock
mutex *sync.Mutex
}
// Global name space lock.
var nsMutex *nsLockMap
// initNSLock - initialize name space lock map.
func initNSLock() {
nsMutex = &nsLockMap{
lockMap: make(map[nsParam]*nsLock),
mutex: &sync.Mutex{},
}
}
// Lock the namespace resource.
func (n *nsLockMap) lock(volume, path string, readLock bool) {
n.mutex.Lock()
param := nsParam{volume, path}
nsLk, found := n.lockMap[param]
if !found {
nsLk = &nsLock{
RWMutex: &sync.RWMutex{},
ref: 0,
}
n.lockMap[param] = nsLk
}
nsLk.ref++ // Update ref count here to avoid multiple races.
// Unlock map before Locking NS which might block.
n.mutex.Unlock()
// Locking here can block.
if readLock {
nsLk.RLock()
} else {
nsLk.Lock()
}
}
// Unlock the namespace resource.
func (n *nsLockMap) unlock(volume, path string, readLock bool) {
// nsLk.Unlock() will not block, hence locking the map for the entire function is fine.
n.mutex.Lock()
defer n.mutex.Unlock()
param := nsParam{volume, path}
if nsLk, found := n.lockMap[param]; found {
if readLock {
nsLk.RUnlock()
} else {
nsLk.Unlock()
}
if nsLk.ref == 0 {
errorIf(errors.New("Namespace reference count cannot be 0."), "Invalid reference count detected.")
}
if nsLk.ref != 0 {
nsLk.ref--
}
if nsLk.ref == 0 {
// Remove from the map if there are no more references.
delete(n.lockMap, param)
}
}
}
// Lock - locks the given resource for writes, using a previously
// allocated name space lock or initializing a new one.
func (n *nsLockMap) Lock(volume, path string) {
readLock := false
n.lock(volume, path, readLock)
}
// Unlock - unlocks any previously acquired write locks.
func (n *nsLockMap) Unlock(volume, path string) {
readLock := false
n.unlock(volume, path, readLock)
}
// RLock - locks any previously acquired read locks.
func (n *nsLockMap) RLock(volume, path string) {
readLock := true
n.lock(volume, path, readLock)
}
// RUnlock - unlocks any previously acquired read locks.
func (n *nsLockMap) RUnlock(volume, path string) {
readLock := true
n.unlock(volume, path, readLock)
}
+18 -25
View File
@@ -18,69 +18,62 @@ package main
import (
"fmt"
"math"
"runtime"
"strings"
"github.com/fatih/color"
"github.com/minio/minio/pkg/probe"
"github.com/olekukonko/ts"
)
// colorizeUpdateMessage - inspired from Yeoman project npm package https://github.com/yeoman/update-notifier
func colorizeUpdateMessage(updateString string) (string, *probe.Error) {
// initialize coloring
func colorizeUpdateMessage(updateString string) (string, error) {
// Initialize coloring.
cyan := color.New(color.FgCyan, color.Bold).SprintFunc()
yellow := color.New(color.FgYellow, color.Bold).SprintfFunc()
// calculate length without color coding, due to ANSI color characters padded to actual
// string the final length is wrong than the original string length
line1Str := fmt.Sprintf(" Update available: ")
line2Str := fmt.Sprintf(" Run \"%s\" to update. ", updateString)
// Calculate length without color coding, due to ANSI color
// characters padded to actual string the final length is wrong
// than the original string length.
line1Str := fmt.Sprintf(" New update: %s ", updateString)
line1Length := len(line1Str)
line2Length := len(line2Str)
// populate lines with color coding
line1InColor := line1Str
line2InColor := fmt.Sprintf(" Run \"%s\" to update. ", cyan(updateString))
// Populate lines with color coding.
line1InColor := fmt.Sprintf(" New update: %s ", cyan(updateString))
// calculate the rectangular box size
maxContentWidth := int(math.Max(float64(line1Length), float64(line2Length)))
// Calculate the rectangular box size.
maxContentWidth := line1Length
line1Rest := maxContentWidth - line1Length
line2Rest := maxContentWidth - line2Length
terminal, err := ts.GetSize()
if err != nil {
return "", probe.NewError(err)
return "", err
}
var message string
switch {
case len(line2Str) > terminal.Col():
message = "\n" + line1InColor + "\n" + line2InColor + "\n"
case len(line1Str) > terminal.Col():
message = "\n" + line1InColor + "\n"
default:
// on windows terminal turn off unicode characters
// On windows terminal turn off unicode characters.
var top, bottom, sideBar string
if runtime.GOOS == "windows" {
top = yellow("*" + strings.Repeat("*", maxContentWidth) + "*")
bottom = yellow("*" + strings.Repeat("*", maxContentWidth) + "*")
sideBar = yellow("|")
} else {
// color the rectangular box, use unicode characters here
// Color the rectangular box, use unicode characters here.
top = yellow("┏" + strings.Repeat("━", maxContentWidth) + "┓")
bottom = yellow("┗" + strings.Repeat("━", maxContentWidth) + "┛")
sideBar = yellow("┃")
}
// fill spaces to the rest of the area
// Fill spaces to the rest of the area.
spacePaddingLine1 := strings.Repeat(" ", line1Rest)
spacePaddingLine2 := strings.Repeat(" ", line2Rest)
// construct the final message
// Construct the final message.
message = "\n" + top + "\n" +
sideBar + line1InColor + spacePaddingLine1 + sideBar + "\n" +
sideBar + line2InColor + spacePaddingLine2 + sideBar + "\n" +
bottom + "\n"
}
// return the final message
// Return the final message.
return message, nil
}
+156
View File
@@ -0,0 +1,156 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import (
"bytes"
"crypto/md5"
"encoding/hex"
"io/ioutil"
"os"
"strconv"
"testing"
)
// Wrapper for calling GetObjectInfo tests for both XL multiple disks and single node setup.
func TestGetObjectInfo(t *testing.T) {
ExecObjectLayerTest(t, testGetObjectInfo)
}
// Testing GetObjectInfo().
func testGetObjectInfo(obj ObjectLayer, instanceType string, t *testing.T) {
// This bucket is used for testing getObjectInfo operations.
err := obj.MakeBucket("test-getobjectinfo")
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
_, err = obj.PutObject("test-getobjectinfo", "Asia/asiapics.jpg", int64(len("asiapics")), bytes.NewBufferString("asiapics"), nil)
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
resultCases := []ObjectInfo{
// ObjectInfo -1.
// ObjectName set to a existing object in the test case (Test case 14).
{Bucket: "test-getobjectinfo", Name: "Asia/asiapics.jpg", ContentType: "image/jpeg", IsDir: false},
}
testCases := []struct {
bucketName string
objectName string
// Expected output of GetObjectInfo.
result ObjectInfo
err error
// Flag indicating whether the test is expected to pass or not.
shouldPass bool
}{
// Test cases with invalid bucket names ( Test number 1-4 ).
{".test", "", ObjectInfo{}, BucketNameInvalid{Bucket: ".test"}, false},
{"Test", "", ObjectInfo{}, BucketNameInvalid{Bucket: "Test"}, false},
{"---", "", ObjectInfo{}, BucketNameInvalid{Bucket: "---"}, false},
{"ad", "", ObjectInfo{}, BucketNameInvalid{Bucket: "ad"}, false},
// Test cases with valid but non-existing bucket names (Test number 5-7).
{"abcdefgh", "abc", ObjectInfo{}, BucketNotFound{Bucket: "abcdefgh"}, false},
{"ijklmnop", "efg", ObjectInfo{}, BucketNotFound{Bucket: "ijklmnop"}, false},
// Test cases with valid but non-existing bucket names and invalid object name (Test number 8-9).
{"test-getobjectinfo", "", ObjectInfo{}, ObjectNameInvalid{Bucket: "test-getobjectinfo", Object: ""}, false},
{"test-getobjectinfo", "", ObjectInfo{}, ObjectNameInvalid{Bucket: "test-getobjectinfo", Object: ""}, false},
// Test cases with non-existing object name with existing bucket (Test number 10-12).
{"test-getobjectinfo", "Africa", ObjectInfo{}, ObjectNotFound{Bucket: "test-getobjectinfo", Object: "Africa"}, false},
{"test-getobjectinfo", "Antartica", ObjectInfo{}, ObjectNotFound{Bucket: "test-getobjectinfo", Object: "Antartica"}, false},
{"test-getobjectinfo", "Asia/myfile", ObjectInfo{}, ObjectNotFound{Bucket: "test-getobjectinfo", Object: "Asia/myfile"}, false},
// Test case with existing bucket but object name set to a directory (Test number 13).
{"test-getobjectinfo", "Asia", ObjectInfo{}, ObjectNotFound{Bucket: "test-getobjectinfo", Object: "Asia"}, false},
// Valid case with existing object (Test number 14).
{"test-getobjectinfo", "Asia/asiapics.jpg", resultCases[0], nil, true},
}
for i, testCase := range testCases {
result, err := obj.GetObjectInfo(testCase.bucketName, testCase.objectName)
if err != nil && testCase.shouldPass {
t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s", i+1, instanceType, err.Error())
}
if err == nil && !testCase.shouldPass {
t.Errorf("Test %d: %s: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, instanceType, testCase.err.Error())
}
// Failed as expected, but does it fail for the expected reason.
if err != nil && !testCase.shouldPass {
if testCase.err.Error() != err.Error() {
t.Errorf("Test %d: %s: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, instanceType, testCase.err.Error(), err.Error())
}
}
// Test passes as expected, but the output values are verified for correctness here.
if err == nil && testCase.shouldPass {
if testCase.result.Bucket != result.Bucket {
t.Fatalf("Test %d: %s: Expected Bucket name to be '%s', but found '%s' instead", i+1, instanceType, testCase.result.Bucket, result.Bucket)
}
if testCase.result.Name != result.Name {
t.Errorf("Test %d: %s: Expected Object name to be %s, but instead found it to be %s", i+1, instanceType, testCase.result.Name, result.Name)
}
if testCase.result.ContentType != result.ContentType {
t.Errorf("Test %d: %s: Expected Content Type of the object to be %v, but instead found it to be %v", i+1, instanceType, testCase.result.ContentType, result.ContentType)
}
if testCase.result.IsDir != result.IsDir {
t.Errorf("Test %d: %s: Expected IsDir flag of the object to be %v, but instead found it to be %v", i+1, instanceType, testCase.result.IsDir, result.IsDir)
}
}
}
}
func BenchmarkGetObjectFS(b *testing.B) {
// Make a temporary directory to use as the obj.
directory, err := ioutil.TempDir("", "minio-benchmark-getobject")
if err != nil {
b.Fatal(err)
}
defer os.RemoveAll(directory)
// Create the obj.
obj, err := newFSObjects(directory)
if err != nil {
b.Fatal(err)
}
// Make a bucket and put in a few objects.
err = obj.MakeBucket("bucket")
if err != nil {
b.Fatal(err)
}
text := "Jack and Jill went up the hill / To fetch a pail of water."
hasher := md5.New()
hasher.Write([]byte(text))
metadata := make(map[string]string)
for i := 0; i < 10; i++ {
metadata["md5Sum"] = hex.EncodeToString(hasher.Sum(nil))
_, err = obj.PutObject("bucket", "object"+strconv.Itoa(i), int64(len(text)), bytes.NewBufferString(text), metadata)
if err != nil {
b.Fatal(err)
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
var buffer = new(bytes.Buffer)
err = obj.GetObject("bucket", "object"+strconv.Itoa(i%10), 0, int64(len([]byte(text))), buffer)
if err != nil {
b.Error(err)
}
if buffer.Len() != len(text) {
b.Errorf("GetObject returned incorrect length %d (should be %d)\n", buffer.Len(), len(text))
}
}
}
+611
View File
@@ -0,0 +1,611 @@
/*
* Minio Cloud Storage, (C) 2015-2016 Minio, Inc.
*
* 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 main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"testing"
)
// Wrapper for calling ListObjects tests for both XL multiple disks and single node setup.
func TestListObjects(t *testing.T) {
ExecObjectLayerTest(t, testListObjects)
}
// Unit test for ListObjects in general.
func testListObjects(obj ObjectLayer, instanceType string, t *testing.T) {
// This bucket is used for testing ListObject operations.
err := obj.MakeBucket("test-bucket-list-object")
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
// Will not store any objects in this bucket,
// Its to test ListObjects on an empty bucket.
err = obj.MakeBucket("empty-bucket")
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
tmpfile, err := ioutil.TempFile("", "simple-file.txt")
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
defer os.Remove(tmpfile.Name()) // clean up
_, err = obj.PutObject("test-bucket-list-object", "Asia-maps", int64(len("asia-maps")), bytes.NewBufferString("asia-maps"), nil)
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
_, err = obj.PutObject("test-bucket-list-object", "Asia/India/India-summer-photos-1", int64(len("contentstring")), bytes.NewBufferString("contentstring"), nil)
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
_, err = obj.PutObject("test-bucket-list-object", "Asia/India/Karnataka/Bangalore/Koramangala/pics", int64(len("contentstring")), bytes.NewBufferString("contentstring"), nil)
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
for i := 0; i < 2; i++ {
key := "newPrefix" + strconv.Itoa(i)
_, err = obj.PutObject("test-bucket-list-object", key, int64(len(key)), bytes.NewBufferString(key), nil)
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
}
_, err = obj.PutObject("test-bucket-list-object", "newzen/zen/recurse/again/again/again/pics", int64(len("recurse")), bytes.NewBufferString("recurse"), nil)
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
for i := 0; i < 3; i++ {
key := "obj" + strconv.Itoa(i)
_, err = obj.PutObject("test-bucket-list-object", key, int64(len(key)), bytes.NewBufferString(key), nil)
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
}
// Formualting the result data set to be expected from ListObjects call inside the tests,
// This will be used in testCases and used for asserting the correctness of ListObjects output in the tests.
resultCases := []ListObjectsInfo{
// ListObjectsResult-0.
// Testing for listing all objects in the bucket, (testCase 20,21,22).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "Asia-maps"},
{Name: "Asia/India/India-summer-photos-1"},
{Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"},
{Name: "newPrefix0"},
{Name: "newPrefix1"},
{Name: "newzen/zen/recurse/again/again/again/pics"},
{Name: "obj0"},
{Name: "obj1"},
{Name: "obj2"},
},
},
// ListObjectsResult-1.
// Used for asserting the truncated case, (testCase 23).
{
IsTruncated: true,
Objects: []ObjectInfo{
{Name: "Asia-maps"},
{Name: "Asia/India/India-summer-photos-1"},
{Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"},
{Name: "newPrefix0"},
{Name: "newPrefix1"},
},
},
// ListObjectsResult-2.
// (TestCase 24).
{
IsTruncated: true,
Objects: []ObjectInfo{
{Name: "Asia-maps"},
{Name: "Asia/India/India-summer-photos-1"},
{Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"},
{Name: "newPrefix0"},
},
},
// ListObjectsResult-3.
// (TestCase 25).
{
IsTruncated: true,
Objects: []ObjectInfo{
{Name: "Asia-maps"},
{Name: "Asia/India/India-summer-photos-1"},
{Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"},
},
},
// ListObjectsResult-4.
// Again used for truncated case.
// (TestCase 26).
{
IsTruncated: true,
Objects: []ObjectInfo{
{Name: "Asia-maps"},
},
},
// ListObjectsResult-5.
// Used for Asserting prefixes.
// Used for test case with prefix "new", (testCase 27-29).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "newPrefix0"},
{Name: "newPrefix1"},
{Name: "newzen/zen/recurse/again/again/again/pics"},
},
},
// ListObjectsResult-6.
// Used for Asserting prefixes.
// Used for test case with prefix = "obj", (testCase 30).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "obj0"},
{Name: "obj1"},
{Name: "obj2"},
},
},
// ListObjectsResult-7.
// Used for Asserting prefixes and truncation.
// Used for test case with prefix = "new" and maxKeys = 1, (testCase 31).
{
IsTruncated: true,
Objects: []ObjectInfo{
{Name: "newPrefix0"},
},
},
// ListObjectsResult-8.
// Used for Asserting prefixes.
// Used for test case with prefix = "obj" and maxKeys = 2, (testCase 32).
{
IsTruncated: true,
Objects: []ObjectInfo{
{Name: "obj0"},
{Name: "obj1"},
},
},
// ListObjectsResult-9.
// Used for asserting the case with marker, but without prefix.
//marker is set to "newPrefix0" in the testCase, (testCase 33).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "newPrefix1"},
{Name: "newzen/zen/recurse/again/again/again/pics"},
{Name: "obj0"},
{Name: "obj1"},
{Name: "obj2"},
},
},
// ListObjectsResult-10.
//marker is set to "newPrefix1" in the testCase, (testCase 34).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "newzen/zen/recurse/again/again/again/pics"},
{Name: "obj0"},
{Name: "obj1"},
{Name: "obj2"},
},
},
// ListObjectsResult-11.
//marker is set to "obj0" in the testCase, (testCase 35).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "obj1"},
{Name: "obj2"},
},
},
// ListObjectsResult-12.
// Marker is set to "obj1" in the testCase, (testCase 36).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "obj2"},
},
},
// ListObjectsResult-13.
// Marker is set to "man" in the testCase, (testCase37).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "newPrefix0"},
{Name: "newPrefix1"},
{Name: "newzen/zen/recurse/again/again/again/pics"},
{Name: "obj0"},
{Name: "obj1"},
{Name: "obj2"},
},
},
// ListObjectsResult-14.
// Marker is set to "Abc" in the testCase, (testCase 39).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "Asia-maps"},
{Name: "Asia/India/India-summer-photos-1"},
{Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"},
{Name: "newPrefix0"},
{Name: "newPrefix1"},
{Name: "newzen/zen/recurse/again/again/again/pics"},
{Name: "obj0"},
{Name: "obj1"},
{Name: "obj2"},
},
},
// ListObjectsResult-15.
// Marker is set to "Asia/India/India-summer-photos-1" in the testCase, (testCase 40).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"},
{Name: "newPrefix0"},
{Name: "newPrefix1"},
{Name: "newzen/zen/recurse/again/again/again/pics"},
{Name: "obj0"},
{Name: "obj1"},
{Name: "obj2"},
},
},
// ListObjectsResult-16.
// Marker is set to "Asia/India/Karnataka/Bangalore/Koramangala/pics" in the testCase, (testCase 41).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "newPrefix0"},
{Name: "newPrefix1"},
{Name: "newzen/zen/recurse/again/again/again/pics"},
{Name: "obj0"},
{Name: "obj1"},
{Name: "obj2"},
},
},
// ListObjectsResult-17.
// Used for asserting the case with marker, without prefix but with truncation.
// Marker = "newPrefix0" & maxKeys = 3 in the testCase, (testCase42).
// Output truncated to 3 values.
{
IsTruncated: true,
Objects: []ObjectInfo{
{Name: "newPrefix1"},
{Name: "newzen/zen/recurse/again/again/again/pics"},
{Name: "obj0"},
},
},
// ListObjectsResult-18.
// Marker = "newPrefix1" & maxkeys = 1 in the testCase, (testCase43).
// Output truncated to 1 value.
{
IsTruncated: true,
Objects: []ObjectInfo{
{Name: "newzen/zen/recurse/again/again/again/pics"},
},
},
// ListObjectsResult-19.
// Marker = "obj0" & maxKeys = 1 in the testCase, (testCase44).
// Output truncated to 1 value.
{
IsTruncated: true,
Objects: []ObjectInfo{
{Name: "obj1"},
},
},
// ListObjectsResult-20.
// Marker = "obj0" & prefix = "obj" in the testCase, (testCase 45).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "obj1"},
{Name: "obj2"},
},
},
// ListObjectsResult-21.
// Marker = "obj1" & prefix = "obj" in the testCase, (testCase 46).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "obj2"},
},
},
// ListObjectsResult-22.
// Marker = "newPrefix0" & prefix = "new" in the testCase,, (testCase 47).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "newPrefix1"},
{Name: "newzen/zen/recurse/again/again/again/pics"},
},
},
// ListObjectsResult-23.
// Prefix is set to "Asia/India/" in the testCase, and delimiter is not set (testCase 55).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "Asia/India/India-summer-photos-1"},
{Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"},
},
},
// ListObjectsResult-24.
// Prefix is set to "Asia" in the testCase, and delimiter is not set (testCase 56).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "Asia-maps"},
{Name: "Asia/India/India-summer-photos-1"},
{Name: "Asia/India/Karnataka/Bangalore/Koramangala/pics"},
},
},
// ListObjectsResult-25.
// Prefix is set to "Asia" in the testCase, and delimiter is set (testCase 57).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "Asia-maps"},
},
},
// ListObjectsResult-26.
// prefix = "new" and delimiter is set in the testCase.(testCase 58).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "newPrefix0"},
{Name: "newPrefix1"},
},
},
// ListObjectsResult-27.
// Prefix is set to "Asia/India/" in the testCase, and delimiter is set to forward slash '/' (testCase 59).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "Asia/India/India-summer-photos-1"},
},
},
// ListObjectsResult-28.
// Marker is set to "Asia/India/India-summer-photos-1" and delimiter set in the testCase, (testCase 60).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "newPrefix0"},
{Name: "newPrefix1"},
{Name: "obj0"},
{Name: "obj1"},
{Name: "obj2"},
},
},
// ListObjectsResult-29.
// Marker is set to "Asia/India/Karnataka/Bangalore/Koramangala/pics" in the testCase and delimeter set, (testCase 61).
{
IsTruncated: false,
Objects: []ObjectInfo{
{Name: "newPrefix0"},
{Name: "newPrefix1"},
{Name: "obj0"},
{Name: "obj1"},
{Name: "obj2"},
},
},
// ListObjectsResult-30.
// Prefix and Delimiter is set to '/', (testCase 62).
{
IsTruncated: false,
Objects: []ObjectInfo{},
},
}
testCases := []struct {
// Inputs to ListObjects.
bucketName string
prefix string
marker string
delimeter string
maxKeys int
// Expected output of ListObjects.
result ListObjectsInfo
err error
// Flag indicating whether the test is expected to pass or not.
shouldPass bool
}{
// Test cases with invalid bucket names ( Test number 1-4 ).
{".test", "", "", "", 0, ListObjectsInfo{}, BucketNameInvalid{Bucket: ".test"}, false},
{"Test", "", "", "", 0, ListObjectsInfo{}, BucketNameInvalid{Bucket: "Test"}, false},
{"---", "", "", "", 0, ListObjectsInfo{}, BucketNameInvalid{Bucket: "---"}, false},
{"ad", "", "", "", 0, ListObjectsInfo{}, BucketNameInvalid{Bucket: "ad"}, false},
// Using an existing file for bucket name, but its not a directory (5).
{"simple-file.txt", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "simple-file.txt"}, false},
// Valid bucket names, but they donot exist (6-8).
{"volatile-bucket-1", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
{"volatile-bucket-2", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
{"volatile-bucket-3", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
// Valid, existing bucket, but sending invalid delimeter values (9-10).
// Empty string < "" > and forward slash < / > are the ony two valid arguments for delimeter.
{"test-bucket-list-object", "", "", "*", 0, ListObjectsInfo{}, fmt.Errorf("delimiter '%s' is not supported", "*"), false},
{"test-bucket-list-object", "", "", "-", 0, ListObjectsInfo{}, fmt.Errorf("delimiter '%s' is not supported", "-"), false},
// Testing for failure cases with both perfix and marker (13).
// The prefix and marker combination to be valid it should satisy strings.HasPrefix(marker, prefix).
{"test-bucket-list-object", "asia", "europe-object", "", 0, ListObjectsInfo{}, fmt.Errorf("Invalid combination of marker '%s' and prefix '%s'", "europe-object", "asia"), false},
// Setting a non-existing directory to be prefix (14-15).
{"empty-bucket", "europe/france/", "", "", 1, ListObjectsInfo{}, nil, true},
{"empty-bucket", "europe/tunisia/", "", "", 1, ListObjectsInfo{}, nil, true},
// Testing on empty bucket, that is, bucket without any objects in it (16).
{"empty-bucket", "", "", "", 0, ListObjectsInfo{}, nil, true},
// Setting maxKeys to negative value (17-18).
{"empty-bucket", "", "", "", -1, ListObjectsInfo{}, nil, true},
{"empty-bucket", "", "", "", 1, ListObjectsInfo{}, nil, true},
// Setting maxKeys to a very large value (19).
{"empty-bucket", "", "", "", 1111000000000000, ListObjectsInfo{}, nil, true},
// Testing for all 7 objects in the bucket (20).
{"test-bucket-list-object", "", "", "", 9, resultCases[0], nil, true},
//Testing for negative value of maxKey, this should set maxKeys to listObjectsLimit (21).
{"test-bucket-list-object", "", "", "", -1, resultCases[0], nil, true},
// Testing for very large value of maxKey, this should set maxKeys to listObjectsLimit (22).
{"test-bucket-list-object", "", "", "", 1234567891011, resultCases[0], nil, true},
// Testing for trancated value (23-26).
{"test-bucket-list-object", "", "", "", 5, resultCases[1], nil, true},
{"test-bucket-list-object", "", "", "", 4, resultCases[2], nil, true},
{"test-bucket-list-object", "", "", "", 3, resultCases[3], nil, true},
{"test-bucket-list-object", "", "", "", 1, resultCases[4], nil, true},
// Testing with prefix (27-30).
{"test-bucket-list-object", "new", "", "", 3, resultCases[5], nil, true},
{"test-bucket-list-object", "new", "", "", 4, resultCases[5], nil, true},
{"test-bucket-list-object", "new", "", "", 5, resultCases[5], nil, true},
{"test-bucket-list-object", "obj", "", "", 3, resultCases[6], nil, true},
// Testing with prefix and truncation (31-32).
{"test-bucket-list-object", "new", "", "", 1, resultCases[7], nil, true},
{"test-bucket-list-object", "obj", "", "", 2, resultCases[8], nil, true},
// Testing with marker, but without prefix and truncation (33-37).
{"test-bucket-list-object", "", "newPrefix0", "", 5, resultCases[9], nil, true},
{"test-bucket-list-object", "", "newPrefix1", "", 4, resultCases[10], nil, true},
{"test-bucket-list-object", "", "obj0", "", 2, resultCases[11], nil, true},
{"test-bucket-list-object", "", "obj1", "", 1, resultCases[12], nil, true},
{"test-bucket-list-object", "", "man", "", 10, resultCases[13], nil, true},
// Marker being set to a value which is greater than and all object names when sorted (38).
// Expected to send an empty response in this case.
{"test-bucket-list-object", "", "zen", "", 10, ListObjectsInfo{}, nil, true},
// Marker being set to a value which is lesser than and all object names when sorted (39).
// Expected to send all the objects in the bucket in this case.
{"test-bucket-list-object", "", "Abc", "", 10, resultCases[14], nil, true},
// Marker is to a hierarhical value (40-41).
{"test-bucket-list-object", "", "Asia/India/India-summer-photos-1", "", 10, resultCases[15], nil, true},
{"test-bucket-list-object", "", "Asia/India/Karnataka/Bangalore/Koramangala/pics", "", 10, resultCases[16], nil, true},
// Testing with marker and truncation, but no prefix (42-44).
{"test-bucket-list-object", "", "newPrefix0", "", 3, resultCases[17], nil, true},
{"test-bucket-list-object", "", "newPrefix1", "", 1, resultCases[18], nil, true},
{"test-bucket-list-object", "", "obj0", "", 1, resultCases[19], nil, true},
// Testing with both marker and prefix, but without truncation (45-47).
// The valid combination of marker and prefix should satisfy strings.HasPrefix(marker, prefix).
{"test-bucket-list-object", "obj", "obj0", "", 2, resultCases[20], nil, true},
{"test-bucket-list-object", "obj", "obj1", "", 1, resultCases[21], nil, true},
{"test-bucket-list-object", "new", "newPrefix0", "", 2, resultCases[22], nil, true},
// Testing with maxKeys set to 0 (48-54).
// The parameters have to valid.
{"test-bucket-list-object", "", "obj1", "", 0, ListObjectsInfo{}, nil, true},
{"test-bucket-list-object", "", "obj0", "", 0, ListObjectsInfo{}, nil, true},
{"test-bucket-list-object", "new", "", "", 0, ListObjectsInfo{}, nil, true},
{"test-bucket-list-object", "obj", "", "", 0, ListObjectsInfo{}, nil, true},
{"test-bucket-list-object", "obj", "obj0", "", 0, ListObjectsInfo{}, nil, true},
{"test-bucket-list-object", "obj", "obj1", "", 0, ListObjectsInfo{}, nil, true},
{"test-bucket-list-object", "new", "newPrefix0", "", 0, ListObjectsInfo{}, nil, true},
// Tests on hierarchical key names as prefix.
// Without delimteter the code should recurse into the prefix Dir.
// Tests with prefix, but without delimiter (55-56).
{"test-bucket-list-object", "Asia/India/", "", "", 10, resultCases[23], nil, true},
{"test-bucket-list-object", "Asia", "", "", 10, resultCases[24], nil, true},
// Tests with prefix and delimiter (57-59).
// With delimeter the code shouldnot recurse into the sub-directories of prefix Dir.
{"test-bucket-list-object", "Asia", "", "/", 10, resultCases[25], nil, true},
{"test-bucket-list-object", "new", "", "/", 10, resultCases[26], nil, true},
{"test-bucket-list-object", "Asia/India/", "", "/", 10, resultCases[27], nil, true},
// Test with marker set as hierarhical value and with delimiter. (60-61)
{"test-bucket-list-object", "", "Asia/India/India-summer-photos-1", "/", 10, resultCases[28], nil, true},
{"test-bucket-list-object", "", "Asia/India/Karnataka/Bangalore/Koramangala/pics", "/", 10, resultCases[29], nil, true},
// Test with prefix and delimiter set to '/'. (62)
{"test-bucket-list-object", "/", "", "/", 10, resultCases[30], nil, true},
}
for i, testCase := range testCases {
result, err := obj.ListObjects(testCase.bucketName, testCase.prefix, testCase.marker, testCase.delimeter, testCase.maxKeys)
if err != nil && testCase.shouldPass {
t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s", i+1, instanceType, err.Error())
}
if err == nil && !testCase.shouldPass {
t.Errorf("Test %d: %s: Expected to fail with <ERROR> \"%s\", but passed instead", i+1, instanceType, testCase.err.Error())
}
// Failed as expected, but does it fail for the expected reason.
if err != nil && !testCase.shouldPass {
if !strings.Contains(err.Error(), testCase.err.Error()) {
t.Errorf("Test %d: %s: Expected to fail with error \"%s\", but instead failed with error \"%s\" instead", i+1, instanceType, testCase.err.Error(), err.Error())
}
}
// Since there are cases for which ListObjects fails, this is
// necessary. Test passes as expected, but the output values
// are verified for correctness here.
if err == nil && testCase.shouldPass {
// The length of the expected ListObjectsResult.Objects
// should match in both expected result from test cases
// and in the output. On failure calling t.Fatalf,
// otherwise it may lead to index out of range error in
// assertion following this.
if len(testCase.result.Objects) != len(result.Objects) {
t.Fatalf("Test %d: %s: Expected number of object in the result to be '%d', but found '%d' objects instead", i+1, instanceType, len(testCase.result.Objects), len(result.Objects))
}
for j := 0; j < len(testCase.result.Objects); j++ {
if testCase.result.Objects[j].Name != result.Objects[j].Name {
t.Errorf("Test %d: %s: Expected object name to be \"%s\", but found \"%s\" instead", i+1, instanceType, testCase.result.Objects[j].Name, result.Objects[j].Name)
}
}
if testCase.result.IsTruncated != result.IsTruncated {
t.Errorf("Test %d: %s: Expected IsTruncated flag to be %v, but instead found it to be %v", i+1, instanceType, testCase.result.IsTruncated, result.IsTruncated)
}
}
}
}
func BenchmarkListObjects(b *testing.B) {
// Make a temporary directory to use as the obj.
directory, err := ioutil.TempDir("", "minio-list-benchmark")
if err != nil {
b.Fatal(err)
}
defer os.RemoveAll(directory)
// Create the obj.
obj, err := newFSObjects(directory)
if err != nil {
b.Fatal(err)
}
// Create a bucket.
err = obj.MakeBucket("ls-benchmark-bucket")
if err != nil {
b.Fatal(err)
}
for i := 0; i < 20000; i++ {
key := "obj" + strconv.Itoa(i)
_, err = obj.PutObject("ls-benchmark-bucket", key, int64(len(key)), bytes.NewBufferString(key), nil)
if err != nil {
b.Fatal(err)
}
}
b.ResetTimer()
// List the buckets over and over and over.
for i := 0; i < b.N; i++ {
_, err = obj.ListObjects("ls-benchmark-bucket", "", "obj9000", "", -1)
if err != nil {
b.Fatal(err)
}
}
}
File diff suppressed because it is too large Load Diff
+36 -14
View File
@@ -1,5 +1,5 @@
/*
* Minio Cloud Storage, (C) 2015 Minio, Inc.
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,31 +14,54 @@
* limitations under the License.
*/
package fs
package main
import (
"io/ioutil"
"os"
"testing"
. "gopkg.in/check.v1"
)
func Test(t *testing.T) { TestingT(t) }
type MySuite struct{}
var _ = Suite(&MySuite{})
func (s *MySuite) TestAPISuite(c *C) {
func (s *MySuite) TestFSAPISuite(c *C) {
var storageList []string
create := func() Filesystem {
path, e := ioutil.TempDir(os.TempDir(), "minio-")
c.Check(e, IsNil)
storageList = append(storageList, path)
store, err := New(path, 0)
// Initialize name space lock.
initNSLock()
create := func() ObjectLayer {
path, err := ioutil.TempDir(os.TempDir(), "minio-")
c.Check(err, IsNil)
return store
objAPI, err := newFSObjects(path)
c.Check(err, IsNil)
storageList = append(storageList, path)
return objAPI
}
APITestSuite(c, create)
defer removeRoots(c, storageList)
}
func (s *MySuite) TestXLAPISuite(c *C) {
var storageList []string
// Initialize name space lock.
initNSLock()
create := func() ObjectLayer {
var nDisks = 16 // Maximum disks.
var erasureDisks []string
for i := 0; i < nDisks; i++ {
path, err := ioutil.TempDir(os.TempDir(), "minio-")
c.Check(err, IsNil)
erasureDisks = append(erasureDisks, path)
}
objAPI, err := newXLObjects(erasureDisks)
c.Check(err, IsNil)
return objAPI
}
APITestSuite(c, create)
defer removeRoots(c, storageList)
@@ -46,7 +69,6 @@ func (s *MySuite) TestAPISuite(c *C) {
func removeRoots(c *C, roots []string) {
for _, root := range roots {
err := os.RemoveAll(root)
c.Check(err, IsNil)
os.RemoveAll(root)
}
}
+137
View File
@@ -0,0 +1,137 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import (
"path/filepath"
"strings"
"sync"
)
const (
// Block size used for all internal operations version 1.
blockSizeV1 = 10 * 1024 * 1024 // 10MiB.
)
// House keeping code needed for FS.
func fsHouseKeeping(storageDisk StorageAPI) error {
// Attempt to create `.minio`.
err := storageDisk.MakeVol(minioMetaBucket)
if err != nil {
if err != errVolumeExists && err != errDiskNotFound {
return err
}
}
// Cleanup all temp entries upon start.
err = cleanupDir(storageDisk, minioMetaBucket, tmpMetaPrefix)
if err != nil {
return err
}
return nil
}
// Depending on the disk type network or local, initialize storage API.
func newStorageAPI(disk string) (storage StorageAPI, err error) {
if !strings.ContainsRune(disk, ':') || filepath.VolumeName(disk) != "" {
// Initialize filesystem storage API.
return newPosix(disk)
}
// Initialize rpc client storage API.
return newRPCClient(disk)
}
// House keeping code needed for XL.
func xlHouseKeeping(storageDisks []StorageAPI) error {
// This happens for the first time, but keep this here since this
// is the only place where it can be made expensive optimizing all
// other calls. Create minio meta volume, if it doesn't exist yet.
var wg = &sync.WaitGroup{}
// Initialize errs to collect errors inside go-routine.
var errs = make([]error, len(storageDisks))
// Initialize all disks in parallel.
for index, disk := range storageDisks {
if disk == nil {
errs[index] = errDiskNotFound
continue
}
wg.Add(1)
go func(index int, disk StorageAPI) {
// Indicate this wait group is done.
defer wg.Done()
// Attempt to create `.minio`.
err := disk.MakeVol(minioMetaBucket)
if err != nil && err != errVolumeExists && err != errDiskNotFound {
errs[index] = err
return
}
// Cleanup all temp entries upon start.
err = cleanupDir(disk, minioMetaBucket, tmpMetaPrefix)
if err != nil {
errs[index] = err
return
}
errs[index] = nil
}(index, disk)
}
// Wait for all cleanup to finish.
wg.Wait()
// Return upon first error.
for _, err := range errs {
if err == nil {
continue
}
return toObjectErr(err, minioMetaBucket, tmpMetaPrefix)
}
// Return success here.
return nil
}
// Cleanup a directory recursively.
func cleanupDir(storage StorageAPI, volume, dirPath string) error {
var delFunc func(string) error
// Function to delete entries recursively.
delFunc = func(entryPath string) error {
if !strings.HasSuffix(entryPath, slashSeparator) {
// No trailing "/" means that this is a file which can be deleted.
return storage.DeleteFile(volume, entryPath)
}
// If it's a directory, list and call delFunc() for each entry.
entries, err := storage.ListDir(volume, entryPath)
if err != nil {
if err == errFileNotFound {
// if dirPath prefix never existed.
return nil
}
return err
}
for _, entry := range entries {
err = delFunc(pathJoin(entryPath, entry))
if err != nil {
return err
}
}
return nil
}
err := delFunc(retainSlash(pathJoin(dirPath)))
return err
}
+52 -49
View File
@@ -1,5 +1,5 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,41 +14,36 @@
* limitations under the License.
*/
package fs
package main
import (
"os"
"time"
)
import "time"
// BucketMetadata - name and create date
type BucketMetadata struct {
// StorageInfo - represents total capacity of underlying storage.
type StorageInfo struct {
Total int64 // Total disk space.
Free int64 // Free total available disk space.
}
// BucketInfo - bucket name and create date
type BucketInfo struct {
Name string
Created time.Time
}
// ObjectMetadata - object key and its relevant metadata
type ObjectMetadata struct {
Bucket string
Object string
ContentType string
LastModified time.Time
Mode os.FileMode
MD5 string
Size int64
// ObjectInfo - object info.
type ObjectInfo struct {
Bucket string
Name string
ModTime time.Time
Size int64
IsDir bool
MD5Sum string
ContentType string
ContentEncoding string
}
// PartMetadata - various types of individual part resources
type PartMetadata struct {
PartNumber int
LastModified time.Time
ETag string
Size int64
}
// ObjectResourcesMetadata - various types of object resources
type ObjectResourcesMetadata struct {
// ListPartsInfo - various types of object resources.
type ListPartsInfo struct {
Bucket string
Object string
UploadID string
@@ -58,20 +53,12 @@ type ObjectResourcesMetadata struct {
MaxParts int
IsTruncated bool
Part []PartMetadata
Parts []partInfo
EncodingType string
}
// UploadMetadata container capturing metadata on in progress multipart upload in a given bucket
type UploadMetadata struct {
Object string
UploadID string
StorageClass string
Initiated time.Time
}
// BucketMultipartResourcesMetadata - various types of bucket resources for inprogress multipart uploads
type BucketMultipartResourcesMetadata struct {
// ListMultipartsInfo - various types of bucket resources for inprogress multipart uploads.
type ListMultipartsInfo struct {
KeyMarker string
UploadIDMarker string
NextKeyMarker string
@@ -79,34 +66,50 @@ type BucketMultipartResourcesMetadata struct {
EncodingType string
MaxUploads int
IsTruncated bool
Upload []*UploadMetadata
Uploads []uploadMetadata
Prefix string
Delimiter string
CommonPrefixes []string
}
// ListObjectsResult - container for list object request results.
type ListObjectsResult struct {
// ListObjectsInfo - container for list objects.
type ListObjectsInfo struct {
IsTruncated bool
NextMarker string
Objects []ObjectMetadata
Objects []ObjectInfo
Prefixes []string
}
// CompletePart - completed part container
type CompletePart struct {
// partInfo - various types of individual part resources.
type partInfo struct {
PartNumber int
LastModified time.Time
ETag string
Size int64
}
// uploadMetadata container capturing metadata on in progress multipart upload in a given bucket
type uploadMetadata struct {
Object string
UploadID string
StorageClass string
Initiated time.Time
}
// completePart - completed part container.
type completePart struct {
PartNumber int
ETag string
}
// completedParts is a sortable interface for Part slice
type completedParts []CompletePart
type completedParts []completePart
func (a completedParts) Len() int { return len(a) }
func (a completedParts) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a completedParts) Less(i, j int) bool { return a[i].PartNumber < a[j].PartNumber }
// CompleteMultipartUpload container for completing multipart upload
type CompleteMultipartUpload struct {
Part []CompletePart
// completeMultipartUpload container for completing multipart upload
type completeMultipartUpload struct {
Parts []completePart `xml:"Part"`
}
+258
View File
@@ -0,0 +1,258 @@
/*
* Minio Cloud Storage, (C) 2015 Minio, Inc.
*
* 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 main
import (
"fmt"
"io"
)
// Converts underlying storage error. Convenience function written to
// handle all cases where we have known types of errors returned by
// underlying storage layer.
func toObjectErr(err error, params ...string) error {
switch err {
case errVolumeNotFound:
if len(params) >= 1 {
return BucketNotFound{Bucket: params[0]}
}
case errVolumeNotEmpty:
if len(params) >= 1 {
return BucketNotEmpty{Bucket: params[0]}
}
case errVolumeExists:
if len(params) >= 1 {
return BucketExists{Bucket: params[0]}
}
case errDiskFull:
return StorageFull{}
case errIsNotRegular, errFileAccessDenied:
if len(params) >= 2 {
return ObjectExistsAsDirectory{
Bucket: params[0],
Object: params[1],
}
}
case errFileNotFound:
if len(params) >= 2 {
return ObjectNotFound{
Bucket: params[0],
Object: params[1],
}
}
case errFileNameTooLong:
if len(params) >= 2 {
return ObjectNameInvalid{
Bucket: params[0],
Object: params[1],
}
}
case errXLReadQuorum:
return InsufficientReadQuorum{}
case errXLWriteQuorum:
return InsufficientWriteQuorum{}
case io.ErrUnexpectedEOF, io.ErrShortWrite:
return IncompleteBody{}
}
return err
}
// StorageFull storage ran out of space.
type StorageFull struct{}
func (e StorageFull) Error() string {
return "Storage reached its minimum free disk threshold."
}
// InsufficientReadQuorum storage cannot satisfy quorum for read operation.
type InsufficientReadQuorum struct{}
func (e InsufficientReadQuorum) Error() string {
return "Storage resources are insufficient for the read operation."
}
// InsufficientWriteQuorum storage cannot satisfy quorum for write operation.
type InsufficientWriteQuorum struct{}
func (e InsufficientWriteQuorum) Error() string {
return "Storage resources are insufficient for the write operation."
}
// GenericError - generic object layer error.
type GenericError struct {
Bucket string
Object string
}
// BucketNotFound bucket does not exist.
type BucketNotFound GenericError
func (e BucketNotFound) Error() string {
return "Bucket not found: " + e.Bucket
}
// BucketNotEmpty bucket is not empty.
type BucketNotEmpty GenericError
func (e BucketNotEmpty) Error() string {
return "Bucket not empty: " + e.Bucket
}
// ObjectNotFound object does not exist.
type ObjectNotFound GenericError
func (e ObjectNotFound) Error() string {
return "Object not found: " + e.Bucket + "#" + e.Object
}
// ObjectExistsAsDirectory object already exists as a directory.
type ObjectExistsAsDirectory GenericError
func (e ObjectExistsAsDirectory) Error() string {
return "Object exists on : " + e.Bucket + " as directory " + e.Object
}
// BucketExists bucket exists.
type BucketExists GenericError
func (e BucketExists) Error() string {
return "Bucket exists: " + e.Bucket
}
// BadDigest - Content-MD5 you specified did not match what we received.
type BadDigest struct {
ExpectedMD5 string
CalculatedMD5 string
}
func (e BadDigest) Error() string {
return "Bad digest: Expected " + e.ExpectedMD5 + " is not valid with what we calculated " + e.CalculatedMD5
}
// UnsupportedDelimiter - unsupported delimiter.
type UnsupportedDelimiter struct {
Delimiter string
}
func (e UnsupportedDelimiter) Error() string {
return fmt.Sprintf("delimiter '%s' is not supported. Only '/' is supported", e.Delimiter)
}
// InvalidUploadIDKeyCombination - invalid upload id and key marker combination.
type InvalidUploadIDKeyCombination struct {
UploadIDMarker, KeyMarker string
}
func (e InvalidUploadIDKeyCombination) Error() string {
return fmt.Sprintf("Invalid combination of uploadID marker '%s' and marker '%s'", e.UploadIDMarker, e.KeyMarker)
}
// InvalidMarkerPrefixCombination - invalid marker and prefix combination.
type InvalidMarkerPrefixCombination struct {
Marker, Prefix string
}
func (e InvalidMarkerPrefixCombination) Error() string {
return fmt.Sprintf("Invalid combination of marker '%s' and prefix '%s'", e.Marker, e.Prefix)
}
// BucketPolicyNotFound - no bucket policy found.
type BucketPolicyNotFound GenericError
func (e BucketPolicyNotFound) Error() string {
return "No bucket policy found for bucket: " + e.Bucket
}
/// Bucket related errors.
// BucketNameInvalid - bucketname provided is invalid.
type BucketNameInvalid GenericError
// Return string an error formatted as the given text.
func (e BucketNameInvalid) Error() string {
return "Bucket name invalid: " + e.Bucket
}
/// Object related errors.
// ObjectNameInvalid - object name provided is invalid.
type ObjectNameInvalid GenericError
// Return string an error formatted as the given text.
func (e ObjectNameInvalid) Error() string {
return "Object name invalid: " + e.Bucket + "#" + e.Object
}
// UnExpectedDataSize - Reader contains more/less data than specified.
type UnExpectedDataSize struct {
Size int
}
func (e UnExpectedDataSize) Error() string {
return fmt.Sprintf("Contains more data than specified size of %d bytes.", e.Size)
}
// IncompleteBody You did not provide the number of bytes specified by the Content-Length HTTP header.
type IncompleteBody GenericError
// Return string an error formatted as the given text.
func (e IncompleteBody) Error() string {
return e.Bucket + "#" + e.Object + "has incomplete body"
}
/// Multipart related errors.
// MalformedUploadID malformed upload id.
type MalformedUploadID struct {
UploadID string
}
func (e MalformedUploadID) Error() string {
return "Malformed upload id " + e.UploadID
}
// InvalidUploadID invalid upload id.
type InvalidUploadID struct {
UploadID string
}
func (e InvalidUploadID) Error() string {
return "Invalid upload id " + e.UploadID
}
// InvalidPart One or more of the specified parts could not be found
type InvalidPart struct{}
func (e InvalidPart) Error() string {
return "One or more of the specified parts could not be found"
}
// InvalidPartOrder parts are not ordered as Requested
type InvalidPartOrder struct {
UploadID string
}
func (e InvalidPartOrder) Error() string {
return "Invalid part order sent for " + e.UploadID
}
// PartTooSmall - error if part size is less than 5MB.
type PartTooSmall struct{}
func (e PartTooSmall) Error() string {
return "Part size should be atleast 5MB"
}
+495 -379
View File
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import "io"
// ObjectLayer implements primitives for object API layer.
type ObjectLayer interface {
// Storage operations.
StorageInfo() StorageInfo
// Bucket operations.
MakeBucket(bucket string) error
GetBucketInfo(bucket string) (bucketInfo BucketInfo, err error)
ListBuckets() (buckets []BucketInfo, err error)
DeleteBucket(bucket string) error
ListObjects(bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error)
// Object operations.
GetObject(bucket, object string, startOffset int64, length int64, writer io.Writer) (err error)
GetObjectInfo(bucket, object string) (objInfo ObjectInfo, err error)
PutObject(bucket, object string, size int64, data io.Reader, metadata map[string]string) (md5 string, err error)
DeleteObject(bucket, object string) error
// Multipart operations.
ListMultipartUploads(bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (result ListMultipartsInfo, err error)
NewMultipartUpload(bucket, object string, metadata map[string]string) (uploadID string, err error)
PutObjectPart(bucket, object, uploadID string, partID int, size int64, data io.Reader, md5Hex string) (md5 string, err error)
ListObjectParts(bucket, object, uploadID string, partNumberMarker int, maxParts int) (result ListPartsInfo, err error)
AbortMultipartUpload(bucket, object, uploadID string) error
CompleteMultipartUpload(bucket, object, uploadID string, uploadedParts []completePart) (md5 string, err error)
}
+159
View File
@@ -0,0 +1,159 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* 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 main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"path"
"regexp"
"strings"
"unicode/utf8"
"github.com/skyrings/skyring-common/tools/uuid"
)
const (
// Minio meta bucket.
minioMetaBucket = ".minio"
// Multipart meta prefix.
mpartMetaPrefix = "multipart"
// Tmp meta prefix.
tmpMetaPrefix = "tmp"
)
// validBucket regexp.
var validBucket = regexp.MustCompile(`^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$`)
// IsValidBucketName verifies a bucket name in accordance with Amazon's
// requirements. It must be 3-63 characters long, can contain dashes
// and periods, but must begin and end with a lowercase letter or a number.
// See: http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html
func IsValidBucketName(bucket string) bool {
if len(bucket) < 3 || len(bucket) > 63 {
return false
}
if bucket[0] == '.' || bucket[len(bucket)-1] == '.' {
return false
}
return validBucket.MatchString(bucket)
}
// IsValidObjectName verifies an object name in accordance with Amazon's
// requirements. It cannot exceed 1024 characters and must be a valid UTF8
// string.
//
// See:
// http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html
//
// You should avoid the following characters in a key name because of
// significant special handling for consistency across all
// applications.
//
// Rejects strings with following characters.
//
// - Backslash ("\")
// - Caret ("^")
// - Grave accent / back tick ("`")
// - Vertical bar / pipe ("|")
//
// Minio does not support object names with trailing "/".
func IsValidObjectName(object string) bool {
if len(object) == 0 {
return false
}
if strings.HasSuffix(object, slashSeparator) {
return false
}
if strings.HasPrefix(object, slashSeparator) {
return false
}
return IsValidObjectPrefix(object)
}
// IsValidObjectPrefix verifies whether the prefix is a valid object name.
// Its valid to have a empty prefix.
func IsValidObjectPrefix(object string) bool {
if len(object) > 1024 {
return false
}
if !utf8.ValidString(object) {
return false
}
// Reject unsupported characters in object name.
if strings.ContainsAny(object, "`^*|\\\"") {
return false
}
return true
}
// Slash separator.
const slashSeparator = "/"
// retainSlash - retains slash from a path.
func retainSlash(s string) string {
return strings.TrimSuffix(s, slashSeparator) + slashSeparator
}
// pathJoin - like path.Join() but retains trailing "/" of the last element
func pathJoin(elem ...string) string {
trailingSlash := ""
if len(elem) > 0 {
if strings.HasSuffix(elem[len(elem)-1], slashSeparator) {
trailingSlash = "/"
}
}
return path.Join(elem...) + trailingSlash
}
// getUUID() - get a unique uuid.
func getUUID() (uuidStr string) {
for {
uuid, err := uuid.New()
if err != nil {
errorIf(err, "Unable to initialize uuid")
continue
}
uuidStr = uuid.String()
break
}
return uuidStr
}
// Create an s3 compatible MD5sum for complete multipart transaction.
func completeMultipartMD5(parts ...completePart) (string, error) {
var finalMD5Bytes []byte
for _, part := range parts {
md5Bytes, err := hex.DecodeString(part.ETag)
if err != nil {
return "", err
}
finalMD5Bytes = append(finalMD5Bytes, md5Bytes...)
}
md5Hasher := md5.New()
md5Hasher.Write(finalMD5Bytes)
s3MD5 := fmt.Sprintf("%s-%d", hex.EncodeToString(md5Hasher.Sum(nil)), len(parts))
return s3MD5, nil
}
// byBucketName is a collection satisfying sort.Interface.
type byBucketName []BucketInfo
func (d byBucketName) Len() int { return len(d) }
func (d byBucketName) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
func (d byBucketName) Less(i, j int) bool { return d[i].Name < d[j].Name }
+107
View File
@@ -0,0 +1,107 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* 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 main
import (
"testing"
)
// Tests validate bucket name.
func TestIsValidBucketName(t *testing.T) {
testCases := []struct {
bucketName string
shouldPass bool
}{
// cases which should pass the test.
// passing in valid bucket names.
{"lol", true},
{"1-this-is-valid", true},
{"1-this-too-is-valid-1", true},
{"this.works.too.1", true},
{"1234567", true},
{"123", true},
{"s3-eu-west-1.amazonaws.com", true},
{"ideas-are-more-powerful-than-guns", true},
{"testbucket", true},
{"1bucket", true},
{"bucket1", true},
// cases for which test should fail.
// passing invalid bucket names.
{"------", false},
{"$this-is-not-valid-too", false},
{"contains-$-dollar", false},
{"contains-^-carrot", false},
{"contains-$-dollar", false},
{"contains-$-dollar", false},
{"......", false},
{"", false},
{"a", false},
{"ab", false},
{".starts-with-a-dot", false},
{"ends-with-a-dot.", false},
{"ends-with-a-dash-", false},
{"-starts-with-a-dash", false},
{"THIS-BEINGS-WITH-UPPERCASe", false},
{"tHIS-ENDS-WITH-UPPERCASE", false},
{"ThisBeginsAndEndsWithUpperCase", false},
{"una ñina", false},
{"lalalallalallalalalallalallalala-theString-size-is-greater-than-64", false},
}
for i, testCase := range testCases {
isValidBucketName := IsValidBucketName(testCase.bucketName)
if testCase.shouldPass && !isValidBucketName {
t.Errorf("Test case %d: Expected \"%s\" to be a valid bucket name", i+1, testCase.bucketName)
}
if !testCase.shouldPass && isValidBucketName {
t.Errorf("Test case %d: Expected bucket name \"%s\" to be invalid", i+1, testCase.bucketName)
}
}
}
// Tests for validate object name.
func TestIsValidObjectName(t *testing.T) {
testCases := []struct {
objectName string
shouldPass bool
}{
// cases which should pass the test.
// passing in valid object name.
{"object", true},
{"The Shining Script <v1>.pdf", true},
{"Cost Benefit Analysis (2009-2010).pptx", true},
{"117Gn8rfHL2ACARPAhaFd0AGzic9pUbIA/5OCn5A", true},
{"SHØRT", true},
{"There are far too many object names, and far too few bucket names!", true},
// cases for which test should fail.
// passing invalid object names.
{"", false},
{"a/b/c/", false},
{"/a/b/c", false},
{string([]byte{0xff, 0xfe, 0xfd}), false},
}
for i, testCase := range testCases {
isValidObjectName := IsValidObjectName(testCase.objectName)
if testCase.shouldPass && !isValidObjectName {
t.Errorf("Test case %d: Expected \"%s\" to be a valid object name", i+1, testCase.objectName)
}
if !testCase.shouldPass && isValidObjectName {
t.Errorf("Test case %d: Expected object name \"%s\" to be invalid", i+1, testCase.objectName)
}
}
}
+432
View File
@@ -0,0 +1,432 @@
/*
* Minio Cloud Storage, (C) 2015, 2016 Minio, Inc.
*
* 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 main
import (
"bytes"
"crypto/md5"
"encoding/hex"
"math/rand"
"strconv"
"gopkg.in/check.v1"
)
// APITestSuite - collection of API tests.
func APITestSuite(c *check.C, create func() ObjectLayer) {
testMakeBucket(c, create)
testMultipleObjectCreation(c, create)
testPaging(c, create)
testObjectOverwriteWorks(c, create)
testNonExistantBucketOperations(c, create)
testBucketRecreateFails(c, create)
testPutObjectInSubdir(c, create)
testListBuckets(c, create)
testListBucketsOrder(c, create)
testListObjectsTestsForNonExistantBucket(c, create)
testNonExistantObjectInBucket(c, create)
testGetDirectoryReturnsObjectNotFound(c, create)
testDefaultContentType(c, create)
testMultipartObjectCreation(c, create)
testMultipartObjectAbort(c, create)
}
// Tests validate bucket creation.
func testMakeBucket(c *check.C, create func() ObjectLayer) {
obj := create()
err := obj.MakeBucket("bucket-unknown")
c.Assert(err, check.IsNil)
}
// Tests validate creation of part files during Multipart operation.
func testMultipartObjectCreation(c *check.C, create func() ObjectLayer) {
obj := create()
err := obj.MakeBucket("bucket")
c.Assert(err, check.IsNil)
uploadID, err := obj.NewMultipartUpload("bucket", "key", nil)
c.Assert(err, check.IsNil)
// Create a byte array of 5MB.
data := bytes.Repeat([]byte("0123456789abcdef"), 5*1024*1024/16)
completedParts := completeMultipartUpload{}
for i := 1; i <= 10; i++ {
hasher := md5.New()
hasher.Write(data)
expectedMD5Sumhex := hex.EncodeToString(hasher.Sum(nil))
var calculatedMD5sum string
calculatedMD5sum, err = obj.PutObjectPart("bucket", "key", uploadID, i, int64(len(data)), bytes.NewBuffer(data), expectedMD5Sumhex)
c.Assert(err, check.IsNil)
c.Assert(calculatedMD5sum, check.Equals, expectedMD5Sumhex)
completedParts.Parts = append(completedParts.Parts, completePart{PartNumber: i, ETag: calculatedMD5sum})
}
md5Sum, err := obj.CompleteMultipartUpload("bucket", "key", uploadID, completedParts.Parts)
c.Assert(err, check.IsNil)
c.Assert(md5Sum, check.Equals, "7d364cb728ce42a74a96d22949beefb2-10")
}
// Tests validate abortion of Multipart operation.
func testMultipartObjectAbort(c *check.C, create func() ObjectLayer) {
obj := create()
err := obj.MakeBucket("bucket")
c.Assert(err, check.IsNil)
uploadID, err := obj.NewMultipartUpload("bucket", "key", nil)
c.Assert(err, check.IsNil)
parts := make(map[int]string)
metadata := make(map[string]string)
for i := 1; i <= 10; i++ {
randomPerm := rand.Perm(10)
randomString := ""
for _, num := range randomPerm {
randomString = randomString + strconv.Itoa(num)
}
hasher := md5.New()
hasher.Write([]byte(randomString))
expectedMD5Sumhex := hex.EncodeToString(hasher.Sum(nil))
metadata["md5"] = expectedMD5Sumhex
var calculatedMD5sum string
calculatedMD5sum, err = obj.PutObjectPart("bucket", "key", uploadID, i, int64(len(randomString)), bytes.NewBufferString(randomString), expectedMD5Sumhex)
c.Assert(err, check.IsNil)
c.Assert(calculatedMD5sum, check.Equals, expectedMD5Sumhex)
parts[i] = expectedMD5Sumhex
}
err = obj.AbortMultipartUpload("bucket", "key", uploadID)
c.Assert(err, check.IsNil)
}
// Tests validate object creation.
func testMultipleObjectCreation(c *check.C, create func() ObjectLayer) {
objects := make(map[string][]byte)
obj := create()
err := obj.MakeBucket("bucket")
c.Assert(err, check.IsNil)
for i := 0; i < 10; i++ {
randomPerm := rand.Perm(100)
randomString := ""
for _, num := range randomPerm {
randomString = randomString + strconv.Itoa(num)
}
hasher := md5.New()
hasher.Write([]byte(randomString))
expectedMD5Sumhex := hex.EncodeToString(hasher.Sum(nil))
key := "obj" + strconv.Itoa(i)
objects[key] = []byte(randomString)
metadata := make(map[string]string)
metadata["md5Sum"] = expectedMD5Sumhex
var md5Sum string
md5Sum, err = obj.PutObject("bucket", key, int64(len(randomString)), bytes.NewBufferString(randomString), metadata)
c.Assert(err, check.IsNil)
c.Assert(md5Sum, check.Equals, expectedMD5Sumhex)
}
for key, value := range objects {
var byteBuffer bytes.Buffer
err = obj.GetObject("bucket", key, 0, int64(len(value)), &byteBuffer)
c.Assert(err, check.IsNil)
c.Assert(byteBuffer.Bytes(), check.DeepEquals, value)
objInfo, err := obj.GetObjectInfo("bucket", key)
c.Assert(err, check.IsNil)
c.Assert(objInfo.Size, check.Equals, int64(len(value)))
}
}
// Tests validate creation of objects and the order of listing using various filters for ListObjects operation.
func testPaging(c *check.C, create func() ObjectLayer) {
obj := create()
obj.MakeBucket("bucket")
result, err := obj.ListObjects("bucket", "", "", "", 0)
c.Assert(err, check.IsNil)
c.Assert(len(result.Objects), check.Equals, 0)
c.Assert(result.IsTruncated, check.Equals, false)
// check before paging occurs.
for i := 0; i < 5; i++ {
key := "obj" + strconv.Itoa(i)
_, err = obj.PutObject("bucket", key, int64(len("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.")), bytes.NewBufferString("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed."), nil)
c.Assert(err, check.IsNil)
result, err = obj.ListObjects("bucket", "", "", "", 5)
c.Assert(err, check.IsNil)
c.Assert(len(result.Objects), check.Equals, i+1)
c.Assert(result.IsTruncated, check.Equals, false)
}
// check after paging occurs pages work.
for i := 6; i <= 10; i++ {
key := "obj" + strconv.Itoa(i)
_, err = obj.PutObject("bucket", key, int64(len("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.")), bytes.NewBufferString("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed."), nil)
c.Assert(err, check.IsNil)
result, err = obj.ListObjects("bucket", "obj", "", "", 5)
c.Assert(err, check.IsNil)
c.Assert(len(result.Objects), check.Equals, 5)
c.Assert(result.IsTruncated, check.Equals, true)
}
// check paging with prefix at end returns less objects.
{
_, err = obj.PutObject("bucket", "newPrefix", int64(len("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.")), bytes.NewBufferString("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed."), nil)
c.Assert(err, check.IsNil)
_, err = obj.PutObject("bucket", "newPrefix2", int64(len("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.")), bytes.NewBufferString("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed."), nil)
c.Assert(err, check.IsNil)
result, err = obj.ListObjects("bucket", "new", "", "", 5)
c.Assert(err, check.IsNil)
c.Assert(len(result.Objects), check.Equals, 2)
}
// check ordering of pages.
{
result, err = obj.ListObjects("bucket", "", "", "", 1000)
c.Assert(err, check.IsNil)
c.Assert(result.Objects[0].Name, check.Equals, "newPrefix")
c.Assert(result.Objects[1].Name, check.Equals, "newPrefix2")
c.Assert(result.Objects[2].Name, check.Equals, "obj0")
c.Assert(result.Objects[3].Name, check.Equals, "obj1")
c.Assert(result.Objects[4].Name, check.Equals, "obj10")
}
// check delimited results with delimiter and prefix.
{
_, err = obj.PutObject("bucket", "this/is/delimited", int64(len("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.")), bytes.NewBufferString("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed."), nil)
c.Assert(err, check.IsNil)
_, err = obj.PutObject("bucket", "this/is/also/a/delimited/file", int64(len("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.")), bytes.NewBufferString("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed."), nil)
c.Assert(err, check.IsNil)
result, err = obj.ListObjects("bucket", "this/is/", "", "/", 10)
c.Assert(err, check.IsNil)
c.Assert(len(result.Objects), check.Equals, 1)
c.Assert(result.Prefixes[0], check.Equals, "this/is/also/")
}
// check delimited results with delimiter without prefix.
{
result, err = obj.ListObjects("bucket", "", "", "/", 1000)
c.Assert(err, check.IsNil)
c.Assert(result.Objects[0].Name, check.Equals, "newPrefix")
c.Assert(result.Objects[1].Name, check.Equals, "newPrefix2")
c.Assert(result.Objects[2].Name, check.Equals, "obj0")
c.Assert(result.Objects[3].Name, check.Equals, "obj1")
c.Assert(result.Objects[4].Name, check.Equals, "obj10")
c.Assert(result.Prefixes[0], check.Equals, "this/")
}
// check results with Marker.
{
result, err = obj.ListObjects("bucket", "", "newPrefix", "", 3)
c.Assert(err, check.IsNil)
c.Assert(result.Objects[0].Name, check.Equals, "newPrefix2")
c.Assert(result.Objects[1].Name, check.Equals, "obj0")
c.Assert(result.Objects[2].Name, check.Equals, "obj1")
}
// check ordering of results with prefix.
{
result, err = obj.ListObjects("bucket", "obj", "", "", 1000)
c.Assert(err, check.IsNil)
c.Assert(result.Objects[0].Name, check.Equals, "obj0")
c.Assert(result.Objects[1].Name, check.Equals, "obj1")
c.Assert(result.Objects[2].Name, check.Equals, "obj10")
c.Assert(result.Objects[3].Name, check.Equals, "obj2")
c.Assert(result.Objects[4].Name, check.Equals, "obj3")
}
// check ordering of results with prefix and no paging.
{
result, err = obj.ListObjects("bucket", "new", "", "", 5)
c.Assert(err, check.IsNil)
c.Assert(result.Objects[0].Name, check.Equals, "newPrefix")
c.Assert(result.Objects[1].Name, check.Equals, "newPrefix2")
}
}
// Tests validate overwriting of an existing object.
func testObjectOverwriteWorks(c *check.C, create func() ObjectLayer) {
obj := create()
err := obj.MakeBucket("bucket")
c.Assert(err, check.IsNil)
_, err = obj.PutObject("bucket", "object", int64(len("The list of parts was not in ascending order. The parts list must be specified in order by part number.")), bytes.NewBufferString("The list of parts was not in ascending order. The parts list must be specified in order by part number."), nil)
c.Assert(err, check.IsNil)
length := int64(len("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed."))
_, err = obj.PutObject("bucket", "object", length, bytes.NewBufferString("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed."), nil)
c.Assert(err, check.IsNil)
var bytesBuffer bytes.Buffer
err = obj.GetObject("bucket", "object", 0, length, &bytesBuffer)
c.Assert(err, check.IsNil)
c.Assert(string(bytesBuffer.Bytes()), check.Equals, "The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.")
}
// Tests validate that bucket operation on non-existent bucket fails.
func testNonExistantBucketOperations(c *check.C, create func() ObjectLayer) {
obj := create()
_, err := obj.PutObject("bucket1", "object", int64(len("one")), bytes.NewBufferString("one"), nil)
c.Assert(err, check.Not(check.IsNil))
c.Assert(err.Error(), check.Equals, "Bucket not found: bucket1")
}
// Tests validate that recreation of the bucket fails.
func testBucketRecreateFails(c *check.C, create func() ObjectLayer) {
obj := create()
err := obj.MakeBucket("string")
c.Assert(err, check.IsNil)
err = obj.MakeBucket("string")
c.Assert(err, check.Not(check.IsNil))
c.Assert(err.Error(), check.Equals, "Bucket exists: string")
}
// Tests validate PutObject with subdirectory prefix.
func testPutObjectInSubdir(c *check.C, create func() ObjectLayer) {
obj := create()
err := obj.MakeBucket("bucket")
c.Assert(err, check.IsNil)
length := int64(len("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed."))
_, err = obj.PutObject("bucket", "dir1/dir2/object", length, bytes.NewBufferString("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed."), nil)
c.Assert(err, check.IsNil)
var bytesBuffer bytes.Buffer
err = obj.GetObject("bucket", "dir1/dir2/object", 0, length, &bytesBuffer)
c.Assert(err, check.IsNil)
c.Assert(len(bytesBuffer.Bytes()), check.Equals, len("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed."))
}
// Tests validate ListBuckets.
func testListBuckets(c *check.C, create func() ObjectLayer) {
obj := create()
// test empty list.
buckets, err := obj.ListBuckets()
c.Assert(err, check.IsNil)
c.Assert(len(buckets), check.Equals, 0)
// add one and test exists.
err = obj.MakeBucket("bucket1")
c.Assert(err, check.IsNil)
buckets, err = obj.ListBuckets()
c.Assert(len(buckets), check.Equals, 1)
c.Assert(err, check.IsNil)
// add two and test exists.
err = obj.MakeBucket("bucket2")
c.Assert(err, check.IsNil)
buckets, err = obj.ListBuckets()
c.Assert(len(buckets), check.Equals, 2)
c.Assert(err, check.IsNil)
// add three and test exists + prefix.
err = obj.MakeBucket("bucket22")
buckets, err = obj.ListBuckets()
c.Assert(len(buckets), check.Equals, 3)
c.Assert(err, check.IsNil)
}
// Tests validate the order of result of ListBuckets.
func testListBucketsOrder(c *check.C, create func() ObjectLayer) {
// if implementation contains a map, order of map keys will vary.
// this ensures they return in the same order each time.
for i := 0; i < 10; i++ {
obj := create()
// add one and test exists.
err := obj.MakeBucket("bucket1")
c.Assert(err, check.IsNil)
err = obj.MakeBucket("bucket2")
c.Assert(err, check.IsNil)
buckets, err := obj.ListBuckets()
c.Assert(err, check.IsNil)
c.Assert(len(buckets), check.Equals, 2)
c.Assert(buckets[0].Name, check.Equals, "bucket1")
c.Assert(buckets[1].Name, check.Equals, "bucket2")
}
}
// Tests validate that ListObjects operation on a non-existent bucket fails as expected.
func testListObjectsTestsForNonExistantBucket(c *check.C, create func() ObjectLayer) {
obj := create()
result, err := obj.ListObjects("bucket", "", "", "", 1000)
c.Assert(err, check.Not(check.IsNil))
c.Assert(result.IsTruncated, check.Equals, false)
c.Assert(len(result.Objects), check.Equals, 0)
c.Assert(err.Error(), check.Equals, "Bucket not found: bucket")
}
// Tests validate that GetObject fails on a non-existent bucket as expected.
func testNonExistantObjectInBucket(c *check.C, create func() ObjectLayer) {
obj := create()
err := obj.MakeBucket("bucket")
c.Assert(err, check.IsNil)
var bytesBuffer bytes.Buffer
err = obj.GetObject("bucket", "dir1", 0, 10, &bytesBuffer)
c.Assert(err, check.Not(check.IsNil))
switch err := err.(type) {
case ObjectNotFound:
c.Assert(err, check.ErrorMatches, "Object not found: bucket#dir1")
default:
c.Assert(err, check.Equals, "fails")
}
}
// Tests validate that GetObject on an existing directory fails as expected.
func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() ObjectLayer) {
obj := create()
err := obj.MakeBucket("bucket")
c.Assert(err, check.IsNil)
_, err = obj.PutObject("bucket", "dir1/dir3/object", int64(len("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.")), bytes.NewBufferString("One or more of the specified parts could not be found. The part might not have been uploaded, or the specified entity tag might not have matched the part's entity tag."), nil)
c.Assert(err, check.IsNil)
var bytesBuffer bytes.Buffer
err = obj.GetObject("bucket", "dir1", 0, 10, &bytesBuffer)
switch err := err.(type) {
case ObjectNotFound:
c.Assert(err.Bucket, check.Equals, "bucket")
c.Assert(err.Object, check.Equals, "dir1")
default:
// force a failure with a line number.
c.Assert(err, check.Equals, "ObjectNotFound")
}
err = obj.GetObject("bucket", "dir1/", 0, 10, &bytesBuffer)
switch err := err.(type) {
case ObjectNameInvalid:
c.Assert(err.Bucket, check.Equals, "bucket")
c.Assert(err.Object, check.Equals, "dir1/")
default:
// force a failure with a line number.
c.Assert(err, check.Equals, "ObjectNotFound")
}
}
// Tests valdiate the default ContentType.
func testDefaultContentType(c *check.C, create func() ObjectLayer) {
obj := create()
err := obj.MakeBucket("bucket")
c.Assert(err, check.IsNil)
// Test empty.
_, err = obj.PutObject("bucket", "one", int64(len("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed.")), bytes.NewBufferString("The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed."), nil)
c.Assert(err, check.IsNil)
objInfo, err := obj.GetObjectInfo("bucket", "one")
c.Assert(err, check.IsNil)
c.Assert(objInfo.ContentType, check.Equals, "application/octet-stream")
}
-95
View File
@@ -1,95 +0,0 @@
/*
* Minio Cloud Storage (C) 2015-2016 Minio, Inc.
*
* 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.
*/
// NOTE - Rename() not guaranteed to be atomic on all filesystems which are not fully POSIX compatible
// Package atomic provides atomic file write semantics by leveraging Rename's() atomicity.
package atomic
import (
"io/ioutil"
"os"
"path/filepath"
)
// File container provided for atomic file writes
type File struct {
*os.File
file string
}
// CloseAndSync sync file to disk and close, returns an error if any
func (f *File) CloseAndSync() error {
// sync to the disk
if err := f.File.Sync(); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
return nil
}
// Close the file, returns an error if any
func (f *File) Close() error {
// close the embedded fd
if err := f.File.Close(); err != nil {
return err
}
// atomic rename to final destination
if err := os.Rename(f.Name(), f.file); err != nil {
return err
}
return nil
}
// CloseAndPurge removes the temp file, closes the transaction and returns an error if any
func (f *File) CloseAndPurge() error {
// close the embedded fd
if err := f.File.Close(); err != nil {
return err
}
if err := os.Remove(f.Name()); err != nil {
return err
}
return nil
}
// FileCreate creates a new file at filePath for atomic writes, it also creates parent directories if they don't exist
func FileCreate(filePath string) (*File, error) {
return FileCreateWithPrefix(filePath, "$deleteme.")
}
// FileCreateWithPrefix creates a new file at filePath for atomic writes, it also creates parent directories if they don't exist
// prefix specifies the prefix of the temporary files so that cleaning stale temp files is easy
func FileCreateWithPrefix(filePath string, prefix string) (*File, error) {
// if parent directories do not exist, ioutil.TempFile doesn't create them
// handle such a case with os.MkdirAll()
if err := os.MkdirAll(filepath.Dir(filePath), 0700); err != nil {
return nil, err
}
f, err := ioutil.TempFile(filepath.Dir(filePath), prefix+filepath.Base(filePath))
if err != nil {
return nil, err
}
if err = os.Chmod(f.Name(), 0600); err != nil {
if err = os.Remove(f.Name()); err != nil {
return nil, err
}
return nil, err
}
return &File{File: f, file: filePath}, nil
}
-1
View File
@@ -1 +0,0 @@
*.syso
-150
View File
@@ -1,150 +0,0 @@
/*
* Minio Cloud Storage, (C) 2015-2016 Minio, Inc.
*
* 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.
*/
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file of
// Golang project:
// https://github.com/golang/go/blob/master/LICENSE
// Using this part of Minio codebase under the license
// Apache License Version 2.0 with modifications
// Package sha1 implements the SHA1 hash algorithm as defined in RFC 3174.
package sha1
import "hash"
// Size - The size of a SHA1 checksum in bytes.
const Size = 20
// BlockSize - The blocksize of SHA1 in bytes.
const BlockSize = 64
const (
chunk = 64
init0 = 0x67452301
init1 = 0xEFCDAB89
init2 = 0x98BADCFE
init3 = 0x10325476
init4 = 0xC3D2E1F0
)
// digest represents the partial evaluation of a checksum.
type digest struct {
h [5]uint32
x [chunk]byte
nx int
len uint64
}
// Reset digest
func (d *digest) Reset() {
d.h[0] = init0
d.h[1] = init1
d.h[2] = init2
d.h[3] = init3
d.h[4] = init4
d.nx = 0
d.len = 0
}
// New returns a new hash.Hash computing the SHA1 checksum.
func New() hash.Hash {
d := new(digest)
d.Reset()
return d
}
// Return output size
func (d *digest) Size() int { return Size }
// Return checksum blocksize
func (d *digest) BlockSize() int { return BlockSize }
// Write to digest
func (d *digest) Write(p []byte) (nn int, err error) {
nn = len(p)
d.len += uint64(nn)
if d.nx > 0 {
n := copy(d.x[d.nx:], p)
d.nx += n
if d.nx == chunk {
block(d, d.x[:])
d.nx = 0
}
p = p[n:]
}
if len(p) >= chunk {
n := len(p) &^ (chunk - 1)
block(d, p[:n])
p = p[n:]
}
if len(p) > 0 {
d.nx = copy(d.x[:], p)
}
return
}
// Return checksum bytes
func (d *digest) Sum(in []byte) []byte {
// Make a copy of d0 so that caller can keep writing and summing.
d0 := *d
hash := d0.checkSum()
return append(in, hash[:]...)
}
// Intermediate checksum function
func (d *digest) checkSum() [Size]byte {
len := d.len
// Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
var tmp [64]byte
tmp[0] = 0x80
if len%64 < 56 {
d.Write(tmp[0 : 56-len%64])
} else {
d.Write(tmp[0 : 64+56-len%64])
}
// Length in bits.
len <<= 3
for i := uint(0); i < 8; i++ {
tmp[i] = byte(len >> (56 - 8*i))
}
d.Write(tmp[0:8])
if d.nx != 0 {
panic("d.nx != 0")
}
var digest [Size]byte
for i, s := range d.h {
digest[i*4] = byte(s >> 24)
digest[i*4+1] = byte(s >> 16)
digest[i*4+2] = byte(s >> 8)
digest[i*4+3] = byte(s)
}
return digest
}
// Sum - single caller sha1 helper
func Sum(data []byte) [Size]byte {
var d digest
d.Reset()
d.Write(data)
return d.checkSum()
}
-967
View File
@@ -1,967 +0,0 @@
/*
* Implement fast SHA-1 with AVX2 instructions. (x86_64)
*
* This file is provided under a dual BSD/GPLv2 license. When using or
* redistributing this file, you may do so under either license.
*
* GPL LICENSE SUMMARY
*
* Copyright(c) 2014 Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* 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
* General Public License for more details.
*
* Contact Information:
* Ilya Albrekht <ilya.albrekht@intel.com>
* Maxim Locktyukhin <maxim.locktyukhin@intel.com>
* Ronen Zohar <ronen.zohar@intel.com>
* Chandramouli Narayanan <mouli@linux.intel.com>
*
* BSD LICENSE
*
* Copyright(c) 2014 Intel Corporation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/*
* SHA-1 implementation with Intel(R) AVX2 instruction set extensions.
*
*This implementation is based on the previous SSSE3 release:
* https://software.intel.com/en-us/articles/improving-the-performance-of-the-secure-hash-algorithm-1
*
*Updates 20-byte SHA-1 record in 'hash' for even number of
*'num_blocks' consecutive 64-byte blocks
*
*/
/*
* Using this part of Minio codebase under the license
* Apache License Version 2.0 with modifications
*
*/
#ifdef HAS_AVX2
#ifndef ENTRY
#define ENTRY(name) \
.globl name ; \
.align 4,0x90 ; \
name:
#endif
#ifndef END
#define END(name) \
.size name, .-name
#endif
#ifndef ENDPROC
#define ENDPROC(name) \
.type name, @function ; \
END(name)
#endif
#define NUM_INVALID 100
#define TYPE_R32 0
#define TYPE_R64 1
#define TYPE_XMM 2
#define TYPE_INVALID 100
.macro R32_NUM opd r32
\opd = NUM_INVALID
.ifc \r32,%eax
\opd = 0
.endif
.ifc \r32,%ecx
\opd = 1
.endif
.ifc \r32,%edx
\opd = 2
.endif
.ifc \r32,%ebx
\opd = 3
.endif
.ifc \r32,%esp
\opd = 4
.endif
.ifc \r32,%ebp
\opd = 5
.endif
.ifc \r32,%esi
\opd = 6
.endif
.ifc \r32,%edi
\opd = 7
.endif
#ifdef X86_64
.ifc \r32,%r8d
\opd = 8
.endif
.ifc \r32,%r9d
\opd = 9
.endif
.ifc \r32,%r10d
\opd = 10
.endif
.ifc \r32,%r11d
\opd = 11
.endif
.ifc \r32,%r12d
\opd = 12
.endif
.ifc \r32,%r13d
\opd = 13
.endif
.ifc \r32,%r14d
\opd = 14
.endif
.ifc \r32,%r15d
\opd = 15
.endif
#endif
.endm
.macro R64_NUM opd r64
\opd = NUM_INVALID
#ifdef X86_64
.ifc \r64,%rax
\opd = 0
.endif
.ifc \r64,%rcx
\opd = 1
.endif
.ifc \r64,%rdx
\opd = 2
.endif
.ifc \r64,%rbx
\opd = 3
.endif
.ifc \r64,%rsp
\opd = 4
.endif
.ifc \r64,%rbp
\opd = 5
.endif
.ifc \r64,%rsi
\opd = 6
.endif
.ifc \r64,%rdi
\opd = 7
.endif
.ifc \r64,%r8
\opd = 8
.endif
.ifc \r64,%r9
\opd = 9
.endif
.ifc \r64,%r10
\opd = 10
.endif
.ifc \r64,%r11
\opd = 11
.endif
.ifc \r64,%r12
\opd = 12
.endif
.ifc \r64,%r13
\opd = 13
.endif
.ifc \r64,%r14
\opd = 14
.endif
.ifc \r64,%r15
\opd = 15
.endif
#endif
.endm
.macro XMM_NUM opd xmm
\opd = NUM_INVALID
.ifc \xmm,%xmm0
\opd = 0
.endif
.ifc \xmm,%xmm1
\opd = 1
.endif
.ifc \xmm,%xmm2
\opd = 2
.endif
.ifc \xmm,%xmm3
\opd = 3
.endif
.ifc \xmm,%xmm4
\opd = 4
.endif
.ifc \xmm,%xmm5
\opd = 5
.endif
.ifc \xmm,%xmm6
\opd = 6
.endif
.ifc \xmm,%xmm7
\opd = 7
.endif
.ifc \xmm,%xmm8
\opd = 8
.endif
.ifc \xmm,%xmm9
\opd = 9
.endif
.ifc \xmm,%xmm10
\opd = 10
.endif
.ifc \xmm,%xmm11
\opd = 11
.endif
.ifc \xmm,%xmm12
\opd = 12
.endif
.ifc \xmm,%xmm13
\opd = 13
.endif
.ifc \xmm,%xmm14
\opd = 14
.endif
.ifc \xmm,%xmm15
\opd = 15
.endif
.endm
.macro TYPE type reg
R32_NUM reg_type_r32 \reg
R64_NUM reg_type_r64 \reg
XMM_NUM reg_type_xmm \reg
.if reg_type_r64 <> NUM_INVALID
\type = TYPE_R64
.elseif reg_type_r32 <> NUM_INVALID
\type = TYPE_R32
.elseif reg_type_xmm <> NUM_INVALID
\type = TYPE_XMM
.else
\type = TYPE_INVALID
.endif
.endm
.macro PFX_OPD_SIZE
.byte 0x66
.endm
.macro PFX_REX opd1 opd2 W=0
.if ((\opd1 | \opd2) & 8) || \W
.byte 0x40 | ((\opd1 & 8) >> 3) | ((\opd2 & 8) >> 1) | (\W << 3)
.endif
.endm
.macro MODRM mod opd1 opd2
.byte \mod | (\opd1 & 7) | ((\opd2 & 7) << 3)
.endm
.macro PSHUFB_XMM xmm1 xmm2
XMM_NUM pshufb_opd1 \xmm1
XMM_NUM pshufb_opd2 \xmm2
PFX_OPD_SIZE
PFX_REX pshufb_opd1 pshufb_opd2
.byte 0x0f, 0x38, 0x00
MODRM 0xc0 pshufb_opd1 pshufb_opd2
.endm
.macro PCLMULQDQ imm8 xmm1 xmm2
XMM_NUM clmul_opd1 \xmm1
XMM_NUM clmul_opd2 \xmm2
PFX_OPD_SIZE
PFX_REX clmul_opd1 clmul_opd2
.byte 0x0f, 0x3a, 0x44
MODRM 0xc0 clmul_opd1 clmul_opd2
.byte \imm8
.endm
.macro PEXTRD imm8 xmm gpr
R32_NUM extrd_opd1 \gpr
XMM_NUM extrd_opd2 \xmm
PFX_OPD_SIZE
PFX_REX extrd_opd1 extrd_opd2
.byte 0x0f, 0x3a, 0x16
MODRM 0xc0 extrd_opd1 extrd_opd2
.byte \imm8
.endm
.macro MOVQ_R64_XMM opd1 opd2
TYPE movq_r64_xmm_opd1_type \opd1
.if movq_r64_xmm_opd1_type == TYPE_XMM
XMM_NUM movq_r64_xmm_opd1 \opd1
R64_NUM movq_r64_xmm_opd2 \opd2
.else
R64_NUM movq_r64_xmm_opd1 \opd1
XMM_NUM movq_r64_xmm_opd2 \opd2
.endif
PFX_OPD_SIZE
PFX_REX movq_r64_xmm_opd1 movq_r64_xmm_opd2 1
.if movq_r64_xmm_opd1_type == TYPE_XMM
.byte 0x0f, 0x7e
.else
.byte 0x0f, 0x6e
.endif
MODRM 0xc0 movq_r64_xmm_opd1 movq_r64_xmm_opd2
.endm
#define CTX %rdi /* arg1 */
#define BUF %rsi /* arg2 */
#define CNT %rdx /* arg3 */
#define REG_A %ecx
#define REG_B %esi
#define REG_C %edi
#define REG_D %eax
#define REG_E %edx
#define REG_TB %ebx
#define REG_TA %r12d
#define REG_RA %rcx
#define REG_RB %rsi
#define REG_RC %rdi
#define REG_RD %rax
#define REG_RE %rdx
#define REG_RTA %r12
#define REG_RTB %rbx
#define REG_T1 %ebp
#define xmm_mov vmovups
#define avx2_zeroupper vzeroupper
#define RND_F1 1
#define RND_F2 2
#define RND_F3 3
.macro REGALLOC
.set A, REG_A
.set B, REG_B
.set C, REG_C
.set D, REG_D
.set E, REG_E
.set TB, REG_TB
.set TA, REG_TA
.set RA, REG_RA
.set RB, REG_RB
.set RC, REG_RC
.set RD, REG_RD
.set RE, REG_RE
.set RTA, REG_RTA
.set RTB, REG_RTB
.set T1, REG_T1
.endm
#define K_BASE %r8
#define HASH_PTR %r9
#define BUFFER_PTR %r10
#define BUFFER_PTR2 %r13
#define BUFFER_END %r11
#define PRECALC_BUF %r14
#define WK_BUF %r15
#define W_TMP %xmm0
#define WY_TMP %ymm0
#define WY_TMP2 %ymm9
# AVX2 variables
#define WY0 %ymm3
#define WY4 %ymm5
#define WY08 %ymm7
#define WY12 %ymm8
#define WY16 %ymm12
#define WY20 %ymm13
#define WY24 %ymm14
#define WY28 %ymm15
#define YMM_SHUFB_BSWAP %ymm10
/*
* Keep 2 iterations precalculated at a time:
* - 80 DWORDs per iteration * 2
*/
#define W_SIZE (80*2*2 +16)
#define WK(t) ((((t) % 80) / 4)*32 + ( (t) % 4)*4 + ((t)/80)*16 )(WK_BUF)
#define PRECALC_WK(t) ((t)*2*2)(PRECALC_BUF)
.macro UPDATE_HASH hash, val
add \hash, \val
mov \val, \hash
.endm
.macro PRECALC_RESET_WY
.set WY_00, WY0
.set WY_04, WY4
.set WY_08, WY08
.set WY_12, WY12
.set WY_16, WY16
.set WY_20, WY20
.set WY_24, WY24
.set WY_28, WY28
.set WY_32, WY_00
.endm
.macro PRECALC_ROTATE_WY
/* Rotate macros */
.set WY_32, WY_28
.set WY_28, WY_24
.set WY_24, WY_20
.set WY_20, WY_16
.set WY_16, WY_12
.set WY_12, WY_08
.set WY_08, WY_04
.set WY_04, WY_00
.set WY_00, WY_32
/* Define register aliases */
.set WY, WY_00
.set WY_minus_04, WY_04
.set WY_minus_08, WY_08
.set WY_minus_12, WY_12
.set WY_minus_16, WY_16
.set WY_minus_20, WY_20
.set WY_minus_24, WY_24
.set WY_minus_28, WY_28
.set WY_minus_32, WY
.endm
.macro PRECALC_00_15
.if (i == 0) # Initialize and rotate registers
PRECALC_RESET_WY
PRECALC_ROTATE_WY
.endif
/* message scheduling pre-compute for rounds 0-15 */
.if ((i & 7) == 0)
/*
* blended AVX2 and ALU instruction scheduling
* 1 vector iteration per 8 rounds
*/
vmovdqu ((i * 2) + PRECALC_OFFSET)(BUFFER_PTR), W_TMP
.elseif ((i & 7) == 1)
vinsertf128 $1, (((i-1) * 2)+PRECALC_OFFSET)(BUFFER_PTR2),\
WY_TMP, WY_TMP
.elseif ((i & 7) == 2)
vpshufb YMM_SHUFB_BSWAP, WY_TMP, WY
.elseif ((i & 7) == 4)
vpaddd K_XMM(K_BASE), WY, WY_TMP
.elseif ((i & 7) == 7)
vmovdqu WY_TMP, PRECALC_WK(i&~7)
PRECALC_ROTATE_WY
.endif
.endm
.macro PRECALC_16_31
/*
* message scheduling pre-compute for rounds 16-31
* calculating last 32 w[i] values in 8 XMM registers
* pre-calculate K+w[i] values and store to mem
* for later load by ALU add instruction
*
* "brute force" vectorization for rounds 16-31 only
* due to w[i]->w[i-3] dependency
*/
.if ((i & 7) == 0)
/*
* blended AVX2 and ALU instruction scheduling
* 1 vector iteration per 8 rounds
*/
/* w[i-14] */
vpalignr $8, WY_minus_16, WY_minus_12, WY
vpsrldq $4, WY_minus_04, WY_TMP /* w[i-3] */
.elseif ((i & 7) == 1)
vpxor WY_minus_08, WY, WY
vpxor WY_minus_16, WY_TMP, WY_TMP
.elseif ((i & 7) == 2)
vpxor WY_TMP, WY, WY
vpslldq $12, WY, WY_TMP2
.elseif ((i & 7) == 3)
vpslld $1, WY, WY_TMP
vpsrld $31, WY, WY
.elseif ((i & 7) == 4)
vpor WY, WY_TMP, WY_TMP
vpslld $2, WY_TMP2, WY
.elseif ((i & 7) == 5)
vpsrld $30, WY_TMP2, WY_TMP2
vpxor WY, WY_TMP, WY_TMP
.elseif ((i & 7) == 7)
vpxor WY_TMP2, WY_TMP, WY
vpaddd K_XMM(K_BASE), WY, WY_TMP
vmovdqu WY_TMP, PRECALC_WK(i&~7)
PRECALC_ROTATE_WY
.endif
.endm
.macro PRECALC_32_79
/*
* in SHA-1 specification:
* w[i] = (w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]) rol 1
* instead we do equal:
* w[i] = (w[i-6] ^ w[i-16] ^ w[i-28] ^ w[i-32]) rol 2
* allows more efficient vectorization
* since w[i]=>w[i-3] dependency is broken
*/
.if ((i & 7) == 0)
/*
* blended AVX2 and ALU instruction scheduling
* 1 vector iteration per 8 rounds
*/
vpalignr $8, WY_minus_08, WY_minus_04, WY_TMP
.elseif ((i & 7) == 1)
/* W is W_minus_32 before xor */
vpxor WY_minus_28, WY, WY
.elseif ((i & 7) == 2)
vpxor WY_minus_16, WY_TMP, WY_TMP
.elseif ((i & 7) == 3)
vpxor WY_TMP, WY, WY
.elseif ((i & 7) == 4)
vpslld $2, WY, WY_TMP
.elseif ((i & 7) == 5)
vpsrld $30, WY, WY
vpor WY, WY_TMP, WY
.elseif ((i & 7) == 7)
vpaddd K_XMM(K_BASE), WY, WY_TMP
vmovdqu WY_TMP, PRECALC_WK(i&~7)
PRECALC_ROTATE_WY
.endif
.endm
.macro PRECALC r, s
.set i, \r
.if (i < 40)
.set K_XMM, 32*0
.elseif (i < 80)
.set K_XMM, 32*1
.elseif (i < 120)
.set K_XMM, 32*2
.else
.set K_XMM, 32*3
.endif
.if (i<32)
PRECALC_00_15 \s
.elseif (i<64)
PRECALC_16_31 \s
.elseif (i < 160)
PRECALC_32_79 \s
.endif
.endm
.macro ROTATE_STATE
.set T_REG, E
.set E, D
.set D, C
.set C, B
.set B, TB
.set TB, A
.set A, T_REG
.set T_REG, RE
.set RE, RD
.set RD, RC
.set RC, RB
.set RB, RTB
.set RTB, RA
.set RA, T_REG
.endm
/* Macro relies on saved ROUND_Fx */
.macro RND_FUN f, r
.if (\f == RND_F1)
ROUND_F1 \r
.elseif (\f == RND_F2)
ROUND_F2 \r
.elseif (\f == RND_F3)
ROUND_F3 \r
.endif
.endm
.macro RR r
.set round_id, (\r % 80)
.if (round_id == 0) /* Precalculate F for first round */
.set ROUND_FUNC, RND_F1
mov B, TB
rorx $(32-30), B, B /* b>>>2 */
andn D, TB, T1
and C, TB
xor T1, TB
.endif
RND_FUN ROUND_FUNC, \r
ROTATE_STATE
.if (round_id == 18)
.set ROUND_FUNC, RND_F2
.elseif (round_id == 38)
.set ROUND_FUNC, RND_F3
.elseif (round_id == 58)
.set ROUND_FUNC, RND_F2
.endif
.set round_id, ( (\r+1) % 80)
RND_FUN ROUND_FUNC, (\r+1)
ROTATE_STATE
.endm
.macro ROUND_F1 r
add WK(\r), E
andn C, A, T1 /* ~b&d */
lea (RE,RTB), E /* Add F from the previous round */
rorx $(32-5), A, TA /* T2 = A >>> 5 */
rorx $(32-30),A, TB /* b>>>2 for next round */
PRECALC (\r) /* msg scheduling for next 2 blocks */
/*
* Calculate F for the next round
* (b & c) ^ andn[b, d]
*/
and B, A /* b&c */
xor T1, A /* F1 = (b&c) ^ (~b&d) */
lea (RE,RTA), E /* E += A >>> 5 */
.endm
.macro ROUND_F2 r
add WK(\r), E
lea (RE,RTB), E /* Add F from the previous round */
/* Calculate F for the next round */
rorx $(32-5), A, TA /* T2 = A >>> 5 */
.if ((round_id) < 79)
rorx $(32-30), A, TB /* b>>>2 for next round */
.endif
PRECALC (\r) /* msg scheduling for next 2 blocks */
.if ((round_id) < 79)
xor B, A
.endif
add TA, E /* E += A >>> 5 */
.if ((round_id) < 79)
xor C, A
.endif
.endm
.macro ROUND_F3 r
add WK(\r), E
PRECALC (\r) /* msg scheduling for next 2 blocks */
lea (RE,RTB), E /* Add F from the previous round */
mov B, T1
or A, T1
rorx $(32-5), A, TA /* T2 = A >>> 5 */
rorx $(32-30), A, TB /* b>>>2 for next round */
/* Calculate F for the next round
* (b and c) or (d and (b or c))
*/
and C, T1
and B, A
or T1, A
add TA, E /* E += A >>> 5 */
.endm
/*
* macro implements 80 rounds of SHA-1, for multiple blocks with s/w pipelining
*/
.macro SHA1_PIPELINED_MAIN_BODY
REGALLOC
mov (HASH_PTR), A
mov 4(HASH_PTR), B
mov 8(HASH_PTR), C
mov 12(HASH_PTR), D
mov 16(HASH_PTR), E
mov %rsp, PRECALC_BUF
lea (2*4*80+32)(%rsp), WK_BUF
# Precalc WK for first 2 blocks
PRECALC_OFFSET = 0
.set i, 0
.rept 160
PRECALC i
.set i, i + 1
.endr
PRECALC_OFFSET = 128
xchg WK_BUF, PRECALC_BUF
.align 32
_loop:
/*
* code loops through more than one block
* we use K_BASE value as a signal of a last block,
* it is set below by: cmovae BUFFER_PTR, K_BASE
*/
cmp K_BASE, BUFFER_PTR
jne _begin
.align 32
jmp _end
.align 32
_begin:
/*
* Do first block
* rounds: 0,2,4,6,8
*/
.set j, 0
.rept 5
RR j
.set j, j+2
.endr
jmp _loop0
_loop0:
/*
* rounds:
* 10,12,14,16,18
* 20,22,24,26,28
* 30,32,34,36,38
* 40,42,44,46,48
* 50,52,54,56,58
*/
.rept 25
RR j
.set j, j+2
.endr
add $(2*64), BUFFER_PTR /* move to next odd-64-byte block */
cmp BUFFER_END, BUFFER_PTR /* is current block the last one? */
cmovae K_BASE, BUFFER_PTR /* signal the last iteration smartly */
/*
* rounds
* 60,62,64,66,68
* 70,72,74,76,78
*/
.rept 10
RR j
.set j, j+2
.endr
UPDATE_HASH (HASH_PTR), A
UPDATE_HASH 4(HASH_PTR), TB
UPDATE_HASH 8(HASH_PTR), C
UPDATE_HASH 12(HASH_PTR), D
UPDATE_HASH 16(HASH_PTR), E
cmp K_BASE, BUFFER_PTR /* is current block the last one? */
je _loop
mov TB, B
/* Process second block */
/*
* rounds
* 0+80, 2+80, 4+80, 6+80, 8+80
* 10+80,12+80,14+80,16+80,18+80
*/
.set j, 0
.rept 10
RR j+80
.set j, j+2
.endr
jmp _loop1
_loop1:
/*
* rounds
* 20+80,22+80,24+80,26+80,28+80
* 30+80,32+80,34+80,36+80,38+80
*/
.rept 10
RR j+80
.set j, j+2
.endr
jmp _loop2
_loop2:
/*
* rounds
* 40+80,42+80,44+80,46+80,48+80
* 50+80,52+80,54+80,56+80,58+80
*/
.rept 10
RR j+80
.set j, j+2
.endr
add $(2*64), BUFFER_PTR2 /* move to next even-64-byte block */
cmp BUFFER_END, BUFFER_PTR2 /* is current block the last one */
cmovae K_BASE, BUFFER_PTR /* signal the last iteration smartly */
jmp _loop3
_loop3:
/*
* rounds
* 60+80,62+80,64+80,66+80,68+80
* 70+80,72+80,74+80,76+80,78+80
*/
.rept 10
RR j+80
.set j, j+2
.endr
UPDATE_HASH (HASH_PTR), A
UPDATE_HASH 4(HASH_PTR), TB
UPDATE_HASH 8(HASH_PTR), C
UPDATE_HASH 12(HASH_PTR), D
UPDATE_HASH 16(HASH_PTR), E
/* Reset state for AVX2 reg permutation */
mov A, TA
mov TB, A
mov C, TB
mov E, C
mov D, B
mov TA, D
REGALLOC
xchg WK_BUF, PRECALC_BUF
jmp _loop
.align 32
_end:
.endm
.section .rodata
#define K1 0x5a827999
#define K2 0x6ed9eba1
#define K3 0x8f1bbcdc
#define K4 0xca62c1d6
.align 128
K_XMM_AR:
.long K1, K1, K1, K1
.long K1, K1, K1, K1
.long K2, K2, K2, K2
.long K2, K2, K2, K2
.long K3, K3, K3, K3
.long K3, K3, K3, K3
.long K4, K4, K4, K4
.long K4, K4, K4, K4
BSWAP_SHUFB_CTL:
.long 0x00010203
.long 0x04050607
.long 0x08090a0b
.long 0x0c0d0e0f
.long 0x00010203
.long 0x04050607
.long 0x08090a0b
.long 0x0c0d0e0f
# void sha1_transform(int32_t *hash, const char* input, size_t num_blocks) ;
.text
ENTRY(sha1_transform)
push %rbx
push %rbp
push %r12
push %r13
push %r14
push %r15
RESERVE_STACK = (W_SIZE*4 + 8+24)
/* Align stack */
mov %rsp, %rbx
and $~(0x20-1), %rsp
push %rbx
sub $RESERVE_STACK, %rsp
avx2_zeroupper
lea K_XMM_AR(%rip), K_BASE
mov CTX, HASH_PTR
mov BUF, BUFFER_PTR
lea 64(BUF), BUFFER_PTR2
shl $6, CNT /* mul by 64 */
add BUF, CNT
add $64, CNT
mov CNT, BUFFER_END
cmp BUFFER_END, BUFFER_PTR2
cmovae K_BASE, BUFFER_PTR2
xmm_mov BSWAP_SHUFB_CTL(%rip), YMM_SHUFB_BSWAP
SHA1_PIPELINED_MAIN_BODY
avx2_zeroupper
add $RESERVE_STACK, %rsp
pop %rsp
pop %r15
pop %r14
pop %r13
pop %r12
pop %rbp
pop %rbx
ret
ENDPROC(sha1_transform)
#endif
-579
View File
@@ -1,579 +0,0 @@
;---------------------
; https://software.intel.com/en-us/articles/improving-the-performance-of-the-secure-hash-algorithm-1
;
; License information:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This implementation notably advances the performance of SHA-1 algorithm compared to existing
; implementations. We are encouraging all projects utilizing SHA-1 to integrate this new fast
; implementation and are ready to help if issues or concerns arise (you are welcome to leave
; a comment or write an email to the authors). It is provided 'as is' and free for either
; commercial or non-commercial use.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This code implements two interfaces of SHA-1 update function: 1) working on a single
; 64-byte block and 2) working on a buffer of multiple 64-bit blocks. Multiple blocks
; version of code is software pipelined and faster overall, it is a default. Assemble
; with -DINTEL_SHA1_SINGLEBLOCK to select single 64-byte block function interface.
;
; C++ prototypes of implemented functions are below:
;
; #ifndef INTEL_SHA1_SINGLEBLOCK
; // Updates 20-byte SHA-1 record in 'hash' for 'num_blocks' consequtive 64-byte blocks
; extern "C" void sha1_update_intel(int *hash, const char* input, size_t num_blocks );
; #else
; // Updates 20-byte SHA-1 record in 'hash' for one 64-byte block pointed by 'input'
; extern "C" void sha1_update_intel(int *hash, const char* input);
; #endif
;
; Function name 'sha1_update_intel' can be changed in the source or via macro:
; -DINTEL_SHA1_UPDATE_FUNCNAME=my_sha1_update_func_name
;
; It implements both UNIX(default) and Windows ABIs, use -DWIN_ABI on Windows
;
; Code checks CPU for SSSE3 support via CPUID feature flag (CPUID.1.ECX.SSSE3[bit 9]==1),
; and performs dispatch. Since in most cases the functionality on non-SSSE3 supporting CPUs
; is also required, the default (e.g. one being replaced) function can be provided for
; dispatch on such CPUs, the name of old function can be changed in the source or via macro:
; -DINTEL_SHA1_UPDATE_DEFAULT_DISPATCH=default_sha1_update_function_name
;
; Authors: Maxim Locktyukhin and Ronen Zohar at Intel.com
;
%ifndef INTEL_SHA1_UPDATE_DEFAULT_DISPATCH
;; can be replaced with a default SHA-1 update function name
%define INTEL_SHA1_UPDATE_DEFAULT_DISPATCH sha1_intel_non_ssse3_cpu_stub_
%else
extern INTEL_SHA1_UPDATE_DEFAULT_DISPATCH
%endif
;; provide alternative SHA-1 update function's name here
%ifndef INTEL_SHA1_UPDATE_FUNCNAME
%define INTEL_SHA1_UPDATE_FUNCNAME sha1_update_intel
%endif
global INTEL_SHA1_UPDATE_FUNCNAME
%ifndef INTEL_SHA1_SINGLEBLOCK
%assign multiblock 1
%else
%assign multiblock 0
%endif
bits 64
default rel
%ifdef WIN_ABI
%xdefine arg1 rcx
%xdefine arg2 rdx
%xdefine arg3 r8
%else
%xdefine arg1 rdi
%xdefine arg2 rsi
%xdefine arg3 rdx
%endif
%xdefine ctx arg1
%xdefine buf arg2
%xdefine cnt arg3
%macro REGALLOC 0
%xdefine A ecx
%xdefine B esi
%xdefine C edi
%xdefine D ebp
%xdefine E edx
%xdefine T1 eax
%xdefine T2 ebx
%endmacro
%xdefine K_BASE r8
%xdefine HASH_PTR r9
%xdefine BUFFER_PTR r10
%xdefine BUFFER_END r11
%xdefine W_TMP xmm0
%xdefine W_TMP2 xmm9
%xdefine W0 xmm1
%xdefine W4 xmm2
%xdefine W8 xmm3
%xdefine W12 xmm4
%xdefine W16 xmm5
%xdefine W20 xmm6
%xdefine W24 xmm7
%xdefine W28 xmm8
%xdefine XMM_SHUFB_BSWAP xmm10
;; we keep window of 64 w[i]+K pre-calculated values in a circular buffer
%xdefine WK(t) (rsp + (t & 15)*4)
;------------------------------------------------------------------------------
;
; macro implements SHA-1 function's body for single or several 64-byte blocks
; first param: function's name
; second param: =0 - function implements single 64-byte block hash
; =1 - function implements multiple64-byte blocks hash
; 3rd function's argument is a number, greater 0, of 64-byte blocks to calc hash for
;
%macro SHA1_VECTOR_ASM 2
align 4096
%1:
push rbx
push rbp
%ifdef WIN_ABI
push rdi
push rsi
%xdefine stack_size (16*4 + 16*5 + 8)
%else
%xdefine stack_size (16*4 + 8)
%endif
sub rsp, stack_size
%ifdef WIN_ABI
%xdefine xmm_save_base (rsp + 16*4)
xmm_mov [xmm_save_base + 0*16], xmm6
xmm_mov [xmm_save_base + 1*16], xmm7
xmm_mov [xmm_save_base + 2*16], xmm8
xmm_mov [xmm_save_base + 3*16], xmm9
xmm_mov [xmm_save_base + 4*16], xmm10
%endif
mov HASH_PTR, ctx
mov BUFFER_PTR, buf
%if (%2 == 1)
shl cnt, 6 ;; mul by 64
add cnt, buf
mov BUFFER_END, cnt
%endif
lea K_BASE, [K_XMM_AR]
xmm_mov XMM_SHUFB_BSWAP, [bswap_shufb_ctl]
SHA1_PIPELINED_MAIN_BODY %2
%ifdef WIN_ABI
xmm_mov xmm6, [xmm_save_base + 0*16]
xmm_mov xmm7, [xmm_save_base + 1*16]
xmm_mov xmm8, [xmm_save_base + 2*16]
xmm_mov xmm9, [xmm_save_base + 3*16]
xmm_mov xmm10,[xmm_save_base + 4*16]
%endif
add rsp, stack_size
%ifdef WIN_ABI
pop rsi
pop rdi
%endif
pop rbp
pop rbx
ret
%endmacro
;--------------------------------------------
; macro implements 80 rounds of SHA-1, for one 64-byte block or multiple blocks with s/w pipelining
; macro param: =0 - process single 64-byte block
; =1 - multiple blocks
;
%macro SHA1_PIPELINED_MAIN_BODY 1
REGALLOC
mov A, [HASH_PTR ]
mov B, [HASH_PTR+ 4]
mov C, [HASH_PTR+ 8]
mov D, [HASH_PTR+12]
mov E, [HASH_PTR+16]
%assign i 0
%rep W_PRECALC_AHEAD
W_PRECALC i
%assign i i+1
%endrep
%xdefine F F1
%if (%1 == 1) ;; code loops through more than one block
%%_loop:
cmp BUFFER_PTR, K_BASE ;; we use K_BASE value as a signal of a last block,
jne %%_begin ;; it is set below by: cmovae BUFFER_PTR, K_BASE
jmp %%_end
align 32
%%_begin:
%endif
RR A,B,C,D,E,0
RR D,E,A,B,C,2
RR B,C,D,E,A,4
RR E,A,B,C,D,6
RR C,D,E,A,B,8
RR A,B,C,D,E,10
RR D,E,A,B,C,12
RR B,C,D,E,A,14
RR E,A,B,C,D,16
RR C,D,E,A,B,18
%xdefine F F2
RR A,B,C,D,E,20
RR D,E,A,B,C,22
RR B,C,D,E,A,24
RR E,A,B,C,D,26
RR C,D,E,A,B,28
RR A,B,C,D,E,30
RR D,E,A,B,C,32
RR B,C,D,E,A,34
RR E,A,B,C,D,36
RR C,D,E,A,B,38
%xdefine F F3
RR A,B,C,D,E,40
RR D,E,A,B,C,42
RR B,C,D,E,A,44
RR E,A,B,C,D,46
RR C,D,E,A,B,48
RR A,B,C,D,E,50
RR D,E,A,B,C,52
RR B,C,D,E,A,54
RR E,A,B,C,D,56
RR C,D,E,A,B,58
%xdefine F F4
%if (%1 == 1) ;; if code loops through more than one block
add BUFFER_PTR, 64 ;; move to next 64-byte block
cmp BUFFER_PTR, BUFFER_END ;; check if current block is the last one
cmovae BUFFER_PTR, K_BASE ;; smart way to signal the last iteration
%else
%xdefine W_NO_TAIL_PRECALC 1 ;; no software pipelining for single block interface
%endif
RR A,B,C,D,E,60
RR D,E,A,B,C,62
RR B,C,D,E,A,64
RR E,A,B,C,D,66
RR C,D,E,A,B,68
RR A,B,C,D,E,70
RR D,E,A,B,C,72
RR B,C,D,E,A,74
RR E,A,B,C,D,76
RR C,D,E,A,B,78
UPDATE_HASH [HASH_PTR ],A
UPDATE_HASH [HASH_PTR+ 4],B
UPDATE_HASH [HASH_PTR+ 8],C
UPDATE_HASH [HASH_PTR+12],D
UPDATE_HASH [HASH_PTR+16],E
%if (%1 == 1)
jmp %%_loop
align 32
%%_end:
%endif
%xdefine W_NO_TAIL_PRECALC 0
%xdefine F %error
%endmacro
%macro F1 3
mov T1,%2
xor T1,%3
and T1,%1
xor T1,%3
%endmacro
%macro F2 3
mov T1,%3
xor T1,%2
xor T1,%1
%endmacro
%macro F3 3
mov T1,%2
mov T2,%1
or T1,%1
and T2,%2
and T1,%3
or T1,T2
%endmacro
%define F4 F2
%macro UPDATE_HASH 2
add %2, %1
mov %1, %2
%endmacro
%macro W_PRECALC 1
%xdefine i (%1)
%if (i < 20)
%xdefine K_XMM 0
%elif (i < 40)
%xdefine K_XMM 16
%elif (i < 60)
%xdefine K_XMM 32
%else
%xdefine K_XMM 48
%endif
%if (i<16 || (i>=80 && i<(80 + W_PRECALC_AHEAD)))
%if (W_NO_TAIL_PRECALC == 0)
%xdefine i ((%1) % 80) ;; pre-compute for the next iteration
%if (i == 0)
W_PRECALC_RESET
%endif
W_PRECALC_00_15
%endif
%elif (i < 32)
W_PRECALC_16_31
%elif (i < 80) ;; rounds 32-79
W_PRECALC_32_79
%endif
%endmacro
%macro W_PRECALC_RESET 0
%xdefine W W0
%xdefine W_minus_04 W4
%xdefine W_minus_08 W8
%xdefine W_minus_12 W12
%xdefine W_minus_16 W16
%xdefine W_minus_20 W20
%xdefine W_minus_24 W24
%xdefine W_minus_28 W28
%xdefine W_minus_32 W
%endmacro
%macro W_PRECALC_ROTATE 0
%xdefine W_minus_32 W_minus_28
%xdefine W_minus_28 W_minus_24
%xdefine W_minus_24 W_minus_20
%xdefine W_minus_20 W_minus_16
%xdefine W_minus_16 W_minus_12
%xdefine W_minus_12 W_minus_08
%xdefine W_minus_08 W_minus_04
%xdefine W_minus_04 W
%xdefine W W_minus_32
%endmacro
%xdefine W_PRECALC_AHEAD 16
%xdefine W_NO_TAIL_PRECALC 0
%xdefine xmm_mov movdqa
%macro W_PRECALC_00_15 0
;; message scheduling pre-compute for rounds 0-15
%if ((i & 3) == 0) ;; blended SSE and ALU instruction scheduling, 1 vector iteration per 4 rounds
movdqu W_TMP, [BUFFER_PTR + (i * 4)]
%elif ((i & 3) == 1)
pshufb W_TMP, XMM_SHUFB_BSWAP
movdqa W, W_TMP
%elif ((i & 3) == 2)
paddd W_TMP, [K_BASE]
%elif ((i & 3) == 3)
movdqa [WK(i&~3)], W_TMP
W_PRECALC_ROTATE
%endif
%endmacro
%macro W_PRECALC_16_31 0
;; message scheduling pre-compute for rounds 16-31
;; calculating last 32 w[i] values in 8 XMM registers
;; pre-calculate K+w[i] values and store to mem, for later load by ALU add instruction
;;
;; "brute force" vectorization for rounds 16-31 only due to w[i]->w[i-3] dependency
;;
%if ((i & 3) == 0) ;; blended SSE and ALU instruction scheduling, 1 vector iteration per 4 rounds
movdqa W, W_minus_12
palignr W, W_minus_16, 8 ;; w[i-14]
movdqa W_TMP, W_minus_04
psrldq W_TMP, 4 ;; w[i-3]
pxor W, W_minus_08
%elif ((i & 3) == 1)
pxor W_TMP, W_minus_16
pxor W, W_TMP
movdqa W_TMP2, W
movdqa W_TMP, W
pslldq W_TMP2, 12
%elif ((i & 3) == 2)
psrld W, 31
pslld W_TMP, 1
por W_TMP, W
movdqa W, W_TMP2
psrld W_TMP2, 30
pslld W, 2
%elif ((i & 3) == 3)
pxor W_TMP, W
pxor W_TMP, W_TMP2
movdqa W, W_TMP
paddd W_TMP, [K_BASE + K_XMM]
movdqa [WK(i&~3)],W_TMP
W_PRECALC_ROTATE
%endif
%endmacro
%macro W_PRECALC_32_79 0
;; in SHA-1 specification: w[i] = (w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]) rol 1
;; instead we do equal: w[i] = (w[i-6] ^ w[i-16] ^ w[i-28] ^ w[i-32]) rol 2
;; allows more efficient vectorization since w[i]=>w[i-3] dependency is broken
;;
%if ((i & 3) == 0) ;; blended SSE and ALU instruction scheduling, 1 vector iteration per 4 rounds
movdqa W_TMP, W_minus_04
pxor W, W_minus_28 ;; W is W_minus_32 before xor
palignr W_TMP, W_minus_08, 8
%elif ((i & 3) == 1)
pxor W, W_minus_16
pxor W, W_TMP
movdqa W_TMP, W
%elif ((i & 3) == 2)
psrld W, 30
pslld W_TMP, 2
por W_TMP, W
%elif ((i & 3) == 3)
movdqa W, W_TMP
paddd W_TMP, [K_BASE + K_XMM]
movdqa [WK(i&~3)],W_TMP
W_PRECALC_ROTATE
%endif
%endmacro
%macro RR 6 ;; RR does two rounds of SHA-1 back to back with W pre-calculation
;; TEMP = A
;; A = F( i, B, C, D ) + E + ROTATE_LEFT( A, 5 ) + W[i] + K(i)
;; C = ROTATE_LEFT( B, 30 )
;; D = C
;; E = D
;; B = TEMP
W_PRECALC (%6 + W_PRECALC_AHEAD)
F %2, %3, %4 ;; F returns result in T1
add %5, [WK(%6)]
rol %2, 30
mov T2, %1
add %4, [WK(%6 + 1)]
rol T2, 5
add %5, T1
W_PRECALC (%6 + W_PRECALC_AHEAD + 1)
add T2, %5
mov %5, T2
rol T2, 5
add %4, T2
F %1, %2, %3 ;; F returns result in T1
add %4, T1
rol %1, 30
;; write: %1, %2
;; rotate: %1<=%4, %2<=%5, %3<=%1, %4<=%2, %5<=%3
%endmacro
;;----------------------
section .data align=128
%xdefine K1 0x5a827999
%xdefine K2 0x6ed9eba1
%xdefine K3 0x8f1bbcdc
%xdefine K4 0xca62c1d6
align 128
K_XMM_AR:
DD K1, K1, K1, K1
DD K2, K2, K2, K2
DD K3, K3, K3, K3
DD K4, K4, K4, K4
align 16
bswap_shufb_ctl:
DD 00010203h
DD 04050607h
DD 08090a0bh
DD 0c0d0e0fh
;; dispatch pointer, points to the init routine for the first invocation
sha1_update_intel_dispatched:
DQ sha1_update_intel_init_
;;----------------------
section .text align=4096
SHA1_VECTOR_ASM sha1_update_intel_ssse3_, multiblock
align 32
sha1_update_intel_init_: ;; we get here with the first time invocation
call sha1_update_intel_dispacth_init_
INTEL_SHA1_UPDATE_FUNCNAME: ;; we get here after init
jmp qword [sha1_update_intel_dispatched]
;; CPUID feature flag based dispatch
sha1_update_intel_dispacth_init_:
push rax
push rbx
push rcx
push rdx
push rsi
lea rsi, [INTEL_SHA1_UPDATE_DEFAULT_DISPATCH]
mov eax, 1
cpuid
test ecx, 0200h ;; SSSE3 support, CPUID.1.ECX[bit 9]
jz _done
lea rsi, [sha1_update_intel_ssse3_]
_done:
mov [sha1_update_intel_dispatched], rsi
pop rsi
pop rdx
pop rcx
pop rbx
pop rax
ret
;;----------------------
;; in the case a default SHA-1 update function implementation was not provided
;; and code was invoked on a non-SSSE3 supporting CPU, dispatch handles this
;; failure in a safest way - jumps to the stub function with UD2 instruction below
sha1_intel_non_ssse3_cpu_stub_:
ud2 ;; in the case no default SHA-1 was provided non-SSSE3 CPUs safely fail here
ret
; END
;----------------------

Some files were not shown because too many files have changed in this diff Show More