mirror of
https://github.com/pgsty/minio.git
synced 2026-08-14 18:53:17 +03:00
Compare commits
1246 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55e713db0a | |||
| 33322e6638 | |||
| a1792ca0d1 | |||
| ac8c43fe9c | |||
| 4d40ee00e9 | |||
| 06f59ad631 | |||
| 877e0cac03 | |||
| ef67c39910 | |||
| 508710f4d1 | |||
| 3aa3d9cf14 | |||
| c2fedb4c3f | |||
| 03dc65e12d | |||
| b8d62a8068 | |||
| dbc2368a7b | |||
| 54aed421b8 | |||
| d5e8dac1cf | |||
| 96ec8fcba1 | |||
| 0663eb69ed | |||
| 0594d37230 | |||
| 3cc30bcc18 | |||
| 99c1a642a4 | |||
| c60f54e5be | |||
| 483389f2e2 | |||
| c0f2f84285 | |||
| 069d118329 | |||
| a7b1834772 | |||
| 6415dec37a | |||
| 74253e1ddc | |||
| 01b3fb91e5 | |||
| 2dc917e87f | |||
| 0a284a1a10 | |||
| 5c8339e1e8 | |||
| fd37418da2 | |||
| bbfea29c2b | |||
| aa703dc903 | |||
| 8cd80fec8c | |||
| 780882efcf | |||
| c5636143c6 | |||
| ba6218b354 | |||
| 8e32de3ba9 | |||
| e37508fb8f | |||
| b46a717425 | |||
| 7926df0b80 | |||
| 557df666fd | |||
| f91b257f50 | |||
| 28a2d1eb3d | |||
| a0ae1489e5 | |||
| edfb310a59 | |||
| a2312028b9 | |||
| 78f1f69d57 | |||
| e1e33077e8 | |||
| b3e7de010d | |||
| f5b04865f4 | |||
| bf1c6edb76 | |||
| 2ac7fee017 | |||
| 128256e3ab | |||
| a66a7f3e97 | |||
| 20b79f8945 | |||
| 9a877734b2 | |||
| 409c391850 | |||
| 763ff085a6 | |||
| 5b9656374c | |||
| b32014549c | |||
| 9476d212bc | |||
| 000928d34e | |||
| 6829ae5b13 | |||
| f09756443d | |||
| 5512016885 | |||
| 21ecb941fe | |||
| 77e94087cf | |||
| 9ab1f25a47 | |||
| aaab7aefbe | |||
| 5b8599e52d | |||
| 74e0c9ab9b | |||
| dcce83b288 | |||
| f731e7ea36 | |||
| ec30bb89a4 | |||
| 7cd08594f6 | |||
| 2b4531f069 | |||
| 11544a62aa | |||
| 18550387d5 | |||
| efb03e19e6 | |||
| c27d0583d4 | |||
| 0de2b9a1b2 | |||
| 9dc29d7687 | |||
| 72871dbb9a | |||
| 4bda4e4e2b | |||
| 1971c54a50 | |||
| bb77b89da0 | |||
| b336e9a79f | |||
| a2ab21e91c | |||
| 603437e70f | |||
| db3a9a5990 | |||
| 24c7e73b4e | |||
| c053e57068 | |||
| 6d20ec3bea | |||
| c50627ee3e | |||
| b3cd893f93 | |||
| 22d2dbc4e6 | |||
| 2b5d9428b1 | |||
| d6446cb096 | |||
| c34bdc33fb | |||
| dd8547e51c | |||
| aec023f537 | |||
| e101eeeda9 | |||
| f29522269d | |||
| 3c470a6b8b | |||
| 6bc7d711b3 | |||
| 10d5dd3a67 | |||
| d9f1df01eb | |||
| cdeab19673 | |||
| 22ee678136 | |||
| 50a8f13e85 | |||
| 6dec60b6e6 | |||
| ac3a19138a | |||
| 21e8e071d7 | |||
| 57f84a8b4c | |||
| 8a672e70a7 | |||
| 22041bbcc4 | |||
| 5afb459113 | |||
| 91ebac0a00 | |||
| 5fcb1cfd31 | |||
| 3a90fb108c | |||
| 4eeb48f8e0 | |||
| 373d48c8a3 | |||
| 1472875670 | |||
| 74cfb207c1 | |||
| 6a096e7dc7 | |||
| 9788d85ea3 | |||
| 69c0e18685 | |||
| 3cac927348 | |||
| 9081346c40 | |||
| fcfadb0e51 | |||
| 2add57cfed | |||
| c5279ec630 | |||
| b73699fad8 | |||
| b8ebe54e53 | |||
| c3d70e0795 | |||
| 8c4561b8da | |||
| fd421ddd6f | |||
| 9947c01c8e | |||
| a00db4267c | |||
| 36385010f5 | |||
| fa6d082bfd | |||
| 9fab91852a | |||
| b733e6e83c | |||
| ce05bb69dc | |||
| 37aa5934a1 | |||
| 1647fc7edc | |||
| 7b92687397 | |||
| 419e5baf16 | |||
| dc48cd841a | |||
| b0e1776d6d | |||
| 7a7068ee47 | |||
| cbc0ef459b | |||
| a2aabfabd9 | |||
| 822cbd4b43 | |||
| 3c19a9308d | |||
| 32890342ce | |||
| ed2c2a285f | |||
| 18e23bafd9 | |||
| 8b8be2695f | |||
| 96fbf18201 | |||
| c8a57a8fa2 | |||
| b1c2dacab3 | |||
| 65939913b4 | |||
| 08b3a466e8 | |||
| 5aa7c38035 | |||
| 1df5e31706 | |||
| 9f7044aed0 | |||
| 41de53996b | |||
| 9878031cfd | |||
| e3fbcaeb72 | |||
| ca6dd8be5e | |||
| fba0924b1d | |||
| 703ed46d79 | |||
| f7ca6c63c2 | |||
| ad69b9907f | |||
| b9269151a4 | |||
| bfddbb8b40 | |||
| 13a2dc8485 | |||
| 1e51424e8a | |||
| 5b114b43f7 | |||
| 812f5a02d7 | |||
| 19f70dbfbf | |||
| 1c99fb106c | |||
| 71c32e9b48 | |||
| 380a59520b | |||
| 3995355150 | |||
| 8208bcb896 | |||
| d665e855de | |||
| 18b3655c99 | |||
| 6a8d8f34a5 | |||
| b1c1f02132 | |||
| ea93643e6a | |||
| e47e625f73 | |||
| b13fcaf666 | |||
| 9458485e43 | |||
| 0ce9e00ffa | |||
| c778c381b5 | |||
| 0d1fbef751 | |||
| b48bbe08b2 | |||
| cce90cb2b7 | |||
| 07b1281046 | |||
| 3515b99671 | |||
| 6a67c277eb | |||
| 1067dd3011 | |||
| 7cafdc0512 | |||
| 8a57b6bced | |||
| 6f0ed2a091 | |||
| 53abd25116 | |||
| 1ea7826c0e | |||
| 97f4cf48f8 | |||
| 0cde37be50 | |||
| 6aeca54ece | |||
| 124e28578c | |||
| 62c9e500de | |||
| 02cc18ff29 | |||
| ba4566e86d | |||
| 87cb0081ec | |||
| 4a6af93c83 | |||
| a2f0771fd3 | |||
| af564b8ba0 | |||
| adb8be069e | |||
| 7c8746732b | |||
| f506117edb | |||
| 1c5af7c31a | |||
| 3a0125fa1f | |||
| 328cb0a076 | |||
| c3c8441a1d | |||
| fa2a8d7209 | |||
| e3ea97c964 | |||
| 7219ae530e | |||
| 8f8f8854f0 | |||
| 4c6869cd9a | |||
| bc7c0d8624 | |||
| 11dfc817f3 | |||
| dde1a12819 | |||
| 065fd094d1 | |||
| d09351bb10 | |||
| 25d38e030b | |||
| 9ebd10d3f4 | |||
| 8a9b886011 | |||
| 21f0d6b549 | |||
| 3ba927edae | |||
| c4ca0a5a57 | |||
| 406ea4f281 | |||
| 64aa7feabd | |||
| 875f4076ec | |||
| 4643efe6be | |||
| b760137e1d | |||
| 5f56f441bf | |||
| 96a22bfcbb | |||
| 6c59b33fb1 | |||
| dfaf735073 | |||
| 0d2b7bf94d | |||
| 7fcfde7f07 | |||
| b1391d1991 | |||
| 49c8e16410 | |||
| 0e93681589 | |||
| eb55034dfe | |||
| c45bc32d98 | |||
| 6e860b6dc5 | |||
| b732a673dc | |||
| b6b6d6e8d8 | |||
| 23e4895dfc | |||
| 8666c55ca6 | |||
| a3f00c5d5e | |||
| 26c23b30f4 | |||
| a436fd513b | |||
| 3bc34ffd94 | |||
| 533cd8d6df | |||
| cb089dcb52 | |||
| e0329cfdbb | |||
| 239ccc9c40 | |||
| b762fbaf21 | |||
| 0285df5a02 | |||
| 45fb375c41 | |||
| 4a4950fe41 | |||
| 1664fd8bb1 | |||
| 21cdd2bf5d | |||
| 0153f96a20 | |||
| a7a7533190 | |||
| 311380f8cb | |||
| b0f0e53bba | |||
| 004f1e2f66 | |||
| 2fa561f22e | |||
| 81be718674 | |||
| 8162fd1e20 | |||
| 49a1e2f98e | |||
| 684c46369c | |||
| 715c9e3ca9 | |||
| 73edd5b8fd | |||
| 5e5bdf5432 | |||
| 48a3e9bc82 | |||
| f13cfcb83e | |||
| 9c0e8cd15b | |||
| ad2a70ba06 | |||
| 731e03fe5a | |||
| f9d029c8fa | |||
| 7057d00a28 | |||
| 114fab4c70 | |||
| c2edbfae55 | |||
| a92cb66468 | |||
| 535f97ba61 | |||
| 14ebd82dbd | |||
| aea7b08a47 | |||
| 47dcfcbdd4 | |||
| bf3901342c | |||
| e1731d9403 | |||
| b28bcad11b | |||
| a7c71e4c6b | |||
| 1a42693d68 | |||
| e7b60c4d65 | |||
| f95129894d | |||
| c32c71c836 | |||
| 14e1ace552 | |||
| a7fb3a3853 | |||
| 2da4bd5f1a | |||
| 7e76d66184 | |||
| 7764f4a8e3 | |||
| e1094dde08 | |||
| 4894c67196 | |||
| d004c45386 | |||
| 6624f970c0 | |||
| de684dc122 | |||
| 331bdc2245 | |||
| e12ab486a2 | |||
| 9eeee92d36 | |||
| 756d6aa729 | |||
| bddd53d6d2 | |||
| c0a5bdaed9 | |||
| a99cd825ab | |||
| 6426b74770 | |||
| 4f257bf1e6 | |||
| 73a056999c | |||
| 0120ff93bc | |||
| 49638fa533 | |||
| 76510dac8a | |||
| 7a3a7b19e5 | |||
| 24e86d0c59 | |||
| 9b5c2c386a | |||
| d118031ed6 | |||
| 341a89c00d | |||
| df29d25e6b | |||
| 3e196fa7b3 | |||
| 04c792476f | |||
| 005a4a275a | |||
| bdddf597f6 | |||
| bb6921bf9c | |||
| bb63375f1b | |||
| 4f89e5bba9 | |||
| 183428db03 | |||
| fc6d873758 | |||
| 5e2f8d7a42 | |||
| 9b9871cfbb | |||
| f80b6926d3 | |||
| 6dc55fe5ed | |||
| 2d1cda2061 | |||
| a566bcf613 | |||
| f6040dffaf | |||
| 9885a0a6af | |||
| f64d62b01d | |||
| 82075e8e3a | |||
| 5b7c83341b | |||
| 8522905d97 | |||
| 524ed7ccd0 | |||
| fb49aead9b | |||
| 85f5700e4e | |||
| 43b3c093ef | |||
| bd6842d917 | |||
| e8c98c3246 | |||
| dfd7cca0d2 | |||
| af3d99e35f | |||
| f6186965c3 | |||
| 90c2129f44 | |||
| 69e131ee69 | |||
| 28a01f0320 | |||
| 6d0bc5ab1e | |||
| 7af78af1f0 | |||
| 45a717a142 | |||
| cb1ec0a0d9 | |||
| abb1f22057 | |||
| 73efe436a5 | |||
| f41edb23e2 | |||
| 6335a48a53 | |||
| e20aab25ec | |||
| 66bea3942a | |||
| 08acd9c43d | |||
| ff5988f4e0 | |||
| 1bf23374a3 | |||
| 899b429094 | |||
| c47ff44f5e | |||
| 8af0773baf | |||
| 37cbd114de | |||
| 2dbb1cff4a | |||
| 6efcf9c982 | |||
| 0bc34952eb | |||
| f6b48ed02a | |||
| e37c4efc6e | |||
| 22f5bc643c | |||
| 15fd5ce2fa | |||
| 7f782983ca | |||
| 9d628346eb | |||
| bde533a9c7 | |||
| 2fcb75d86d | |||
| aae6846413 | |||
| 5317a0b755 | |||
| 73de721a63 | |||
| d2f5c3621f | |||
| 1818764840 | |||
| d3e5e607a7 | |||
| c1943ea3af | |||
| 2a82c15bf1 | |||
| 87b6fb37d6 | |||
| 21fbe88e1f | |||
| 1f8b9b4bd5 | |||
| fcbed41cc3 | |||
| 216069d0da | |||
| eefa047974 | |||
| d8dad5c9ea | |||
| bf8a68879c | |||
| f3248a4b37 | |||
| d315d012a4 | |||
| bd9bf3693f | |||
| 15daa2e74a | |||
| 74759b05a5 | |||
| 82ce78a17c | |||
| 9af6c6ceef | |||
| 021372cc4c | |||
| b94ab07c2f | |||
| 7605d07bb2 | |||
| ccc5801112 | |||
| 7c72b25ef0 | |||
| 02c2ec3027 | |||
| 65c31fab12 | |||
| b6b68be052 | |||
| 15911c85f6 | |||
| 5a1612fe32 | |||
| bbb7ae156c | |||
| f9b8d1c699 | |||
| 1443b5927a | |||
| 35ef35b5c1 | |||
| 6806537eb3 | |||
| 64de61d15d | |||
| 22b7c8cd8a | |||
| c4d0c49a5f | |||
| 142a5b0dcd | |||
| 25db1e4eca | |||
| 47a48b6832 | |||
| e98309eb75 | |||
| 87051872a7 | |||
| a2aed12dcd | |||
| d8e6e76e89 | |||
| 8c33fdf5f4 | |||
| ad4e511026 | |||
| 4a562d6732 | |||
| a9082e4f79 | |||
| 6278679ffd | |||
| 0474791cf8 | |||
| 69f819e199 | |||
| f32efd5429 | |||
| 22c247a988 | |||
| 35d71682f6 | |||
| 3d6b88a60e | |||
| 26a0803388 | |||
| ae95384dd8 | |||
| 0f0dcf0c5e | |||
| 6f2406b0b6 | |||
| bb24346e04 | |||
| be45ffd8a4 | |||
| f986b0c493 | |||
| c9e87f0548 | |||
| 43468f4d47 | |||
| 91987d6f7a | |||
| 6b7c98bd0f | |||
| b829e80ecb | |||
| 6e38d0f3ab | |||
| 38342b1df5 | |||
| dbd4c2425e | |||
| 49ce85ee3d | |||
| eba378e4a1 | |||
| 442c50ff00 | |||
| d1448adbda | |||
| 5a21b1f353 | |||
| 123a2fb3a8 | |||
| 2f9e2147f5 | |||
| 75c6fc4f02 | |||
| f9e07d6143 | |||
| 1436858347 | |||
| 8030e12ba5 | |||
| a485b923bf | |||
| 0649aca219 | |||
| b210ea79bc | |||
| 68f80b5fe7 | |||
| e95825a42e | |||
| 931712dc46 | |||
| 54e544e03e | |||
| f86b9abf32 | |||
| 9ef7eda33a | |||
| c9e26401fa | |||
| e53f49e9a9 | |||
| 14f6ac9222 | |||
| b8474295af | |||
| 817e85a3e0 | |||
| fb5ce3b87a | |||
| 6fe028b7c5 | |||
| 1cd7f1e38d | |||
| 043fd8b536 | |||
| 669acbb032 | |||
| 086d8f036e | |||
| 394690dcfb | |||
| 398bca92ff | |||
| fb328b1a64 | |||
| 563f667e30 | |||
| c839b64f6a | |||
| 6425fec366 | |||
| d5059840ef | |||
| 65cba212e8 | |||
| 7a69c9c75a | |||
| 4a425cbac1 | |||
| 615169c4ec | |||
| 5cd9dcb844 | |||
| 54c5c88fe6 | |||
| 443250d135 | |||
| 9b5829c16e | |||
| d749aaab69 | |||
| 62df731006 | |||
| d0a0eb9738 | |||
| 66156b8230 | |||
| 5677f73794 | |||
| ef54200db7 | |||
| 7875efbf61 | |||
| 3e128c116e | |||
| 55a3310446 | |||
| fc03be7891 | |||
| b1b00a5055 | |||
| 2920b0fc6d | |||
| a30a55f3b1 | |||
| ecfb18b26a | |||
| 41fa8fa2d2 | |||
| e94e6adf91 | |||
| 7d433f16c4 | |||
| b06d7bf834 | |||
| b784e458cb | |||
| 9d96b18df0 | |||
| ad2ab6eb3e | |||
| f037c9b286 | |||
| 85912985b6 | |||
| 876f51a708 | |||
| f7d29b4a53 | |||
| 06557fe8be | |||
| 2131046427 | |||
| aaf1abc993 | |||
| 413549bcf5 | |||
| 9a799065b3 | |||
| fd2959fa3a | |||
| 07927e032a | |||
| 15bec32bb4 | |||
| e2b7a08c10 | |||
| ef2fc0f99e | |||
| d063596430 | |||
| bd2dc6c670 | |||
| 684399433b | |||
| b62791617c | |||
| e07c2ab868 | |||
| 203755793c | |||
| 883c98e26f | |||
| f5a20a5d06 | |||
| ef7177ebbd | |||
| 3637aad36e | |||
| 77db9686fb | |||
| c326e5a34e | |||
| c23c982593 | |||
| a3d666356c | |||
| 3cdbc2f414 | |||
| b92cdea578 | |||
| 5e629a99af | |||
| 99c4ffa34f | |||
| a7f266c907 | |||
| 57acacd5a7 | |||
| 42fb3cd95e | |||
| 855ed642c3 | |||
| 629503ff73 | |||
| e3a070e3de | |||
| 5b364bca1f | |||
| c5c1426262 | |||
| be18d435a2 | |||
| 7eea6cdb12 | |||
| 824c55b3a4 | |||
| 76913a9fd5 | |||
| 2f44dac14f | |||
| 5569acd95c | |||
| 1d0211d395 | |||
| 06cd0a636e | |||
| 7f7b489a3d | |||
| bb6f4d7633 | |||
| 6e24dff26a | |||
| e372e4e592 | |||
| 9571b0825e | |||
| 90e2cc3d4c | |||
| 0c0820caef | |||
| 9112ca4e29 | |||
| 8203cb9990 | |||
| d5bce978a8 | |||
| ec84bad882 | |||
| b53376a3a4 | |||
| 0ec722bc54 | |||
| 4640b13c66 | |||
| 1704abaf6b | |||
| ab34f0065c | |||
| e8c0a50862 | |||
| b963f69f34 | |||
| 02d8f3cdc8 | |||
| 7ae69accc0 | |||
| 701b89f377 | |||
| 46d45a6923 | |||
| 7fad0c8b41 | |||
| 6e27264c6b | |||
| 5c83c9724f | |||
| d5aff735be | |||
| 98c26df53e | |||
| 2448a9e047 | |||
| b28d391a22 | |||
| c8b92f6067 | |||
| e7cac8acef | |||
| 31b5acc245 | |||
| 6105997299 | |||
| 8c874884fc | |||
| ebfe81e5fd | |||
| 0b7ca094e4 | |||
| 72802a5972 | |||
| b1f3935c5b | |||
| dbd53af369 | |||
| b09fe0e50e | |||
| fae9000304 | |||
| 8fd07bcd51 | |||
| 6addc7a35d | |||
| 477230c82e | |||
| d1737199ed | |||
| 61101d82d9 | |||
| cebb948da2 | |||
| c61c4b71b2 | |||
| 84f31ed45d | |||
| 8a81e317d6 | |||
| 224d9a752f | |||
| 0db34e4b85 | |||
| 8a9b9832fd | |||
| f66625be67 | |||
| 6825bd7e75 | |||
| 18515a4e3b | |||
| 839b9c9271 | |||
| dd9ed85e22 | |||
| e96c88e914 | |||
| 6c1410f7f5 | |||
| f92450d8b3 | |||
| c133979b8e | |||
| a9269cee29 | |||
| cf42ede92c | |||
| 958a480e53 | |||
| 62151a751d | |||
| f1ab9df2ee | |||
| a42650c065 | |||
| bdad3730f7 | |||
| a5835cecbf | |||
| b19620b324 | |||
| cd6dec49c0 | |||
| d350654aee | |||
| 6877578bbc | |||
| 10693fddfa | |||
| f3682b6149 | |||
| 056ca0c68e | |||
| 25f7a8e406 | |||
| 1f1c267b6c | |||
| eab1dc927b | |||
| fc94ea1ced | |||
| 67d2cf8f30 | |||
| 2c85b84cbc | |||
| 09a25ea7b7 | |||
| 260a63ca73 | |||
| d3f70ea340 | |||
| ceebd35ef7 | |||
| 91b6fe1af3 | |||
| 9803f68522 | |||
| 47b7469a60 | |||
| 4c204707fd | |||
| 8fd6be0827 | |||
| c06e0bfef9 | |||
| 8625a9dbb3 | |||
| 2b71b659e0 | |||
| 0320ac43cb | |||
| 111c7d4026 | |||
| 62c3df0ca3 | |||
| ae011663e8 | |||
| 12591cd241 | |||
| 0499e1c4b0 | |||
| 3158f2d12e | |||
| 51f7f9aaa3 | |||
| 6e359c586e | |||
| 699a24f7e5 | |||
| 5fa3665074 | |||
| 3b7781835e | |||
| 5fe1b46bfd | |||
| f65cce4317 | |||
| 27d0d22e5d | |||
| 407c9ddcbf | |||
| d90d0c8931 | |||
| 216a471bbb | |||
| a7b7860e0e | |||
| d703daa480 | |||
| dc8fdcb9c9 | |||
| 7a6c4e438e | |||
| c468b4e2a8 | |||
| b04956a676 | |||
| 518f6e4d39 | |||
| 483b226cc1 | |||
| 13151cbb2b | |||
| 8e02660a0d | |||
| 16feef2a2c | |||
| 66ff17e452 | |||
| 4c5edacae2 | |||
| 8b4d0255b7 | |||
| 58c129f94a | |||
| 74040b457b | |||
| c259a8ea38 | |||
| 2d51e42305 | |||
| 5e3bfd2148 | |||
| 8b0ab6ead6 | |||
| b1b0aadabf | |||
| ac7d9c449a | |||
| 1346561b9d | |||
| 4bc52897b2 | |||
| 035791669e | |||
| 6017b63a06 | |||
| 11d04279c8 | |||
| 0448728228 | |||
| 12047702f5 | |||
| fb1492f531 | |||
| d14ead7bec | |||
| 05444a0f6a | |||
| b3c54ec81e | |||
| 6c11dbffd5 | |||
| 3b5dbf9046 | |||
| 09c733677a | |||
| 8d6558b236 | |||
| 67f4ba154a | |||
| 440ad20c1d | |||
| 31fba6f434 | |||
| 280442e533 | |||
| 850a945a18 | |||
| 46f9049fb4 | |||
| 58266c9e2c | |||
| d1e775313d | |||
| a65df1e67b | |||
| e700be8cd6 | |||
| 3fdd574f54 | |||
| e0f4dd6027 | |||
| de02eca467 | |||
| 50dbd2cacc | |||
| c2f9cc5824 | |||
| c7f7e67a10 | |||
| 628042e65e | |||
| cde7eeb660 | |||
| 6305b206e1 | |||
| d85da9236e | |||
| 9800760cb3 | |||
| 5c087bdcad | |||
| a547bf517d | |||
| b984bf8d1a | |||
| 18f9cccfa7 | |||
| fb6ab1cca2 | |||
| 56c57e2c53 | |||
| a6057c35cc | |||
| 901887e6bf | |||
| ee54643004 | |||
| 0a17acdb34 | |||
| 72e5212842 | |||
| 714283fae2 | |||
| 3423028713 | |||
| 9d062b37d7 | |||
| 4636d3a9c3 | |||
| c95ede35c1 | |||
| 7415e1aa56 | |||
| f350953a19 | |||
| 958bba5b42 | |||
| 0f2b95b497 | |||
| d07089ceac | |||
| 47dfa62384 | |||
| 3a3265cf88 | |||
| 0ff931dc76 | |||
| 4d708cebe9 | |||
| 4d7c8e3bb8 | |||
| 8cde38404d | |||
| fe7bf6cbbc | |||
| 8b4eb2304b | |||
| ae029191a3 | |||
| bfedea9bad | |||
| 7777d3b43a | |||
| 9ed4fc9687 | |||
| b49b39e99d | |||
| cd3a2de5a3 | |||
| 6e8960ccdd | |||
| e05f3d5d84 | |||
| 94c6cb1323 | |||
| 3f81cd1b22 | |||
| 8da0f4c5bb | |||
| 9acf1024e4 | |||
| b21d3f9b82 | |||
| 2bbf380262 | |||
| f678bcf7ba | |||
| a0f06eac2a | |||
| 83fe1a2732 | |||
| 5c98223c89 | |||
| 663a0b7783 | |||
| 6efe4d1df6 | |||
| fb17f97cf3 | |||
| 6b65ba1551 | |||
| 9202c6e26a | |||
| 59a5456091 | |||
| 8bfe972bab | |||
| fd6622458b | |||
| 8a08861dd9 | |||
| 82dcfd4e10 | |||
| b66d7dc708 | |||
| eebdd2b31d | |||
| b94733ab31 | |||
| 7f2c90a0ed | |||
| 84bb7d05a9 | |||
| 98a84d88e2 | |||
| e470268c7c | |||
| 3a6cd4f73d | |||
| 6ea150fd68 | |||
| a7188bc9d0 | |||
| e1e9ddd4a4 | |||
| a1dd08f2e6 | |||
| d136ac0596 | |||
| c33a237067 | |||
| eb7d3da994 | |||
| 37134e42d4 | |||
| 626a4efaad | |||
| 0c1f8b4e0f | |||
| 857674c3a0 | |||
| 15a75bd79b | |||
| 74887c7372 | |||
| 31188e9327 | |||
| ee6d96eb46 | |||
| 11fe2fd79a | |||
| c2863cc6ef | |||
| d6d01067a0 | |||
| 1d3b18c3f4 | |||
| a15b6f21b8 | |||
| 689179bf18 | |||
| bf749eec61 | |||
| d0f4cc89a5 | |||
| d65debb6bc | |||
| 72daccd468 | |||
| b363400587 | |||
| 6b41f941b6 | |||
| b9f5f9ba3f | |||
| c8ffa59d28 | |||
| 1141187bf2 | |||
| 52aeebebea | |||
| e101384aa4 | |||
| 71f02adfca | |||
| 9de26531e4 | |||
| fadc46b906 | |||
| b1d98febfd | |||
| 1828fb212a | |||
| c97f50e274 | |||
| be92046dfd | |||
| 990fc415f7 | |||
| d8daabae9b | |||
| 84fe4fd156 | |||
| 747d475e76 | |||
| 095b518802 | |||
| 0319ae756a | |||
| 11c7ecb5cf | |||
| 422c396d73 | |||
| ffd57fde90 | |||
| a451d1cb8d | |||
| 14cf8f1b22 | |||
| 5996c8c4d5 | |||
| 21885f9457 | |||
| 6ac48aff46 | |||
| 85ff76e7b0 | |||
| e47a31f9fc | |||
| 517fcd423d | |||
| b780359598 | |||
| aa8b9572b9 | |||
| 8ca14e6267 | |||
| 876e1a91b2 | |||
| 0b7989aa4b | |||
| 2278fc8f47 | |||
| a91f353621 | |||
| cdb1b48ad9 | |||
| a24037bfec | |||
| 2d0f30f062 | |||
| cea2ca8c8e | |||
| f713436dd0 | |||
| b923a62425 | |||
| 67fce4a5b3 | |||
| eaa65b7ade | |||
| 820d94447c | |||
| ed20134a7b | |||
| d19cbc81b5 | |||
| 1fd7946dce | |||
| 027ff0f3a8 | |||
| 8fa80874a6 | |||
| 430669cfad | |||
| 54b561898f | |||
| 65c104a589 | |||
| 0a0416b6ea | |||
| 441babdc41 | |||
| 1bf1fafc86 | |||
| 50d58e9b2d | |||
| e64b9f6751 | |||
| d67a846ec4 | |||
| ca2a1c3f60 | |||
| 93fbb228bf | |||
| 3683673fb0 | |||
| f37a5b6dae | |||
| 31b0decd46 | |||
| eb561e1c05 | |||
| 54c9ecff5b | |||
| edcd72585d | |||
| 1a17fc17bb | |||
| ddad231921 | |||
| e73894fa50 | |||
| 03b94f907f | |||
| 3fa7218c44 | |||
| 0f591d245d | |||
| 1b02e046c2 | |||
| d08e3cc895 | |||
| d98116559b | |||
| 71c95ad0d0 | |||
| 5c1a4ba5f9 | |||
| 698862ec5d | |||
| b4ef5ff294 | |||
| 3db658e51e | |||
| 5a9f7516d6 | |||
| 3039fd4519 | |||
| 095fc0561d | |||
| beb1924437 | |||
| c8e1154f1e | |||
| b204c2dbec | |||
| b22b39de96 | |||
| c242e6c391 | |||
| e05205756f | |||
| d03b244fcd | |||
| 5ef679d8f1 | |||
| d33c527e39 | |||
| 7bc95c47a3 | |||
| 475a88b555 | |||
| 9815dac48f | |||
| 1ece3d1dfe | |||
| 2146ed4033 | |||
| 52b88b52f0 | |||
| ebd4388cca | |||
| 57fd02ee57 | |||
| 0333412148 | |||
| 1c85652cff | |||
| e0086c1be7 | |||
| b29e159604 | |||
| 7883e55da2 | |||
| b197623ed2 | |||
| a15a2556c3 | |||
| 14d29b77ae | |||
| a2514ffeed | |||
| f1bbb7fef5 | |||
| 72394a8319 | |||
| 1cd8e1d8b6 | |||
| 62cd918061 | |||
| 6a04067514 | |||
| 49b3908635 | |||
| 75faef888e | |||
| b67d97b1ba | |||
| b8943fdf19 | |||
| f93183f66e | |||
| 2937711390 | |||
| aa56c6d51d | |||
| 27417459fb | |||
| 5b8fe2e89a | |||
| acc9c033ed | |||
| 8528b265a9 | |||
| 44250f1a52 | |||
| f7560670d9 | |||
| 3891885800 | |||
| b882310e2b | |||
| de0b43de32 | |||
| 48152a56ac | |||
| 29dd7f1d68 | |||
| 6423e4c767 | |||
| 1dd8f0e8f3 | |||
| 2fa35def2c | |||
| 34167c51d5 | |||
| a5f8af4efb | |||
| 5a218f38a1 | |||
| e57e946206 | |||
| b4f71362e9 | |||
| ed37b7a9d5 | |||
| 6511021fbe | |||
| 6197ba851b | |||
| 3ae1f9d852 | |||
| 0db1930f48 | |||
| 89db3fdb5d | |||
| 80fc3a8a52 | |||
| 988a2e8fed | |||
| 2433698372 | |||
| 5d7e8f79ed | |||
| bad229e16e | |||
| d37e514733 | |||
| c73ea27ed7 | |||
| 0159b56717 | |||
| 9e6cc847f8 | |||
| 709eb283d9 | |||
| 76dde82b41 | |||
| 939c0100a6 | |||
| 2d60bf8c50 | |||
| 37e20f6ef2 | |||
| 76905b7a67 | |||
| a469e6768d | |||
| 2fc182d8e6 | |||
| a2cbeaa9e6 | |||
| 444ff20bc5 | |||
| 20ef5e7a6a | |||
| c233c8e329 | |||
| e06127566d | |||
| dfe73629a3 | |||
| b03dd1af17 | |||
| 4bc367c490 | |||
| 3eb2d086b2 | |||
| 70986b6e6e | |||
| 8edc2faaa9 | |||
| ebe395788b | |||
| 12fd6678ee | |||
| 90d35b70b4 | |||
| 9f71369b67 | |||
| 04ae9058ed | |||
| a30cfdd88f | |||
| 1bae32dc96 | |||
| 932d2c3c62 | |||
| 52f4124678 | |||
| 8d8d07ac5c | |||
| 44735be38e | |||
| 1ef1b2ba50 | |||
| 6fdbd778d5 | |||
| 419f351df3 | |||
| 180d6b30ca | |||
| 3fd9059b4e | |||
| a713aee3d5 | |||
| a9f5b58a01 | |||
| d882ba2cb4 | |||
| 90e37a8745 | |||
| 6086f45d25 | |||
| d6351879f3 | |||
| 5655272f5a | |||
| 9b35c72349 | |||
| 98cffbce03 | |||
| 1cd875de1e | |||
| 5a8df7efb3 | |||
| c84e2939e4 | |||
| 641ab24aec | |||
| 71133105d7 | |||
| 625677b189 | |||
| 76943ac05e | |||
| 87cbd41265 | |||
| be92cf5959 | |||
| cc1d8f0057 | |||
| 1f1dcdce65 | |||
| 98a67a3776 | |||
| 9b1e70e4f9 | |||
| 09d4f8cd0f | |||
| 53cbc020b9 | |||
| 63fc6ba2cd | |||
| ce53d7f6c2 | |||
| fe8eed963e | |||
| 97eb7dbf5f | |||
| 59f877fc64 | |||
| f96fe9773c | |||
| 04948b4d55 | |||
| 98ba622679 | |||
| 08103870a5 | |||
| 993e586855 | |||
| 58ec835af0 | |||
| 6aea950d74 | |||
| 7198be5be9 | |||
| 3661aaf8a1 | |||
| a22b4adf4c | |||
| b7bb122be8 | |||
| 8441a3bf5f | |||
| 853c4de75a | |||
| 3597af789e | |||
| 4c9cac0b47 | |||
| 1a0b68498b | |||
| 5246e3be84 | |||
| 8a07000e58 | |||
| 3bb82ef60d | |||
| c8a221a9a7 | |||
| 91f45c4aa6 | |||
| 7c5e4da90c | |||
| d6bc141bd1 | |||
| 7ac64ad24a | |||
| 14e52f29b0 | |||
| 344ae9f84e | |||
| f7db12c7ef | |||
| 962d1f1a71 | |||
| 6d76db9d6c | |||
| 00857f8f59 | |||
| 66239f30ce | |||
| bf89f79694 | |||
| ce299b47ea | |||
| 6dc7109a9f | |||
| bdcb485740 | |||
| e32b948a49 | |||
| 4fe9cbb973 | |||
| 5b242f1d11 | |||
| 34d28dd79f | |||
| 6eef9b4a23 | |||
| 5f1999cc71 | |||
| 40a2c6b882 | |||
| 7ba281728f | |||
| 7b7356f04c | |||
| bbc312fce6 | |||
| 1b0dfb0f58 | |||
| 7260241511 | |||
| 3b1a9b9fdf | |||
| 52769e1e71 | |||
| 72afc2727a | |||
| 808739867c | |||
| 752e18e795 | |||
| 76d822bf1e | |||
| ddeca9f12a | |||
| 19d0340ddf | |||
| 21251d8c22 | |||
| 1f3db03bf0 | |||
| 944c62daf4 | |||
| 9547b7d0e9 | |||
| 76c4ea7682 | |||
| 808ecfe0f2 | |||
| 2894dd4d1a | |||
| 797fa7f97b | |||
| fd8750e959 | |||
| 7be65f66b8 | |||
| 4f5d38a4b1 | |||
| 7e73fc2870 | |||
| d2c9a9e395 | |||
| 0d49b365ff | |||
| 7721595aa9 | |||
| fd6f6fc8df | |||
| 4fb47cd568 | |||
| ecc932d5dd | |||
| b57fbff7c1 | |||
| 4892a766a8 | |||
| 0303cd8625 | |||
| d765b89a63 | |||
| 6e4acf0504 | |||
| 71954faa3a | |||
| 6d22e74d11 | |||
| dc92bb4646 | |||
| 0f0e154315 | |||
| 136d41775f | |||
| ec77d28e62 | |||
| 86420a1f46 | |||
| 7dd8b6c8ed | |||
| 8afa6fefd8 | |||
| 533c9d4fe3 | |||
| a35ef155fc | |||
| 8dd3c41b2a | |||
| 4523da6543 | |||
| ce8456a1a9 | |||
| 1673778633 | |||
| 9ce1884732 | |||
| 23b329b9df | |||
| 0c34e51a75 | |||
| 1633b30979 | |||
| 630dabf4b9 | |||
| fc6c794972 | |||
| 2e33b99c6b | |||
| 3b7292b637 | |||
| e4f469ae7a | |||
| c921dc75c7 | |||
| 86d543d0f6 | |||
| e4e90b53c1 | |||
| 58d776daa0 | |||
| f6b2e89109 | |||
| ac85c2af76 | |||
| 5aba2aedb3 | |||
| bd77f1df4c | |||
| a8332efa94 | |||
| 3dbef72dc7 | |||
| 2d16e74f38 | |||
| de5070446d | |||
| 374abd1e7d | |||
| 0506d9e83d | |||
| bd3dfad8b9 | |||
| 9fff315555 | |||
| 07b6dce1a5 | |||
| 18fb86b7be | |||
| 58a8275e84 | |||
| 196fab6834 | |||
| 85fc7cea97 | |||
| 328d660106 | |||
| c68910005b | |||
| c79bcc8838 | |||
| 0fe58dbb34 | |||
| 6cb2f56395 | |||
| 59e33b3b21 | |||
| 0e3c92c027 | |||
| db7a9b2c37 | |||
| 44097faec1 | |||
| ff5fca76ab | |||
| bf3da5081f | |||
| 5532982857 | |||
| 783dd875f7 | |||
| 97112c69be | |||
| b0b573052a | |||
| 2939000342 | |||
| 41e1654f9a | |||
| e3cb0278ce | |||
| 0c81f1bdb3 | |||
| 6220875803 | |||
| afd4279cd8 | |||
| 0c8dd8046a | |||
| 3c4ef4338f | |||
| 64cf887b28 | |||
| 927a879052 | |||
| dfe0c96b87 | |||
| e856e10ac2 | |||
| 6d6a731d6d | |||
| 928feb0889 | |||
| b3febe2d24 | |||
| b6b26dba87 | |||
| 5c034e26bd | |||
| cef0fb1434 | |||
| 158d0e26a2 | |||
| 78385bfbeb | |||
| 2a13cc28f2 | |||
| 4d761fda81 | |||
| 4bdf41a6c7 | |||
| 3c605c93fe | |||
| 121f18a443 | |||
| 538aeef27a | |||
| be0d2537b7 | |||
| 3307aa1260 | |||
| 57cfdfd8fb | |||
| dc6733dacc |
+9
-2
@@ -1,10 +1,17 @@
|
|||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
docs
|
|
||||||
default.etcd
|
default.etcd
|
||||||
*.gz
|
*.gz
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.bzip2
|
*.bzip2
|
||||||
*.zip
|
*.zip
|
||||||
browser/node_modules
|
browser/node_modules
|
||||||
node_modules
|
node_modules
|
||||||
|
docs/debugging/s3-verify/s3-verify
|
||||||
|
docs/debugging/xl-meta/xl-meta
|
||||||
|
docs/debugging/s3-check-md5/s3-check-md5
|
||||||
|
docs/debugging/hash-set/hash-set
|
||||||
|
docs/debugging/healing-bin/healing-bin
|
||||||
|
docs/debugging/inspect/inspect
|
||||||
|
docs/debugging/pprofgoparser/pprofgoparser
|
||||||
|
docs/debugging/reorder-disks/reorder-disks
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
## Community Contribution License
|
||||||
|
All community contributions in this pull request are licensed to the project maintainers
|
||||||
|
under the terms of the [Apache 2 license] (https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
By creating this pull request I represent that I have the right to license the
|
||||||
|
contributions to the project maintainers under the Apache 2 license.
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
# Config file for markdownlint-cli
|
|
||||||
MD033:
|
|
||||||
allowed_elements:
|
|
||||||
- details
|
|
||||||
- summary
|
|
||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -20,11 +21,11 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x, 1.19.x]
|
go-version: [1.21.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
|
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||||
- uses: actions/setup-go@bfdd3570ce990073878bf10f6b2d79082de49492 # v2
|
- uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -20,20 +21,28 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.5b7]
|
go-version: [1.21.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: Setup dockerfile for build test
|
- name: Setup dockerfile for build test
|
||||||
run: |
|
run: |
|
||||||
echo "FROM us-docker.pkg.dev/google.com/api-project-999119582588/go-boringcrypto/golang:${{ matrix.go-version }}" > Dockerfile.fips.test
|
GO_VERSION=$(go version | cut -d ' ' -f 3 | sed 's/go//')
|
||||||
echo "COPY . /minio" >> Dockerfile.fips.test
|
echo Detected go version $GO_VERSION
|
||||||
echo "WORKDIR /minio" >> Dockerfile.fips.test
|
cat > Dockerfile.fips.test <<EOF
|
||||||
echo "RUN make" >> Dockerfile.fips.test
|
FROM golang:${GO_VERSION}
|
||||||
|
COPY . /minio
|
||||||
|
WORKDIR /minio
|
||||||
|
ENV GOEXPERIMENT=boringcrypto
|
||||||
|
RUN make
|
||||||
|
EOF
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v3
|
||||||
@@ -48,4 +57,4 @@ jobs:
|
|||||||
- name: Test binary
|
- name: Test binary
|
||||||
run: |
|
run: |
|
||||||
docker run --rm minio/fips-test:latest ./minio --version
|
docker run --rm minio/fips-test:latest ./minio --version
|
||||||
docker run --rm -i minio/fips-test:latest /bin/bash -c 'go tool nm ./minio' | grep -q FIPS
|
docker run --rm -i minio/fips-test:latest /bin/bash -c 'go tool nm ./minio | grep FIPS | grep -q FIPS'
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -20,22 +21,14 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x, 1.19.x]
|
go-version: [1.21.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -20,38 +21,21 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x, 1.19.x]
|
go-version: [1.21.x]
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
if: matrix.os == 'ubuntu-latest'
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- uses: actions/cache@v2
|
|
||||||
if: matrix.os == 'windows-latest'
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
%LocalAppData%\go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
run: |
|
run: |
|
||||||
|
netsh int ipv4 set dynamicport tcp start=60000 num=61000
|
||||||
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
|
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
|
||||||
go test -v --timeout 50m ./...
|
go test -v --timeout 50m ./...
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
@@ -63,9 +47,6 @@ jobs:
|
|||||||
sudo apt install jq -y
|
sudo apt install jq -y
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
nancy_version=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/sonatype-nexus-community/nancy/releases/latest | sed "s/https:\/\/github.com\/sonatype-nexus-community\/nancy\/releases\/tag\///")
|
|
||||||
curl -L -o nancy https://github.com/sonatype-nexus-community/nancy/releases/download/${nancy_version}/nancy-${nancy_version}-linux-amd64 && chmod +x nancy
|
|
||||||
go list -deps -json ./... | jq -s 'unique_by(.Module.Path)|.[]|select(has("Module"))|.Module' | ./nancy sleuth
|
|
||||||
make
|
make
|
||||||
make test
|
make test
|
||||||
make test-race
|
make test-race
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -20,22 +21,14 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x, 1.19.x]
|
go-version: [1.21.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Build on ${{ matrix.os }}
|
- name: Build on ${{ matrix.os }}
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
name: Helm Chart linting
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
|
# updated.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install Helm
|
||||||
|
uses: azure/setup-helm@v3
|
||||||
|
|
||||||
|
- name: Run helm lint
|
||||||
|
run: |
|
||||||
|
cd helm/minio
|
||||||
|
helm lint .
|
||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -61,7 +62,7 @@ jobs:
|
|||||||
# are turned off - i.e. if ldap="", then ldap server is not enabled for
|
# are turned off - i.e. if ldap="", then ldap server is not enabled for
|
||||||
# the tests.
|
# the tests.
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x]
|
go-version: [1.21.x]
|
||||||
ldap: ["", "localhost:389"]
|
ldap: ["", "localhost:389"]
|
||||||
etcd: ["", "http://localhost:2379"]
|
etcd: ["", "http://localhost:2379"]
|
||||||
openid: ["", "http://127.0.0.1:5556/dex"]
|
openid: ["", "http://127.0.0.1:5556/dex"]
|
||||||
@@ -75,24 +76,16 @@ jobs:
|
|||||||
openid: "http://127.0.0.1:5556/dex"
|
openid: "http://127.0.0.1:5556/dex"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Test LDAP/OpenID/Etcd combo
|
- name: Test LDAP/OpenID/Etcd combo
|
||||||
env:
|
env:
|
||||||
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
_MINIO_LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||||
ETCD_SERVER: ${{ matrix.etcd }}
|
_MINIO_ETCD_TEST_SERVER: ${{ matrix.etcd }}
|
||||||
OPENID_TEST_SERVER: ${{ matrix.openid }}
|
_MINIO_OPENID_TEST_SERVER: ${{ matrix.openid }}
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
@@ -100,20 +93,20 @@ jobs:
|
|||||||
- name: Test with multiple OpenID providers
|
- name: Test with multiple OpenID providers
|
||||||
if: matrix.openid == 'http://127.0.0.1:5556/dex'
|
if: matrix.openid == 'http://127.0.0.1:5556/dex'
|
||||||
env:
|
env:
|
||||||
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
_MINIO_LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||||
ETCD_SERVER: ${{ matrix.etcd }}
|
_MINIO_ETCD_TEST_SERVER: ${{ matrix.etcd }}
|
||||||
OPENID_TEST_SERVER: ${{ matrix.openid }}
|
_MINIO_OPENID_TEST_SERVER: ${{ matrix.openid }}
|
||||||
OPENID_TEST_SERVER_2: "http://127.0.0.1:5557/dex"
|
_MINIO_OPENID_TEST_SERVER_2: "http://127.0.0.1:5557/dex"
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
make test-iam
|
make test-iam
|
||||||
- name: Test with Access Management Plugin enabled
|
- name: Test with Access Management Plugin enabled
|
||||||
env:
|
env:
|
||||||
LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
_MINIO_LDAP_TEST_SERVER: ${{ matrix.ldap }}
|
||||||
ETCD_SERVER: ${{ matrix.etcd }}
|
_MINIO_ETCD_TEST_SERVER: ${{ matrix.etcd }}
|
||||||
OPENID_TEST_SERVER: ${{ matrix.openid }}
|
_MINIO_OPENID_TEST_SERVER: ${{ matrix.openid }}
|
||||||
POLICY_PLUGIN_ENDPOINT: "http://127.0.0.1:8080"
|
_MINIO_POLICY_PLUGIN_TEST_ENDPOINT: "http://127.0.0.1:8080"
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
name: Markdown Linter
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
|
||||||
# updated.
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint:
|
|
||||||
name: Lint all docs
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Lint all docs
|
|
||||||
run: |
|
|
||||||
npm install -g markdownlint-cli
|
|
||||||
markdownlint --fix '**/*.md' \
|
|
||||||
--config /home/runner/work/minio/minio/.github/markdown-lint-cfg.yaml \
|
|
||||||
--disable MD013 MD040 MD051
|
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
name: Mint Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
|
# updated.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mint-test:
|
||||||
|
runs-on: mint
|
||||||
|
timeout-minutes: 120
|
||||||
|
steps:
|
||||||
|
- name: cleanup #https://github.com/actions/checkout/issues/273
|
||||||
|
run: |
|
||||||
|
sudo -S rm -rf ${GITHUB_WORKSPACE}
|
||||||
|
mkdir ${GITHUB_WORKSPACE}
|
||||||
|
- name: checkout-step
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: setup-go-step
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.21.x
|
||||||
|
|
||||||
|
- name: github sha short
|
||||||
|
id: vars
|
||||||
|
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: build-minio
|
||||||
|
run: |
|
||||||
|
TAG="quay.io/minio/minio:${{ steps.vars.outputs.sha_short }}" make docker
|
||||||
|
|
||||||
|
- name: multipart uploads test
|
||||||
|
run: |
|
||||||
|
${GITHUB_WORKSPACE}/.github/workflows/multipart/migrate.sh "${{ steps.vars.outputs.sha_short }}"
|
||||||
|
|
||||||
|
- name: compress and encrypt
|
||||||
|
run: |
|
||||||
|
${GITHUB_WORKSPACE}/.github/workflows/run-mint.sh "compress-encrypt" "minio" "minio123" "${{ steps.vars.outputs.sha_short }}"
|
||||||
|
|
||||||
|
- name: multiple pools
|
||||||
|
run: |
|
||||||
|
${GITHUB_WORKSPACE}/.github/workflows/run-mint.sh "pools" "minio" "minio123" "${{ steps.vars.outputs.sha_short }}"
|
||||||
|
|
||||||
|
- name: standalone erasure
|
||||||
|
run: |
|
||||||
|
${GITHUB_WORKSPACE}/.github/workflows/run-mint.sh "erasure" "minio" "minio123" "${{ steps.vars.outputs.sha_short }}"
|
||||||
|
|
||||||
|
- name: The job must cleanup
|
||||||
|
if: ${{ always() }}
|
||||||
|
run: |
|
||||||
|
export JOB_NAME=${{ steps.vars.outputs.sha_short }}
|
||||||
|
for mode in $(echo compress-encrypt pools erasure); do
|
||||||
|
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/mint/minio-${mode}.yaml down || true
|
||||||
|
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/mint/minio-${mode}.yaml rm || true
|
||||||
|
done
|
||||||
|
|
||||||
|
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/multipart/docker-compose-site1.yaml rm -s -f || true
|
||||||
|
docker-compose -f ${GITHUB_WORKSPACE}/.github/workflows/multipart/docker-compose-site2.yaml rm -s -f || true
|
||||||
|
for volume in $(docker volume ls -q | grep minio); do
|
||||||
|
docker volume rm ${volume} || true
|
||||||
|
done
|
||||||
|
|
||||||
|
docker rmi -f quay.io/minio/minio:${{ steps.vars.outputs.sha_short }}
|
||||||
|
docker system prune -f || true
|
||||||
|
docker volume prune -f || true
|
||||||
|
docker volume rm $(docker volume ls -q -f dangling=true) || true
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
# Settings and configurations that are common for all containers
|
||||||
|
x-minio-common: &minio-common
|
||||||
|
image: quay.io/minio/minio:${JOB_NAME}
|
||||||
|
command: server --console-address ":9001" http://minio{1...4}/cdata{1...2}
|
||||||
|
expose:
|
||||||
|
- "9000"
|
||||||
|
- "9001"
|
||||||
|
environment:
|
||||||
|
MINIO_CI_CD: "on"
|
||||||
|
MINIO_ROOT_USER: "minio"
|
||||||
|
MINIO_ROOT_PASSWORD: "minio123"
|
||||||
|
MINIO_COMPRESSION_ENABLE: "on"
|
||||||
|
MINIO_COMPRESSION_MIME_TYPES: "*"
|
||||||
|
MINIO_COMPRESSION_ALLOW_ENCRYPTION: "on"
|
||||||
|
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
# starts 4 docker containers running minio server instances.
|
||||||
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
# it through port 9000.
|
||||||
|
services:
|
||||||
|
minio1:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio1
|
||||||
|
volumes:
|
||||||
|
- cdata1-1:/cdata1
|
||||||
|
- cdata1-2:/cdata2
|
||||||
|
|
||||||
|
minio2:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio2
|
||||||
|
volumes:
|
||||||
|
- cdata2-1:/cdata1
|
||||||
|
- cdata2-2:/cdata2
|
||||||
|
|
||||||
|
minio3:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio3
|
||||||
|
volumes:
|
||||||
|
- cdata3-1:/cdata1
|
||||||
|
- cdata3-2:/cdata2
|
||||||
|
|
||||||
|
minio4:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio4
|
||||||
|
volumes:
|
||||||
|
- cdata4-1:/cdata1
|
||||||
|
- cdata4-2:/cdata2
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:1.19.2-alpine
|
||||||
|
hostname: nginx
|
||||||
|
volumes:
|
||||||
|
- ./nginx-4-node.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
ports:
|
||||||
|
- "9000:9000"
|
||||||
|
- "9001:9001"
|
||||||
|
depends_on:
|
||||||
|
- minio1
|
||||||
|
- minio2
|
||||||
|
- minio3
|
||||||
|
- minio4
|
||||||
|
|
||||||
|
## By default this config uses default local driver,
|
||||||
|
## For custom volumes replace with volume driver configuration.
|
||||||
|
volumes:
|
||||||
|
cdata1-1:
|
||||||
|
cdata1-2:
|
||||||
|
cdata2-1:
|
||||||
|
cdata2-2:
|
||||||
|
cdata3-1:
|
||||||
|
cdata3-2:
|
||||||
|
cdata4-1:
|
||||||
|
cdata4-2:
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
# Settings and configurations that are common for all containers
|
||||||
|
x-minio-common: &minio-common
|
||||||
|
image: quay.io/minio/minio:${JOB_NAME}
|
||||||
|
command: server --console-address ":9001" edata{1...4}
|
||||||
|
expose:
|
||||||
|
- "9000"
|
||||||
|
- "9001"
|
||||||
|
environment:
|
||||||
|
MINIO_CI_CD: "on"
|
||||||
|
MINIO_ROOT_USER: "minio"
|
||||||
|
MINIO_ROOT_PASSWORD: "minio123"
|
||||||
|
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
# starts 4 docker containers running minio server instances.
|
||||||
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
# it through port 9000.
|
||||||
|
services:
|
||||||
|
minio1:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio1
|
||||||
|
volumes:
|
||||||
|
- edata1-1:/edata1
|
||||||
|
- edata1-2:/edata2
|
||||||
|
- edata1-3:/edata3
|
||||||
|
- edata1-4:/edata4
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:1.19.2-alpine
|
||||||
|
hostname: nginx
|
||||||
|
volumes:
|
||||||
|
- ./nginx-1-node.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
ports:
|
||||||
|
- "9000:9000"
|
||||||
|
- "9001:9001"
|
||||||
|
depends_on:
|
||||||
|
- minio1
|
||||||
|
|
||||||
|
## By default this config uses default local driver,
|
||||||
|
## For custom volumes replace with volume driver configuration.
|
||||||
|
volumes:
|
||||||
|
edata1-1:
|
||||||
|
edata1-2:
|
||||||
|
edata1-3:
|
||||||
|
edata1-4:
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
# Settings and configurations that are common for all containers
|
||||||
|
x-minio-common: &minio-common
|
||||||
|
image: quay.io/minio/minio:${JOB_NAME}
|
||||||
|
command: server --console-address ":9001" http://minio{1...4}/pdata{1...2} http://minio{5...8}/pdata{1...2}
|
||||||
|
expose:
|
||||||
|
- "9000"
|
||||||
|
- "9001"
|
||||||
|
environment:
|
||||||
|
MINIO_CI_CD: "on"
|
||||||
|
MINIO_ROOT_USER: "minio"
|
||||||
|
MINIO_ROOT_PASSWORD: "minio123"
|
||||||
|
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
# starts 4 docker containers running minio server instances.
|
||||||
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
# it through port 9000.
|
||||||
|
services:
|
||||||
|
minio1:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio1
|
||||||
|
volumes:
|
||||||
|
- pdata1-1:/pdata1
|
||||||
|
- pdata1-2:/pdata2
|
||||||
|
|
||||||
|
minio2:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio2
|
||||||
|
volumes:
|
||||||
|
- pdata2-1:/pdata1
|
||||||
|
- pdata2-2:/pdata2
|
||||||
|
|
||||||
|
minio3:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio3
|
||||||
|
volumes:
|
||||||
|
- pdata3-1:/pdata1
|
||||||
|
- pdata3-2:/pdata2
|
||||||
|
|
||||||
|
minio4:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio4
|
||||||
|
volumes:
|
||||||
|
- pdata4-1:/pdata1
|
||||||
|
- pdata4-2:/pdata2
|
||||||
|
|
||||||
|
minio5:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio5
|
||||||
|
volumes:
|
||||||
|
- pdata5-1:/pdata1
|
||||||
|
- pdata5-2:/pdata2
|
||||||
|
|
||||||
|
minio6:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio6
|
||||||
|
volumes:
|
||||||
|
- pdata6-1:/pdata1
|
||||||
|
- pdata6-2:/pdata2
|
||||||
|
|
||||||
|
minio7:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio7
|
||||||
|
volumes:
|
||||||
|
- pdata7-1:/pdata1
|
||||||
|
- pdata7-2:/pdata2
|
||||||
|
|
||||||
|
minio8:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: minio8
|
||||||
|
volumes:
|
||||||
|
- pdata8-1:/pdata1
|
||||||
|
- pdata8-2:/pdata2
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:1.19.2-alpine
|
||||||
|
hostname: nginx
|
||||||
|
volumes:
|
||||||
|
- ./nginx-8-node.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
ports:
|
||||||
|
- "9000:9000"
|
||||||
|
- "9001:9001"
|
||||||
|
depends_on:
|
||||||
|
- minio1
|
||||||
|
- minio2
|
||||||
|
- minio3
|
||||||
|
- minio4
|
||||||
|
- minio5
|
||||||
|
- minio6
|
||||||
|
- minio7
|
||||||
|
- minio8
|
||||||
|
|
||||||
|
## By default this config uses default local driver,
|
||||||
|
## For custom volumes replace with volume driver configuration.
|
||||||
|
volumes:
|
||||||
|
pdata1-1:
|
||||||
|
pdata1-2:
|
||||||
|
pdata2-1:
|
||||||
|
pdata2-2:
|
||||||
|
pdata3-1:
|
||||||
|
pdata3-2:
|
||||||
|
pdata4-1:
|
||||||
|
pdata4-2:
|
||||||
|
pdata5-1:
|
||||||
|
pdata5-2:
|
||||||
|
pdata6-1:
|
||||||
|
pdata6-2:
|
||||||
|
pdata7-1:
|
||||||
|
pdata7-2:
|
||||||
|
pdata8-1:
|
||||||
|
pdata8-2:
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
|
upstream minio {
|
||||||
|
server minio1:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream console {
|
||||||
|
ip_hash;
|
||||||
|
server minio1:9001;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9000;
|
||||||
|
listen [::]:9000;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# To allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# To disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://minio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9001;
|
||||||
|
listen [::]:9001;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# To allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# To disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
|
||||||
|
# This is necessary to pass the correct IP to be hashed
|
||||||
|
real_ip_header X-Real-IP;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
|
||||||
|
# To support websocket
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://console;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
|
upstream minio {
|
||||||
|
server minio1:9000;
|
||||||
|
server minio2:9000;
|
||||||
|
server minio3:9000;
|
||||||
|
server minio4:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream console {
|
||||||
|
ip_hash;
|
||||||
|
server minio1:9001;
|
||||||
|
server minio2:9001;
|
||||||
|
server minio3:9001;
|
||||||
|
server minio4:9001;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9000;
|
||||||
|
listen [::]:9000;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# To allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# To disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://minio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9001;
|
||||||
|
listen [::]:9001;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# To allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# To disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
|
||||||
|
# This is necessary to pass the correct IP to be hashed
|
||||||
|
real_ip_header X-Real-IP;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
|
||||||
|
# To support websocket
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://console;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
|
upstream minio {
|
||||||
|
server minio1:9000;
|
||||||
|
server minio2:9000;
|
||||||
|
server minio3:9000;
|
||||||
|
server minio4:9000;
|
||||||
|
server minio5:9000;
|
||||||
|
server minio6:9000;
|
||||||
|
server minio7:9000;
|
||||||
|
server minio8:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream console {
|
||||||
|
ip_hash;
|
||||||
|
server minio1:9001;
|
||||||
|
server minio2:9001;
|
||||||
|
server minio3:9001;
|
||||||
|
server minio4:9001;
|
||||||
|
server minio5:9001;
|
||||||
|
server minio6:9001;
|
||||||
|
server minio7:9001;
|
||||||
|
server minio8:9001;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9000;
|
||||||
|
listen [::]:9000;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# To allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# To disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://minio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9001;
|
||||||
|
listen [::]:9001;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# To allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# To disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
|
||||||
|
# This is necessary to pass the correct IP to be hashed
|
||||||
|
real_ip_header X-Real-IP;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
|
||||||
|
# To support websocket
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://console;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
|
upstream minio {
|
||||||
|
server minio1:9000;
|
||||||
|
server minio2:9000;
|
||||||
|
server minio3:9000;
|
||||||
|
server minio4:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream console {
|
||||||
|
ip_hash;
|
||||||
|
server minio1:9001;
|
||||||
|
server minio2:9001;
|
||||||
|
server minio3:9001;
|
||||||
|
server minio4:9001;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9000;
|
||||||
|
listen [::]:9000;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# To allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# To disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://minio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9001;
|
||||||
|
listen [::]:9001;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# To allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# To disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
|
||||||
|
# This is necessary to pass the correct IP to be hashed
|
||||||
|
real_ip_header X-Real-IP;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
|
||||||
|
# To support websocket
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://console;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
# Settings and configurations that are common for all containers
|
||||||
|
x-minio-common: &minio-common
|
||||||
|
image: quay.io/minio/minio:${RELEASE}
|
||||||
|
command: server http://site1-minio{1...4}/data{1...2}
|
||||||
|
environment:
|
||||||
|
- MINIO_PROMETHEUS_AUTH_TYPE=public
|
||||||
|
- CI=true
|
||||||
|
|
||||||
|
# starts 4 docker containers running minio server instances.
|
||||||
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
# it through port 9000.
|
||||||
|
services:
|
||||||
|
site1-minio1:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site1-minio1
|
||||||
|
volumes:
|
||||||
|
- site1-data1-1:/data1
|
||||||
|
- site1-data1-2:/data2
|
||||||
|
|
||||||
|
site1-minio2:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site1-minio2
|
||||||
|
volumes:
|
||||||
|
- site1-data2-1:/data1
|
||||||
|
- site1-data2-2:/data2
|
||||||
|
|
||||||
|
site1-minio3:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site1-minio3
|
||||||
|
volumes:
|
||||||
|
- site1-data3-1:/data1
|
||||||
|
- site1-data3-2:/data2
|
||||||
|
|
||||||
|
site1-minio4:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site1-minio4
|
||||||
|
volumes:
|
||||||
|
- site1-data4-1:/data1
|
||||||
|
- site1-data4-2:/data2
|
||||||
|
|
||||||
|
site1-nginx:
|
||||||
|
image: nginx:1.19.2-alpine
|
||||||
|
hostname: site1-nginx
|
||||||
|
volumes:
|
||||||
|
- ./nginx-site1.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
ports:
|
||||||
|
- "9001:9001"
|
||||||
|
depends_on:
|
||||||
|
- site1-minio1
|
||||||
|
- site1-minio2
|
||||||
|
- site1-minio3
|
||||||
|
- site1-minio4
|
||||||
|
|
||||||
|
## By default this config uses default local driver,
|
||||||
|
## For custom volumes replace with volume driver configuration.
|
||||||
|
volumes:
|
||||||
|
site1-data1-1:
|
||||||
|
site1-data1-2:
|
||||||
|
site1-data2-1:
|
||||||
|
site1-data2-2:
|
||||||
|
site1-data3-1:
|
||||||
|
site1-data3-2:
|
||||||
|
site1-data4-1:
|
||||||
|
site1-data4-2:
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
# Settings and configurations that are common for all containers
|
||||||
|
x-minio-common: &minio-common
|
||||||
|
image: quay.io/minio/minio:${RELEASE}
|
||||||
|
command: server http://site2-minio{1...4}/data{1...2}
|
||||||
|
environment:
|
||||||
|
- MINIO_PROMETHEUS_AUTH_TYPE=public
|
||||||
|
- CI=true
|
||||||
|
|
||||||
|
# starts 4 docker containers running minio server instances.
|
||||||
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
|
# it through port 9000.
|
||||||
|
services:
|
||||||
|
site2-minio1:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site2-minio1
|
||||||
|
volumes:
|
||||||
|
- site2-data1-1:/data1
|
||||||
|
- site2-data1-2:/data2
|
||||||
|
|
||||||
|
site2-minio2:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site2-minio2
|
||||||
|
volumes:
|
||||||
|
- site2-data2-1:/data1
|
||||||
|
- site2-data2-2:/data2
|
||||||
|
|
||||||
|
site2-minio3:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site2-minio3
|
||||||
|
volumes:
|
||||||
|
- site2-data3-1:/data1
|
||||||
|
- site2-data3-2:/data2
|
||||||
|
|
||||||
|
site2-minio4:
|
||||||
|
<<: *minio-common
|
||||||
|
hostname: site2-minio4
|
||||||
|
volumes:
|
||||||
|
- site2-data4-1:/data1
|
||||||
|
- site2-data4-2:/data2
|
||||||
|
|
||||||
|
site2-nginx:
|
||||||
|
image: nginx:1.19.2-alpine
|
||||||
|
hostname: site2-nginx
|
||||||
|
volumes:
|
||||||
|
- ./nginx-site2.conf:/etc/nginx/nginx.conf:ro
|
||||||
|
ports:
|
||||||
|
- "9002:9002"
|
||||||
|
depends_on:
|
||||||
|
- site2-minio1
|
||||||
|
- site2-minio2
|
||||||
|
- site2-minio3
|
||||||
|
- site2-minio4
|
||||||
|
|
||||||
|
## By default this config uses default local driver,
|
||||||
|
## For custom volumes replace with volume driver configuration.
|
||||||
|
volumes:
|
||||||
|
site2-data1-1:
|
||||||
|
site2-data1-2:
|
||||||
|
site2-data2-1:
|
||||||
|
site2-data2-2:
|
||||||
|
site2-data3-1:
|
||||||
|
site2-data3-2:
|
||||||
|
site2-data4-1:
|
||||||
|
site2-data4-2:
|
||||||
Executable
+115
@@ -0,0 +1,115 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
## change working directory
|
||||||
|
cd .github/workflows/multipart/
|
||||||
|
|
||||||
|
docker-compose -f docker-compose-site1.yaml rm -s -f
|
||||||
|
docker-compose -f docker-compose-site2.yaml rm -s -f
|
||||||
|
for volume in $(docker volume ls -q | grep minio); do
|
||||||
|
docker volume rm ${volume}
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -f ./mc ]; then
|
||||||
|
wget --quiet -O mc https://dl.minio.io/client/mc/release/linux-amd64/mc &&
|
||||||
|
chmod +x mc
|
||||||
|
fi
|
||||||
|
|
||||||
|
(
|
||||||
|
cd /tmp
|
||||||
|
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
|
||||||
|
)
|
||||||
|
|
||||||
|
export RELEASE=RELEASE.2023-08-29T23-07-35Z
|
||||||
|
|
||||||
|
docker-compose -f docker-compose-site1.yaml up -d
|
||||||
|
docker-compose -f docker-compose-site2.yaml up -d
|
||||||
|
|
||||||
|
sleep 30s
|
||||||
|
|
||||||
|
./mc alias set site1 http://site1-nginx:9001 minioadmin minioadmin --api s3v4
|
||||||
|
./mc alias set site2 http://site2-nginx:9002 minioadmin minioadmin --api s3v4
|
||||||
|
|
||||||
|
./mc ready site1/
|
||||||
|
./mc ready site2/
|
||||||
|
|
||||||
|
./mc admin replicate add site1 site2
|
||||||
|
./mc mb site1/testbucket/
|
||||||
|
./mc cp -r --quiet /usr/bin site1/testbucket/
|
||||||
|
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
s3-check-md5 -h
|
||||||
|
|
||||||
|
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
|
||||||
|
if [ $failed_count_site1 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site1 uploads"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $failed_count_site2 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site2 uploads"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
./mc cp -r --quiet /usr/bin site1/testbucket/
|
||||||
|
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
|
||||||
|
## we do not need to fail here, since we are going to test
|
||||||
|
## upgrading to master, healing and being able to recover
|
||||||
|
## the last version.
|
||||||
|
if [ $failed_count_site1 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site1 uploads ${failed_count_site1}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $failed_count_site2 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site2 uploads ${failed_count_site2}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export RELEASE=${1}
|
||||||
|
|
||||||
|
docker-compose -f docker-compose-site1.yaml up -d
|
||||||
|
docker-compose -f docker-compose-site2.yaml up -d
|
||||||
|
|
||||||
|
./mc ready site1/
|
||||||
|
./mc ready site2/
|
||||||
|
|
||||||
|
for i in $(seq 1 10); do
|
||||||
|
# mc admin heal -r --remove when used against a LB endpoint
|
||||||
|
# behaves flaky, let this run 10 times before giving up
|
||||||
|
./mc admin heal -r --remove --json site1/ 2>&1 >/dev/null
|
||||||
|
./mc admin heal -r --remove --json site2/ 2>&1 >/dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
|
||||||
|
|
||||||
|
if [ $failed_count_site1 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site1 uploads"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $failed_count_site2 -ne 0 ]; then
|
||||||
|
echo "failed with multipart on site2 uploads"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker-compose -f docker-compose-site1.yaml rm -s -f
|
||||||
|
docker-compose -f docker-compose-site2.yaml rm -s -f
|
||||||
|
for volume in $(docker volume ls -q | grep minio); do
|
||||||
|
docker volume rm ${volume}
|
||||||
|
done
|
||||||
|
|
||||||
|
docker system prune -f || true
|
||||||
|
docker volume prune -f || true
|
||||||
|
docker volume rm $(docker volume ls -q -f dangling=true) || true
|
||||||
|
|
||||||
|
## change working directory
|
||||||
|
cd ../../../
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
|
upstream minio {
|
||||||
|
server site1-minio1:9000;
|
||||||
|
server site1-minio2:9000;
|
||||||
|
server site1-minio3:9000;
|
||||||
|
server site1-minio4:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9001;
|
||||||
|
listen [::]:9001;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# To allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# To disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://minio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log warn;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
# include /etc/nginx/conf.d/*.conf;
|
||||||
|
|
||||||
|
upstream minio {
|
||||||
|
server site2-minio1:9000;
|
||||||
|
server site2-minio2:9000;
|
||||||
|
server site2-minio3:9000;
|
||||||
|
server site2-minio4:9000;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9002;
|
||||||
|
listen [::]:9002;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# To allow special characters in headers
|
||||||
|
ignore_invalid_headers off;
|
||||||
|
# Allow any size file to be uploaded.
|
||||||
|
# Set to a value such as 1000m; to restrict file size to a specific value
|
||||||
|
client_max_body_size 0;
|
||||||
|
# To disable buffering
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_connect_timeout 300;
|
||||||
|
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection "";
|
||||||
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
|
proxy_pass http://minio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -21,22 +22,14 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x, 1.19.x]
|
go-version: [1.21.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.go-version }}-go-
|
|
||||||
- name: Test Decom
|
- name: Test Decom
|
||||||
run: |
|
run: |
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
name: Root lockdown tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
|
# updated.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Go ${{ matrix.go-version }} on ${{ matrix.os }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go-version: [1.21.x]
|
||||||
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
check-latest: true
|
||||||
|
- name: Start root lockdown tests
|
||||||
|
run: |
|
||||||
|
make test-root-disable
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
export MODE="$1"
|
||||||
|
export ACCESS_KEY="$2"
|
||||||
|
export SECRET_KEY="$3"
|
||||||
|
export JOB_NAME="$4"
|
||||||
|
export MINT_MODE="full"
|
||||||
|
|
||||||
|
docker system prune -f || true
|
||||||
|
docker volume prune -f || true
|
||||||
|
docker volume rm $(docker volume ls -f dangling=true) || true
|
||||||
|
|
||||||
|
## change working directory
|
||||||
|
cd .github/workflows/mint
|
||||||
|
|
||||||
|
docker-compose -f minio-${MODE}.yaml up -d
|
||||||
|
sleep 30s
|
||||||
|
|
||||||
|
docker system prune -f || true
|
||||||
|
docker volume prune -f || true
|
||||||
|
docker volume rm $(docker volume ls -q -f dangling=true) || true
|
||||||
|
|
||||||
|
# Stop two nodes, one of each pool, to check that all S3 calls work while quorum is still there
|
||||||
|
[ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio2
|
||||||
|
[ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio6
|
||||||
|
|
||||||
|
docker run --rm --net=mint_default \
|
||||||
|
--name="mint-${MODE}-${JOB_NAME}" \
|
||||||
|
-e SERVER_ENDPOINT="nginx:9000" \
|
||||||
|
-e ACCESS_KEY="${ACCESS_KEY}" \
|
||||||
|
-e SECRET_KEY="${SECRET_KEY}" \
|
||||||
|
-e ENABLE_HTTPS=0 \
|
||||||
|
-e MINT_MODE="${MINT_MODE}" \
|
||||||
|
docker.io/minio/mint:edge
|
||||||
|
|
||||||
|
docker-compose -f minio-${MODE}.yaml down || true
|
||||||
|
sleep 10s
|
||||||
|
|
||||||
|
docker system prune -f || true
|
||||||
|
docker volume prune -f || true
|
||||||
|
docker volume rm $(docker volume ls -q -f dangling=true) || true
|
||||||
|
|
||||||
|
## change working directory
|
||||||
|
cd ../../../
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
name: Shell formatting checks
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: runner / shfmt
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: luizm/action-sh-checker@master
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
SHFMT_OPTS: "-s"
|
||||||
|
with:
|
||||||
|
sh_checker_shellcheck_disable: true # disable for now
|
||||||
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
# This ensures that previous jobs for the PR are canceled when the PR is
|
# This ensures that previous jobs for the PR are canceled when the PR is
|
||||||
# updated.
|
# updated.
|
||||||
@@ -20,11 +21,11 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x, 1.19.x]
|
go-version: [1.21.x]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-go@v3
|
- uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|||||||
@@ -3,9 +3,15 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- next
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
vulncheck:
|
vulncheck:
|
||||||
name: Analysis
|
name: Analysis
|
||||||
@@ -16,7 +22,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.19.x
|
go-version: 1.21.3
|
||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Get official govulncheck
|
- name: Get official govulncheck
|
||||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
|
|||||||
+5
-1
@@ -25,16 +25,20 @@ mc.*
|
|||||||
s3-check-md5*
|
s3-check-md5*
|
||||||
xl-meta*
|
xl-meta*
|
||||||
healing-*
|
healing-*
|
||||||
inspect*
|
inspect*.zip
|
||||||
200M*
|
200M*
|
||||||
hash-set
|
hash-set
|
||||||
minio.RELEASE*
|
minio.RELEASE*
|
||||||
mc
|
mc
|
||||||
nancy
|
nancy
|
||||||
inspects/*
|
inspects/*
|
||||||
|
.bin/
|
||||||
|
*.gz
|
||||||
docs/debugging/s3-verify/s3-verify
|
docs/debugging/s3-verify/s3-verify
|
||||||
docs/debugging/xl-meta/xl-meta
|
docs/debugging/xl-meta/xl-meta
|
||||||
docs/debugging/s3-check-md5/s3-check-md5
|
docs/debugging/s3-check-md5/s3-check-md5
|
||||||
docs/debugging/hash-set/hash-set
|
docs/debugging/hash-set/hash-set
|
||||||
docs/debugging/healing-bin/healing-bin
|
docs/debugging/healing-bin/healing-bin
|
||||||
docs/debugging/inspect/inspect
|
docs/debugging/inspect/inspect
|
||||||
|
docs/debugging/pprofgoparser/pprofgoparser
|
||||||
|
docs/debugging/reorder-disks/reorder-disks
|
||||||
|
|||||||
+17
-30
@@ -1,46 +1,33 @@
|
|||||||
linters-settings:
|
linters-settings:
|
||||||
golint:
|
|
||||||
min-confidence: 0
|
|
||||||
|
|
||||||
gofumpt:
|
gofumpt:
|
||||||
lang-version: "1.18"
|
simplify: true
|
||||||
|
|
||||||
# Choose whether or not to use the extra rules that are disabled
|
|
||||||
# by default
|
|
||||||
extra-rules: false
|
|
||||||
|
|
||||||
misspell:
|
misspell:
|
||||||
locale: US
|
locale: US
|
||||||
|
|
||||||
|
staticcheck:
|
||||||
|
checks: ['all', '-ST1005', '-ST1000', '-SA4000', '-SA9004', '-SA1019', '-SA1008', '-U1000', '-ST1016']
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
disable-all: true
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
- typecheck
|
- durationcheck
|
||||||
- goimports
|
|
||||||
- misspell
|
|
||||||
- govet
|
|
||||||
- revive
|
|
||||||
- ineffassign
|
|
||||||
- gomodguard
|
|
||||||
- gofmt
|
|
||||||
- unconvert
|
|
||||||
- unused
|
|
||||||
- gocritic
|
- gocritic
|
||||||
- gofumpt
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
- gomodguard
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- misspell
|
||||||
|
- revive
|
||||||
|
- staticcheck
|
||||||
- tenv
|
- tenv
|
||||||
- durationcheck
|
- typecheck
|
||||||
|
- unconvert
|
||||||
|
- unused
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-use-default: false
|
exclude-use-default: false
|
||||||
exclude:
|
exclude:
|
||||||
- should have a package comment
|
- should have a package comment
|
||||||
- error strings should not be capitalized or end with punctuation or a newline
|
- error strings should not be capitalized or end with punctuation or a newline
|
||||||
# todo fix these when we get enough time.
|
|
||||||
- "singleCaseSwitch: should rewrite switch statement to if statement"
|
|
||||||
- "unlambda: replace"
|
|
||||||
- "captLocal:"
|
|
||||||
- "ifElseChain:"
|
|
||||||
- "elseif:"
|
|
||||||
|
|
||||||
service:
|
|
||||||
golangci-lint-version: 1.43.0 # use the fixed version to not introduce new linters unexpectedly
|
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
CVE-2020-26160
|
|
||||||
CVE-2020-15136
|
|
||||||
CVE-2020-15115
|
|
||||||
CVE-2020-15114
|
|
||||||
CVE-2022-2835
|
|
||||||
CVE-2022-2837
|
|
||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
FROM minio/minio:latest
|
FROM minio/minio:latest
|
||||||
|
|
||||||
ENV PATH=/opt/bin:$PATH
|
COPY ./minio /usr/bin/minio
|
||||||
|
|
||||||
COPY ./minio /opt/bin/minio
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
|
|||||||
+34
-24
@@ -1,4 +1,30 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
|
FROM golang:1.21-alpine as build
|
||||||
|
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG RELEASE
|
||||||
|
|
||||||
|
ENV GOPATH /go
|
||||||
|
ENV CGO_ENABLED 0
|
||||||
|
|
||||||
|
# Install curl and minisign
|
||||||
|
RUN apk add -U --no-cache ca-certificates && \
|
||||||
|
apk add -U --no-cache curl && \
|
||||||
|
go install aead.dev/minisign/cmd/minisign@v0.2.0
|
||||||
|
|
||||||
|
# Download minio binary and signature file
|
||||||
|
RUN curl -s -q https://dl.min.io/server/minio/hotfixes/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /go/bin/minio && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/hotfixes/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /go/bin/minio.minisig && \
|
||||||
|
chmod +x /go/bin/minio
|
||||||
|
|
||||||
|
# Download mc binary and signature file
|
||||||
|
RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /go/bin/mc && \
|
||||||
|
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig
|
||||||
|
|
||||||
|
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||||
|
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
|
||||||
|
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||||
|
|
||||||
|
FROM registry.access.redhat.com/ubi9/ubi-micro:9.2
|
||||||
|
|
||||||
ARG RELEASE
|
ARG RELEASE
|
||||||
|
|
||||||
@@ -17,34 +43,18 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
||||||
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
||||||
MINIO_CONFIG_ENV_FILE=config.env \
|
MINIO_CONFIG_ENV_FILE=config.env \
|
||||||
PATH=/opt/bin:$PATH
|
MC_CONFIG_DIR=/tmp/.mc
|
||||||
|
|
||||||
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||||
|
COPY --from=build /go/bin/mc /usr/bin/mc
|
||||||
|
|
||||||
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
|
||||||
COPY CREDITS /licenses/CREDITS
|
COPY CREDITS /licenses/CREDITS
|
||||||
COPY LICENSE /licenses/LICENSE
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
||||||
RUN \
|
|
||||||
microdnf clean all && \
|
|
||||||
microdnf update --nodocs && \
|
|
||||||
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
|
||||||
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
|
||||||
microdnf install minisign --nodocs && \
|
|
||||||
mkdir -p /opt/bin && chmod -R 777 /opt/bin && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/hotfixes/linux-amd64/archive/minio.${RELEASE} -o /opt/bin/minio && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/hotfixes/linux-amd64/archive/minio.${RELEASE}.sha256sum -o /opt/bin/minio.sha256sum && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/hotfixes/linux-amd64/archive/minio.${RELEASE}.minisig -o /opt/bin/minio.minisig && \
|
|
||||||
microdnf clean all && \
|
|
||||||
chmod +x /opt/bin/minio && \
|
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
|
||||||
/usr/bin/verify-minio.sh && \
|
|
||||||
microdnf clean all
|
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
CMD ["minio"]
|
CMD ["minio"]
|
||||||
|
|||||||
+32
-24
@@ -1,6 +1,30 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
|
FROM golang:1.21-alpine as build
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
ARG RELEASE
|
||||||
|
|
||||||
|
ENV GOPATH /go
|
||||||
|
ENV CGO_ENABLED 0
|
||||||
|
|
||||||
|
# Install curl and minisign
|
||||||
|
RUN apk add -U --no-cache ca-certificates && \
|
||||||
|
apk add -U --no-cache curl && \
|
||||||
|
go install aead.dev/minisign/cmd/minisign@v0.2.0
|
||||||
|
|
||||||
|
# Download minio binary and signature file
|
||||||
|
RUN curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /go/bin/minio && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /go/bin/minio.minisig && \
|
||||||
|
chmod +x /go/bin/minio
|
||||||
|
|
||||||
|
# Download mc binary and signature file
|
||||||
|
RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /go/bin/mc && \
|
||||||
|
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig
|
||||||
|
|
||||||
|
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||||
|
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
|
||||||
|
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||||
|
|
||||||
|
FROM registry.access.redhat.com/ubi9/ubi-micro:9.2
|
||||||
|
|
||||||
ARG RELEASE
|
ARG RELEASE
|
||||||
|
|
||||||
@@ -19,34 +43,18 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
||||||
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
||||||
MINIO_CONFIG_ENV_FILE=config.env \
|
MINIO_CONFIG_ENV_FILE=config.env \
|
||||||
PATH=/opt/bin:$PATH
|
MC_CONFIG_DIR=/tmp/.mc
|
||||||
|
|
||||||
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||||
|
COPY --from=build /go/bin/mc /usr/bin/mc
|
||||||
|
|
||||||
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
|
||||||
COPY CREDITS /licenses/CREDITS
|
COPY CREDITS /licenses/CREDITS
|
||||||
COPY LICENSE /licenses/LICENSE
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
||||||
RUN \
|
|
||||||
microdnf clean all && \
|
|
||||||
microdnf update --nodocs && \
|
|
||||||
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
|
||||||
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
|
||||||
microdnf install minisign --nodocs && \
|
|
||||||
mkdir -p /opt/bin && chmod -R 777 /opt/bin && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /opt/bin/minio && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.sha256sum -o /opt/bin/minio.sha256sum && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.minisig -o /opt/bin/minio.minisig && \
|
|
||||||
microdnf clean all && \
|
|
||||||
chmod +x /opt/bin/minio && \
|
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
|
||||||
/usr/bin/verify-minio.sh && \
|
|
||||||
microdnf clean all
|
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
CMD ["minio"]
|
CMD ["minio"]
|
||||||
|
|||||||
+26
-25
@@ -1,6 +1,25 @@
|
|||||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
|
FROM golang:1.21-alpine as build
|
||||||
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
ARG RELEASE
|
||||||
|
|
||||||
|
ENV GOPATH /go
|
||||||
|
ENV CGO_ENABLED 0
|
||||||
|
|
||||||
|
# Install curl and minisign
|
||||||
|
RUN apk add -U --no-cache ca-certificates && \
|
||||||
|
apk add -U --no-cache curl && \
|
||||||
|
go install aead.dev/minisign/cmd/minisign@v0.2.0
|
||||||
|
|
||||||
|
# Download minio binary and signature file
|
||||||
|
RUN curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips -o /go/bin/minio && \
|
||||||
|
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips.minisig -o /go/bin/minio.minisig && \
|
||||||
|
chmod +x /go/bin/minio
|
||||||
|
|
||||||
|
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||||
|
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||||
|
|
||||||
|
FROM registry.access.redhat.com/ubi9/ubi-micro:9.2
|
||||||
|
|
||||||
ARG RELEASE
|
ARG RELEASE
|
||||||
|
|
||||||
@@ -18,35 +37,17 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
||||||
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
||||||
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
||||||
MINIO_CONFIG_ENV_FILE=config.env \
|
MINIO_CONFIG_ENV_FILE=config.env
|
||||||
PATH=/opt/bin:$PATH
|
|
||||||
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||||
|
|
||||||
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
|
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
|
||||||
COPY CREDITS /licenses/CREDITS
|
COPY CREDITS /licenses/CREDITS
|
||||||
COPY LICENSE /licenses/LICENSE
|
COPY LICENSE /licenses/LICENSE
|
||||||
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
||||||
RUN \
|
|
||||||
microdnf clean all && \
|
|
||||||
microdnf update --nodocs && \
|
|
||||||
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
|
||||||
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm && \
|
|
||||||
microdnf install minisign --nodocs && \
|
|
||||||
mkdir -p /opt/bin && chmod -R 777 /opt/bin && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips -o /opt/bin/minio && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips.sha256sum -o /opt/bin/minio.sha256sum && \
|
|
||||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips.minisig -o /opt/bin/minio.minisig && \
|
|
||||||
microdnf clean all && \
|
|
||||||
chmod +x /opt/bin/minio && \
|
|
||||||
chmod +x /usr/bin/docker-entrypoint.sh && \
|
|
||||||
chmod +x /usr/bin/verify-minio.sh && \
|
|
||||||
/usr/bin/verify-minio.sh && \
|
|
||||||
microdnf clean all
|
|
||||||
|
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
|
||||||
|
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/docker-entrypoint.sh"]
|
||||||
CMD ["minio"]
|
CMD ["minio"]
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ GOARCH := $(shell go env GOARCH)
|
|||||||
GOOS := $(shell go env GOOS)
|
GOOS := $(shell go env GOOS)
|
||||||
|
|
||||||
VERSION ?= $(shell git describe --tags)
|
VERSION ?= $(shell git describe --tags)
|
||||||
TAG ?= "minio/minio:$(VERSION)"
|
TAG ?= "quay.io/minio/minio:$(VERSION)"
|
||||||
|
|
||||||
|
GOLANGCI_VERSION = v1.51.2
|
||||||
|
GOLANGCI_DIR = .bin/golangci/$(GOLANGCI_VERSION)
|
||||||
|
GOLANGCI = $(GOLANGCI_DIR)/golangci-lint
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
@@ -19,36 +23,44 @@ help: ## print this help
|
|||||||
|
|
||||||
getdeps: ## fetch necessary dependencies
|
getdeps: ## fetch necessary dependencies
|
||||||
@mkdir -p ${GOPATH}/bin
|
@mkdir -p ${GOPATH}/bin
|
||||||
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin
|
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOLANGCI_DIR) $(GOLANGCI_VERSION)
|
||||||
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@f3635b96e4838a6c773babb65ef35297fe5fe2f9
|
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@v1.1.7
|
||||||
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
|
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
|
||||||
|
|
||||||
crosscompile: ## cross compile minio
|
crosscompile: ## cross compile minio
|
||||||
@(env bash $(PWD)/buildscripts/cross-compile.sh)
|
@(env bash $(PWD)/buildscripts/cross-compile.sh)
|
||||||
|
|
||||||
verifiers: getdeps lint check-gen
|
verifiers: lint check-gen
|
||||||
|
|
||||||
check-gen: ## check for updated autogenerated files
|
check-gen: ## check for updated autogenerated files
|
||||||
@go generate ./... >/dev/null
|
@go generate ./... >/dev/null
|
||||||
@(! git diff --name-only | grep '_gen.go$$') || (echo "Non-committed changes in auto-generated code is detected, please commit them to proceed." && false)
|
@(! git diff --name-only | grep '_gen.go$$') || (echo "Non-committed changes in auto-generated code is detected, please commit them to proceed." && false)
|
||||||
|
|
||||||
lint: ## runs golangci-lint suite of linters
|
lint: getdeps ## runs golangci-lint suite of linters
|
||||||
@echo "Running $@ check"
|
@echo "Running $@ check"
|
||||||
@${GOPATH}/bin/golangci-lint run --build-tags kqueue --timeout=10m --config ./.golangci.yml
|
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml
|
||||||
|
|
||||||
|
lint-fix: getdeps ## runs golangci-lint suite of linters with automatic fixes
|
||||||
|
@echo "Running $@ check"
|
||||||
|
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix
|
||||||
|
|
||||||
check: test
|
check: test
|
||||||
test: verifiers build ## builds minio, runs linters, tests
|
test: verifiers build build-debugging ## builds minio, runs linters, tests
|
||||||
@echo "Running unit tests"
|
@echo "Running unit tests"
|
||||||
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue ./...
|
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue ./...
|
||||||
|
|
||||||
test-decom: install
|
test-root-disable: install-race
|
||||||
|
@echo "Running minio root lockdown tests"
|
||||||
|
@env bash $(PWD)/buildscripts/disable-root.sh
|
||||||
|
|
||||||
|
test-decom: install-race
|
||||||
@echo "Running minio decom tests"
|
@echo "Running minio decom tests"
|
||||||
@env bash $(PWD)/docs/distributed/decom.sh
|
@env bash $(PWD)/docs/distributed/decom.sh
|
||||||
@env bash $(PWD)/docs/distributed/decom-encrypted.sh
|
@env bash $(PWD)/docs/distributed/decom-encrypted.sh
|
||||||
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
|
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
|
||||||
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
|
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
|
||||||
|
|
||||||
test-upgrade: build
|
test-upgrade: install-race
|
||||||
@echo "Running minio upgrade tests"
|
@echo "Running minio upgrade tests"
|
||||||
@(env bash $(PWD)/buildscripts/minio-upgrade.sh)
|
@(env bash $(PWD)/buildscripts/minio-upgrade.sh)
|
||||||
|
|
||||||
@@ -62,20 +74,30 @@ test-iam: build ## verify IAM (external IDP, etcd backends)
|
|||||||
@echo "Running tests for IAM (external IDP, etcd backends) with -race"
|
@echo "Running tests for IAM (external IDP, etcd backends) with -race"
|
||||||
@MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -race -tags kqueue -v -run TestIAM* ./cmd
|
@MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -race -tags kqueue -v -run TestIAM* ./cmd
|
||||||
|
|
||||||
test-replication: install ## verify multi site replication
|
test-sio-error:
|
||||||
@echo "Running tests for replicating three sites"
|
@(env bash $(PWD)/docs/bucket/replication/sio-error.sh)
|
||||||
@(env bash $(PWD)/docs/bucket/replication/setup_3site_replication.sh)
|
|
||||||
|
test-replication-2site:
|
||||||
@(env bash $(PWD)/docs/bucket/replication/setup_2site_existing_replication.sh)
|
@(env bash $(PWD)/docs/bucket/replication/setup_2site_existing_replication.sh)
|
||||||
|
|
||||||
test-site-replication-ldap: install ## verify automatic site replication
|
test-replication-3site:
|
||||||
|
@(env bash $(PWD)/docs/bucket/replication/setup_3site_replication.sh)
|
||||||
|
|
||||||
|
test-delete-replication:
|
||||||
|
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
|
||||||
|
|
||||||
|
test-replication: install-race test-replication-2site test-replication-3site test-delete-replication test-sio-error ## verify multi site replication
|
||||||
|
@echo "Running tests for replicating three sites"
|
||||||
|
|
||||||
|
test-site-replication-ldap: install-race ## verify automatic site replication
|
||||||
@echo "Running tests for automatic site replication of IAM (with LDAP)"
|
@echo "Running tests for automatic site replication of IAM (with LDAP)"
|
||||||
@(env bash $(PWD)/docs/site-replication/run-multi-site-ldap.sh)
|
@(env bash $(PWD)/docs/site-replication/run-multi-site-ldap.sh)
|
||||||
|
|
||||||
test-site-replication-oidc: install ## verify automatic site replication
|
test-site-replication-oidc: install-race ## verify automatic site replication
|
||||||
@echo "Running tests for automatic site replication of IAM (with OIDC)"
|
@echo "Running tests for automatic site replication of IAM (with OIDC)"
|
||||||
@(env bash $(PWD)/docs/site-replication/run-multi-site-oidc.sh)
|
@(env bash $(PWD)/docs/site-replication/run-multi-site-oidc.sh)
|
||||||
|
|
||||||
test-site-replication-minio: install ## verify automatic site replication
|
test-site-replication-minio: install-race ## verify automatic site replication
|
||||||
@echo "Running tests for automatic site replication of IAM (with MinIO IDP)"
|
@echo "Running tests for automatic site replication of IAM (with MinIO IDP)"
|
||||||
@(env bash $(PWD)/docs/site-replication/run-multi-site-minio-idp.sh)
|
@(env bash $(PWD)/docs/site-replication/run-multi-site-minio-idp.sh)
|
||||||
|
|
||||||
@@ -106,6 +128,9 @@ verify-healing-inconsistent-versions: ## verify resolving inconsistent versions
|
|||||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
@(env bash $(PWD)/buildscripts/resolve-right-versions.sh)
|
@(env bash $(PWD)/buildscripts/resolve-right-versions.sh)
|
||||||
|
|
||||||
|
build-debugging:
|
||||||
|
@(env bash $(PWD)/docs/debugging/build.sh)
|
||||||
|
|
||||||
build: checks ## builds minio to $(PWD)
|
build: checks ## builds minio to $(PWD)
|
||||||
@echo "Building minio binary to './minio'"
|
@echo "Building minio binary to './minio'"
|
||||||
@CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
@CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
@@ -133,10 +158,16 @@ docker-hotfix: hotfix-push checks ## builds minio docker container with hotfix t
|
|||||||
@echo "Building minio docker image '$(TAG)'"
|
@echo "Building minio docker image '$(TAG)'"
|
||||||
@docker build -q --no-cache -t $(TAG) --build-arg RELEASE=$(VERSION) . -f Dockerfile.hotfix
|
@docker build -q --no-cache -t $(TAG) --build-arg RELEASE=$(VERSION) . -f Dockerfile.hotfix
|
||||||
|
|
||||||
docker: build checks ## builds minio docker container
|
docker: build ## builds minio docker container
|
||||||
@echo "Building minio docker image '$(TAG)'"
|
@echo "Building minio docker image '$(TAG)'"
|
||||||
@docker build -q --no-cache -t $(TAG) . -f Dockerfile
|
@docker build -q --no-cache -t $(TAG) . -f Dockerfile
|
||||||
|
|
||||||
|
install-race: checks ## builds minio to $(PWD)
|
||||||
|
@echo "Building minio binary to './minio'"
|
||||||
|
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||||
|
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
|
||||||
|
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio
|
||||||
|
|
||||||
install: build ## builds minio and installs it to $GOPATH/bin.
|
install: build ## builds minio and installs it to $GOPATH/bin.
|
||||||
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
|
@echo "Installing minio binary to '$(GOPATH)/bin/minio'"
|
||||||
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio
|
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
MinIO Project, (C) 2015-2021 MinIO, Inc.
|
MinIO Project, (C) 2015-2023 MinIO, Inc.
|
||||||
|
|
||||||
This product includes software developed at MinIO, Inc.
|
This product includes software developed at MinIO, Inc.
|
||||||
(https://min.io/).
|
(https://min.io/).
|
||||||
|
|||||||
@@ -86,8 +86,6 @@ chmod +x minio
|
|||||||
./minio server /data
|
./minio server /data
|
||||||
```
|
```
|
||||||
|
|
||||||
Replace ``/data`` with the path to the drive or directory in which you want MinIO to store data.
|
|
||||||
|
|
||||||
The following table lists supported architectures. Replace the `wget` URL with the architecture for your Linux host.
|
The following table lists supported architectures. Replace the `wget` URL with the architecture for your Linux host.
|
||||||
|
|
||||||
| Architecture | URL |
|
| Architecture | URL |
|
||||||
@@ -125,7 +123,7 @@ You can also connect using any S3-compatible tool, such as the MinIO Client `mc`
|
|||||||
|
|
||||||
## Install from Source
|
## Install from Source
|
||||||
|
|
||||||
Use the following commands to compile and run a standalone MinIO server from source. Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.18](https://golang.org/dl/#stable)
|
Use the following commands to compile and run a standalone MinIO server from source. Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.19](https://golang.org/dl/#stable)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go install github.com/minio/minio@latest
|
go install github.com/minio/minio@latest
|
||||||
@@ -202,7 +200,7 @@ service iptables restart
|
|||||||
|
|
||||||
MinIO Server comes with an embedded web based object browser. Point your web browser to <http://127.0.0.1:9000> to ensure your server has started successfully.
|
MinIO Server comes with an embedded web based object browser. Point your web browser to <http://127.0.0.1:9000> to ensure your server has started successfully.
|
||||||
|
|
||||||
> NOTE: MinIO runs console on random port by default if you wish choose a specific port use `--console-address` to pick a specific interface and port.
|
> NOTE: MinIO runs console on random port by default, if you wish to choose a specific port use `--console-address` to pick a specific interface and port.
|
||||||
|
|
||||||
### Things to consider
|
### Things to consider
|
||||||
|
|
||||||
@@ -243,7 +241,7 @@ mc admin update <minio alias, e.g., myminio>
|
|||||||
### Upgrade Checklist
|
### Upgrade Checklist
|
||||||
|
|
||||||
- Test all upgrades in a lower environment (DEV, QA, UAT) before applying to production. Performing blind upgrades in production environments carries significant risk.
|
- Test all upgrades in a lower environment (DEV, QA, UAT) before applying to production. Performing blind upgrades in production environments carries significant risk.
|
||||||
- Read the release notes for MinIO *before* performing any upgrade, there is no forced requirement to upgrade to latest releases upon every releases. Some releases may not be relevant to your setup, avoid upgrading production environments unnecessarily.
|
- Read the release notes for MinIO *before* performing any upgrade, there is no forced requirement to upgrade to latest release upon every release. Some release may not be relevant to your setup, avoid upgrading production environments unnecessarily.
|
||||||
- If you plan to use `mc admin update`, MinIO process must have write access to the parent directory where the binary is present on the host system.
|
- If you plan to use `mc admin update`, MinIO process must have write access to the parent directory where the binary is present on the host system.
|
||||||
- `mc admin update` is not supported and should be avoided in kubernetes/container environments, please upgrade containers by upgrading relevant container images.
|
- `mc admin update` is not supported and should be avoided in kubernetes/container environments, please upgrade containers by upgrading relevant container images.
|
||||||
- **We do not recommend upgrading one MinIO server at a time, the product is designed to support parallel upgrades please follow our recommended guidelines.**
|
- **We do not recommend upgrading one MinIO server at a time, the product is designed to support parallel upgrades please follow our recommended guidelines.**
|
||||||
@@ -261,6 +259,6 @@ Please follow MinIO [Contributor's Guide](https://github.com/minio/minio/blob/ma
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
- MinIO source is licensed under the GNU AGPLv3 license that can be found in the [LICENSE](https://github.com/minio/minio/blob/master/LICENSE) file.
|
- MinIO source is licensed under the [GNU AGPLv3](https://github.com/minio/minio/blob/master/LICENSE).
|
||||||
- MinIO [Documentation](https://github.com/minio/minio/tree/master/docs) © 2021 by MinIO, Inc is licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
|
- MinIO [documentation](https://github.com/minio/minio/tree/master/docs) is licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
|
||||||
- [License Compliance](https://github.com/minio/minio/blob/master/COMPLIANCE.md)
|
- [License Compliance](https://github.com/minio/minio/blob/master/COMPLIANCE.md)
|
||||||
|
|||||||
+85
-84
@@ -3,19 +3,19 @@
|
|||||||
|
|
||||||
_init() {
|
_init() {
|
||||||
|
|
||||||
shopt -s extglob
|
shopt -s extglob
|
||||||
|
|
||||||
## Minimum required versions for build dependencies
|
## Minimum required versions for build dependencies
|
||||||
GIT_VERSION="1.0"
|
GIT_VERSION="1.0"
|
||||||
GO_VERSION="1.16"
|
GO_VERSION="1.16"
|
||||||
OSX_VERSION="10.8"
|
OSX_VERSION="10.8"
|
||||||
KNAME=$(uname -s)
|
KNAME=$(uname -s)
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
case "${KNAME}" in
|
case "${KNAME}" in
|
||||||
SunOS )
|
SunOS)
|
||||||
ARCH=$(isainfo -k)
|
ARCH=$(isainfo -k)
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
## FIXME:
|
## FIXME:
|
||||||
@@ -28,24 +28,23 @@ _init() {
|
|||||||
## }
|
## }
|
||||||
##
|
##
|
||||||
readlink() {
|
readlink() {
|
||||||
TARGET_FILE=$1
|
TARGET_FILE=$1
|
||||||
|
|
||||||
cd `dirname $TARGET_FILE`
|
cd $(dirname $TARGET_FILE)
|
||||||
TARGET_FILE=`basename $TARGET_FILE`
|
TARGET_FILE=$(basename $TARGET_FILE)
|
||||||
|
|
||||||
# Iterate down a (possible) chain of symlinks
|
# Iterate down a (possible) chain of symlinks
|
||||||
while [ -L "$TARGET_FILE" ]
|
while [ -L "$TARGET_FILE" ]; do
|
||||||
do
|
TARGET_FILE=$(env readlink $TARGET_FILE)
|
||||||
TARGET_FILE=$(env readlink $TARGET_FILE)
|
cd $(dirname $TARGET_FILE)
|
||||||
cd `dirname $TARGET_FILE`
|
TARGET_FILE=$(basename $TARGET_FILE)
|
||||||
TARGET_FILE=`basename $TARGET_FILE`
|
done
|
||||||
done
|
|
||||||
|
|
||||||
# Compute the canonicalized name by finding the physical path
|
# Compute the canonicalized name by finding the physical path
|
||||||
# for the directory we're in and appending the target file.
|
# for the directory we're in and appending the target file.
|
||||||
PHYS_DIR=`pwd -P`
|
PHYS_DIR=$(pwd -P)
|
||||||
RESULT=$PHYS_DIR/$TARGET_FILE
|
RESULT=$PHYS_DIR/$TARGET_FILE
|
||||||
echo $RESULT
|
echo $RESULT
|
||||||
}
|
}
|
||||||
|
|
||||||
## FIXME:
|
## FIXME:
|
||||||
@@ -59,84 +58,86 @@ readlink() {
|
|||||||
## }
|
## }
|
||||||
##
|
##
|
||||||
check_minimum_version() {
|
check_minimum_version() {
|
||||||
IFS='.' read -r -a varray1 <<< "$1"
|
IFS='.' read -r -a varray1 <<<"$1"
|
||||||
IFS='.' read -r -a varray2 <<< "$2"
|
IFS='.' read -r -a varray2 <<<"$2"
|
||||||
|
|
||||||
for i in "${!varray1[@]}"; do
|
for i in "${!varray1[@]}"; do
|
||||||
if [[ ${varray1[i]} -lt ${varray2[i]} ]]; then
|
if [[ ${varray1[i]} -lt ${varray2[i]} ]]; then
|
||||||
return 0
|
return 0
|
||||||
elif [[ ${varray1[i]} -gt ${varray2[i]} ]]; then
|
elif [[ ${varray1[i]} -gt ${varray2[i]} ]]; then
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_is_supported_arch() {
|
assert_is_supported_arch() {
|
||||||
case "${ARCH}" in
|
case "${ARCH}" in
|
||||||
x86_64 | amd64 | aarch64 | ppc64le | arm* | s390x )
|
x86_64 | amd64 | aarch64 | ppc64le | arm* | s390x | loong64 | loongarch64)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Arch '${ARCH}' is not supported. Supported Arch: [x86_64, amd64, aarch64, ppc64le, arm*, s390x]"
|
echo "Arch '${ARCH}' is not supported. Supported Arch: [x86_64, amd64, aarch64, ppc64le, arm*, s390x, loong64, loongarch64]"
|
||||||
exit 1
|
exit 1
|
||||||
esac
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_is_supported_os() {
|
assert_is_supported_os() {
|
||||||
case "${KNAME}" in
|
case "${KNAME}" in
|
||||||
Linux | FreeBSD | OpenBSD | NetBSD | DragonFly | SunOS )
|
Linux | FreeBSD | OpenBSD | NetBSD | DragonFly | SunOS)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
Darwin )
|
Darwin)
|
||||||
osx_host_version=$(env sw_vers -productVersion)
|
osx_host_version=$(env sw_vers -productVersion)
|
||||||
if ! check_minimum_version "${OSX_VERSION}" "${osx_host_version}"; then
|
if ! check_minimum_version "${OSX_VERSION}" "${osx_host_version}"; then
|
||||||
echo "OSX version '${osx_host_version}' is not supported. Minimum supported version: ${OSX_VERSION}"
|
echo "OSX version '${osx_host_version}' is not supported. Minimum supported version: ${OSX_VERSION}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "OS '${KNAME}' is not supported. Supported OS: [Linux, FreeBSD, OpenBSD, NetBSD, Darwin, DragonFly]"
|
echo "OS '${KNAME}' is not supported. Supported OS: [Linux, FreeBSD, OpenBSD, NetBSD, Darwin, DragonFly]"
|
||||||
exit 1
|
exit 1
|
||||||
esac
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_check_golang_env() {
|
assert_check_golang_env() {
|
||||||
if ! which go >/dev/null 2>&1; then
|
if ! which go >/dev/null 2>&1; then
|
||||||
echo "Cannot find go binary in your PATH configuration, please refer to Go installation document at https://golang.org/doc/install"
|
echo "Cannot find go binary in your PATH configuration, please refer to Go installation document at https://golang.org/doc/install"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
installed_go_version=$(go version | sed 's/^.* go\([0-9.]*\).*$/\1/')
|
installed_go_version=$(go version | sed 's/^.* go\([0-9.]*\).*$/\1/')
|
||||||
if ! check_minimum_version "${GO_VERSION}" "${installed_go_version}"; then
|
if ! check_minimum_version "${GO_VERSION}" "${installed_go_version}"; then
|
||||||
echo "Go runtime version '${installed_go_version}' is unsupported. Minimum supported version: ${GO_VERSION} to compile."
|
echo "Go runtime version '${installed_go_version}' is unsupported. Minimum supported version: ${GO_VERSION} to compile."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_check_deps() {
|
assert_check_deps() {
|
||||||
# support unusual Git versions such as: 2.7.4 (Apple Git-66)
|
# support unusual Git versions such as: 2.7.4 (Apple Git-66)
|
||||||
installed_git_version=$(git version | perl -ne '$_ =~ m/git version (.*?)( |$)/; print "$1\n";')
|
installed_git_version=$(git version | perl -ne '$_ =~ m/git version (.*?)( |$)/; print "$1\n";')
|
||||||
if ! check_minimum_version "${GIT_VERSION}" "${installed_git_version}"; then
|
if ! check_minimum_version "${GIT_VERSION}" "${installed_git_version}"; then
|
||||||
echo "Git version '${installed_git_version}' is not supported. Minimum supported version: ${GIT_VERSION}"
|
echo "Git version '${installed_git_version}' is not supported. Minimum supported version: ${GIT_VERSION}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
## Check for supported arch
|
## Check for supported arch
|
||||||
assert_is_supported_arch
|
assert_is_supported_arch
|
||||||
|
|
||||||
## Check for supported os
|
## Check for supported os
|
||||||
assert_is_supported_os
|
assert_is_supported_os
|
||||||
|
|
||||||
## Check for Go environment
|
## Check for Go environment
|
||||||
assert_check_golang_env
|
assert_check_golang_env
|
||||||
|
|
||||||
## Check for dependencies
|
## Check for dependencies
|
||||||
assert_check_deps
|
assert_check_deps
|
||||||
}
|
}
|
||||||
|
|
||||||
_init && main "$@"
|
_init && main "$@"
|
||||||
|
|||||||
@@ -5,33 +5,33 @@ set -e
|
|||||||
[ -n "$BASH_XTRACEFD" ] && set -x
|
[ -n "$BASH_XTRACEFD" ] && set -x
|
||||||
|
|
||||||
function _init() {
|
function _init() {
|
||||||
## All binaries are static make sure to disable CGO.
|
## All binaries are static make sure to disable CGO.
|
||||||
export CGO_ENABLED=0
|
export CGO_ENABLED=0
|
||||||
|
|
||||||
## List of architectures and OS to test coss compilation.
|
## List of architectures and OS to test coss compilation.
|
||||||
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips openbsd/amd64"
|
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/amd64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips"
|
||||||
}
|
}
|
||||||
|
|
||||||
function _build() {
|
function _build() {
|
||||||
local osarch=$1
|
local osarch=$1
|
||||||
IFS=/ read -r -a arr <<<"$osarch"
|
IFS=/ read -r -a arr <<<"$osarch"
|
||||||
os="${arr[0]}"
|
os="${arr[0]}"
|
||||||
arch="${arr[1]}"
|
arch="${arr[1]}"
|
||||||
package=$(go list -f '{{.ImportPath}}')
|
package=$(go list -f '{{.ImportPath}}')
|
||||||
printf -- "--> %15s:%s\n" "${osarch}" "${package}"
|
printf -- "--> %15s:%s\n" "${osarch}" "${package}"
|
||||||
|
|
||||||
# go build -trimpath to build the binary.
|
# go build -trimpath to build the binary.
|
||||||
export GOOS=$os
|
export GOOS=$os
|
||||||
export GOARCH=$arch
|
export GOARCH=$arch
|
||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
go build -trimpath -tags kqueue -o /dev/null
|
go build -trimpath -tags kqueue -o /dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
echo "Testing builds for OS/Arch: ${SUPPORTED_OSARCH}"
|
echo "Testing builds for OS/Arch: ${SUPPORTED_OSARCH}"
|
||||||
for each_osarch in ${SUPPORTED_OSARCH}; do
|
for each_osarch in ${SUPPORTED_OSARCH}; do
|
||||||
_build "${each_osarch}"
|
_build "${each_osarch}"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
_init && main "$@"
|
_init && main "$@"
|
||||||
|
|||||||
Executable
+121
@@ -0,0 +1,121 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
export MINIO_CI_CD=1
|
||||||
|
killall -9 minio
|
||||||
|
|
||||||
|
rm -rf ${HOME}/tmp/dist
|
||||||
|
|
||||||
|
scheme="http"
|
||||||
|
nr_servers=4
|
||||||
|
|
||||||
|
addr="localhost"
|
||||||
|
args=""
|
||||||
|
for ((i = 0; i < $((nr_servers)); i++)); do
|
||||||
|
args="$args $scheme://$addr:$((9100 + i))/${HOME}/tmp/dist/path1/$i"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo $args
|
||||||
|
|
||||||
|
for ((i = 0; i < $((nr_servers)); i++)); do
|
||||||
|
(minio server --address ":$((9100 + i))" $args 2>&1 >/tmp/log$i.txt) &
|
||||||
|
done
|
||||||
|
|
||||||
|
sleep 10s
|
||||||
|
|
||||||
|
if [ ! -f ./mc ]; then
|
||||||
|
wget --quiet -O ./mc https://dl.minio.io/client/mc/release/linux-amd64/./mc &&
|
||||||
|
chmod +x mc
|
||||||
|
fi
|
||||||
|
|
||||||
|
set +e
|
||||||
|
|
||||||
|
export MC_HOST_minioadm=http://minioadmin:minioadmin@localhost:9100/
|
||||||
|
|
||||||
|
./mc ls minioadm/
|
||||||
|
|
||||||
|
./mc admin config set minioadm/ api root_access=off
|
||||||
|
|
||||||
|
sleep 3s # let things settle a little
|
||||||
|
|
||||||
|
./mc ls minioadm/
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "listing succeeded, 'minioadmin' was not disabled"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
killall -9 minio
|
||||||
|
|
||||||
|
export MINIO_API_ROOT_ACCESS=on
|
||||||
|
for ((i = 0; i < $((nr_servers)); i++)); do
|
||||||
|
(minio server --address ":$((9100 + i))" $args 2>&1 >/tmp/log$i.txt) &
|
||||||
|
done
|
||||||
|
|
||||||
|
set +e
|
||||||
|
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
./mc ls minioadm/
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "listing failed, 'minioadmin' should be enabled"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
killall -9 minio
|
||||||
|
|
||||||
|
rm -rf /tmp/multisitea/
|
||||||
|
rm -rf /tmp/multisiteb/
|
||||||
|
|
||||||
|
echo "Setup site-replication and then disable root credentials"
|
||||||
|
|
||||||
|
minio server --address 127.0.0.1:9001 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_1.log 2>&1 &
|
||||||
|
minio server --address 127.0.0.1:9002 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_2.log 2>&1 &
|
||||||
|
|
||||||
|
minio server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_1.log 2>&1 &
|
||||||
|
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
|
||||||
|
|
||||||
|
sleep 20s
|
||||||
|
|
||||||
|
export MC_HOST_sitea=http://minioadmin:minioadmin@127.0.0.1:9001
|
||||||
|
export MC_HOST_siteb=http://minioadmin:minioadmin@127.0.0.1:9004
|
||||||
|
|
||||||
|
./mc admin replicate add sitea siteb
|
||||||
|
|
||||||
|
./mc admin user add sitea foobar foo12345
|
||||||
|
|
||||||
|
./mc admin policy attach sitea/ consoleAdmin --user=foobar
|
||||||
|
|
||||||
|
./mc admin user info siteb foobar
|
||||||
|
|
||||||
|
killall -9 minio
|
||||||
|
|
||||||
|
echo "turning off root access, however site replication must continue"
|
||||||
|
export MINIO_API_ROOT_ACCESS=off
|
||||||
|
|
||||||
|
minio server --address 127.0.0.1:9001 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_1.log 2>&1 &
|
||||||
|
minio server --address 127.0.0.1:9002 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_2.log 2>&1 &
|
||||||
|
|
||||||
|
minio server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_1.log 2>&1 &
|
||||||
|
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
|
||||||
|
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
|
||||||
|
|
||||||
|
sleep 20s
|
||||||
|
|
||||||
|
export MC_HOST_sitea=http://foobar:foo12345@127.0.0.1:9001
|
||||||
|
export MC_HOST_siteb=http://foobar:foo12345@127.0.0.1:9004
|
||||||
|
|
||||||
|
./mc admin user add sitea foobar-admin foo12345
|
||||||
|
|
||||||
|
sleep 2s
|
||||||
|
|
||||||
|
./mc admin user info siteb foobar-admin
|
||||||
@@ -6,88 +6,87 @@ set -x
|
|||||||
|
|
||||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||||
|
|
||||||
if [ ! -x "$PWD/minio" ]; then
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
echo "minio executable binary not found in current directory"
|
echo "minio executable binary not found in current directory"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -x "$PWD/minio" ]; then
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
echo "minio executable binary not found in current directory"
|
echo "minio executable binary not found in current directory"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
function start_minio_4drive() {
|
function start_minio_4drive() {
|
||||||
start_port=$1
|
start_port=$1
|
||||||
|
|
||||||
export MINIO_ROOT_USER=minio
|
export MINIO_ROOT_USER=minio
|
||||||
export MINIO_ROOT_PASSWORD=minio123
|
export MINIO_ROOT_PASSWORD=minio123
|
||||||
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||||
export MINIO_CI_CD=1
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
mkdir ${WORK_DIR}
|
mkdir ${WORK_DIR}
|
||||||
C_PWD=${PWD}
|
C_PWD=${PWD}
|
||||||
if [ ! -x "$PWD/mc" ]; then
|
if [ ! -x "$PWD/mc" ]; then
|
||||||
MC_BUILD_DIR="mc-$RANDOM"
|
MC_BUILD_DIR="mc-$RANDOM"
|
||||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||||
echo "failed to download https://github.com/minio/mc"
|
echo "failed to download https://github.com/minio/mc"
|
||||||
purge "${MC_BUILD_DIR}"
|
purge "${MC_BUILD_DIR}"
|
||||||
exit 1
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(cd "${MC_BUILD_DIR}" && go build -o "$C_PWD/mc")
|
||||||
|
|
||||||
|
# remove mc source.
|
||||||
|
purge "${MC_BUILD_DIR}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
(cd "${MC_BUILD_DIR}" && go build -o "$C_PWD/mc")
|
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/disk{1...4}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
sleep 5
|
||||||
|
|
||||||
# remove mc source.
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
purge "${MC_BUILD_DIR}"
|
echo "server1 log:"
|
||||||
fi
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/disk{1...4}" > "${WORK_DIR}/server1.log" 2>&1 &
|
"${PWD}/mc" mb --with-versioning minio/bucket
|
||||||
pid=$!
|
|
||||||
disown $pid
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
for i in $(seq 1 4); do
|
||||||
echo "server1 log:"
|
"${PWD}/mc" cp /etc/hosts minio/bucket/testobj
|
||||||
cat "${WORK_DIR}/server1.log"
|
|
||||||
echo "FAILED"
|
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
"${PWD}/mc" mb --with-versioning minio/bucket
|
sudo chown -R root. "${WORK_DIR}/disk${i}"
|
||||||
|
|
||||||
for i in $(seq 1 4); do
|
"${PWD}/mc" cp /etc/hosts minio/bucket/testobj
|
||||||
"${PWD}/mc" cp /etc/hosts minio/bucket/testobj
|
|
||||||
|
|
||||||
sudo chown -R root. "${WORK_DIR}/disk${i}"
|
sudo chown -R ${USER}. "${WORK_DIR}/disk${i}"
|
||||||
|
done
|
||||||
|
|
||||||
"${PWD}/mc" cp /etc/hosts minio/bucket/testobj
|
for vid in $("${PWD}/mc" ls --json --versions minio/bucket/testobj | jq -r .versionId); do
|
||||||
|
"${PWD}/mc" cat --vid "${vid}" minio/bucket/testobj | md5sum
|
||||||
|
done
|
||||||
|
|
||||||
sudo chown -R ${USER}. "${WORK_DIR}/disk${i}"
|
pkill minio
|
||||||
done
|
sleep 3
|
||||||
|
|
||||||
for vid in $("${PWD}/mc" ls --json --versions minio/bucket/testobj | jq -r .versionId); do
|
|
||||||
"${PWD}/mc" cat --vid "${vid}" minio/bucket/testobj | md5sum
|
|
||||||
done
|
|
||||||
|
|
||||||
pkill minio
|
|
||||||
sleep 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
start_port=$(shuf -i 10000-65000 -n 1)
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
|
||||||
start_minio_4drive ${start_port}
|
start_minio_4drive ${start_port}
|
||||||
}
|
}
|
||||||
|
|
||||||
function purge()
|
function purge() {
|
||||||
{
|
rm -rf "$1"
|
||||||
rm -rf "$1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
( main "$@" )
|
(main "$@")
|
||||||
rv=$?
|
rv=$?
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit "$rv"
|
exit "$rv"
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -4,89 +4,89 @@ trap 'cleanup $LINENO' ERR
|
|||||||
|
|
||||||
# shellcheck disable=SC2120
|
# shellcheck disable=SC2120
|
||||||
cleanup() {
|
cleanup() {
|
||||||
MINIO_VERSION=dev docker-compose \
|
MINIO_VERSION=dev docker-compose \
|
||||||
-f "buildscripts/upgrade-tests/compose.yml" \
|
-f "buildscripts/upgrade-tests/compose.yml" \
|
||||||
rm -s -f
|
rm -s -f
|
||||||
docker volume prune -f
|
docker volume prune -f
|
||||||
}
|
}
|
||||||
|
|
||||||
verify_checksum_after_heal() {
|
verify_checksum_after_heal() {
|
||||||
local sum1
|
local sum1
|
||||||
sum1=$(curl -s "$2" | sha256sum);
|
sum1=$(curl -s "$2" | sha256sum)
|
||||||
mc admin heal --json -r "$1" >/dev/null; # test after healing
|
mc admin heal --json -r "$1" >/dev/null # test after healing
|
||||||
local sum1_heal
|
local sum1_heal
|
||||||
sum1_heal=$(curl -s "$2" | sha256sum);
|
sum1_heal=$(curl -s "$2" | sha256sum)
|
||||||
|
|
||||||
if [ "${sum1_heal}" != "${sum1}" ]; then
|
if [ "${sum1_heal}" != "${sum1}" ]; then
|
||||||
echo "mismatch expected ${sum1_heal}, got ${sum1}"
|
echo "mismatch expected ${sum1_heal}, got ${sum1}"
|
||||||
exit 1;
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
verify_checksum_mc() {
|
verify_checksum_mc() {
|
||||||
local expected
|
local expected
|
||||||
expected=$(mc cat "$1" | sha256sum)
|
expected=$(mc cat "$1" | sha256sum)
|
||||||
local got
|
local got
|
||||||
got=$(mc cat "$2" | sha256sum)
|
got=$(mc cat "$2" | sha256sum)
|
||||||
|
|
||||||
if [ "${expected}" != "${got}" ]; then
|
if [ "${expected}" != "${got}" ]; then
|
||||||
echo "mismatch - expected ${expected}, got ${got}"
|
echo "mismatch - expected ${expected}, got ${got}"
|
||||||
exit 1;
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "matches - ${expected}, got ${got}"
|
echo "matches - ${expected}, got ${got}"
|
||||||
}
|
}
|
||||||
|
|
||||||
add_alias() {
|
add_alias() {
|
||||||
for i in $(seq 1 4); do
|
for i in $(seq 1 4); do
|
||||||
echo "... attempting to add alias $i"
|
echo "... attempting to add alias $i"
|
||||||
until (mc alias set minio http://127.0.0.1:9000 minioadmin minioadmin); do
|
until (mc alias set minio http://127.0.0.1:9000 minioadmin minioadmin); do
|
||||||
echo "...waiting... for 5secs" && sleep 5
|
echo "...waiting... for 5secs" && sleep 5
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Sleeping for nginx"
|
echo "Sleeping for nginx"
|
||||||
sleep 20
|
sleep 20
|
||||||
}
|
}
|
||||||
|
|
||||||
__init__() {
|
__init__() {
|
||||||
sudo apt install curl -y
|
sudo apt install curl -y
|
||||||
export GOPATH=/tmp/gopath
|
export GOPATH=/tmp/gopath
|
||||||
export PATH=${PATH}:${GOPATH}/bin
|
export PATH=${PATH}:${GOPATH}/bin
|
||||||
|
|
||||||
go install github.com/minio/mc@latest
|
go install github.com/minio/mc@latest
|
||||||
|
|
||||||
TAG=minio/minio:dev make docker
|
TAG=minio/minio:dev make docker
|
||||||
|
|
||||||
MINIO_VERSION=RELEASE.2019-12-19T22-52-26Z docker-compose \
|
MINIO_VERSION=RELEASE.2019-12-19T22-52-26Z docker-compose \
|
||||||
-f "buildscripts/upgrade-tests/compose.yml" \
|
-f "buildscripts/upgrade-tests/compose.yml" \
|
||||||
up -d --build
|
up -d --build
|
||||||
|
|
||||||
add_alias
|
add_alias
|
||||||
|
|
||||||
mc mb minio/minio-test/
|
mc mb minio/minio-test/
|
||||||
mc cp ./minio minio/minio-test/to-read/
|
mc cp ./minio minio/minio-test/to-read/
|
||||||
mc cp /etc/hosts minio/minio-test/to-read/hosts
|
mc cp /etc/hosts minio/minio-test/to-read/hosts
|
||||||
mc anonymous set download minio/minio-test
|
mc anonymous set download minio/minio-test
|
||||||
|
|
||||||
verify_checksum_mc ./minio minio/minio-test/to-read/minio
|
verify_checksum_mc ./minio minio/minio-test/to-read/minio
|
||||||
|
|
||||||
curl -s http://127.0.0.1:9000/minio-test/to-read/hosts | sha256sum
|
curl -s http://127.0.0.1:9000/minio-test/to-read/hosts | sha256sum
|
||||||
|
|
||||||
MINIO_VERSION=dev docker-compose -f "buildscripts/upgrade-tests/compose.yml" stop
|
MINIO_VERSION=dev docker-compose -f "buildscripts/upgrade-tests/compose.yml" stop
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
MINIO_VERSION=dev docker-compose -f "buildscripts/upgrade-tests/compose.yml" up -d --build
|
MINIO_VERSION=dev docker-compose -f "buildscripts/upgrade-tests/compose.yml" up -d --build
|
||||||
|
|
||||||
add_alias
|
add_alias
|
||||||
|
|
||||||
verify_checksum_after_heal minio/minio-test http://127.0.0.1:9000/minio-test/to-read/hosts
|
verify_checksum_after_heal minio/minio-test http://127.0.0.1:9000/minio-test/to-read/hosts
|
||||||
|
|
||||||
verify_checksum_mc ./minio minio/minio-test/to-read/minio
|
verify_checksum_mc ./minio minio/minio-test/to-read/minio
|
||||||
|
|
||||||
verify_checksum_mc /etc/hosts minio/minio-test/to-read/hosts
|
verify_checksum_mc /etc/hosts minio/minio-test/to-read/hosts
|
||||||
|
|
||||||
cleanup
|
cleanup
|
||||||
}
|
}
|
||||||
|
|
||||||
( __init__ "$@" && main "$@" )
|
(__init__ "$@" && main "$@")
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ set -e
|
|||||||
export GORACE="history_size=7"
|
export GORACE="history_size=7"
|
||||||
export MINIO_API_REQUESTS_MAX=10000
|
export MINIO_API_REQUESTS_MAX=10000
|
||||||
|
|
||||||
## TODO remove `dsync` from race detector once this is merged and released https://go-review.googlesource.com/c/go/+/333529/
|
for d in $(go list ./...); do
|
||||||
for d in $(go list ./... | grep -v dsync); do
|
CGO_ENABLED=1 go test -v -race --timeout 100m "$d"
|
||||||
CGO_ENABLED=1 go test -v -race --timeout 100m "$d"
|
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -3,70 +3,70 @@
|
|||||||
set -E
|
set -E
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
set -x
|
set -x
|
||||||
|
set -e
|
||||||
|
|
||||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||||
|
|
||||||
if [ ! -x "$PWD/minio" ]; then
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
echo "minio executable binary not found in current directory"
|
echo "minio executable binary not found in current directory"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
function start_minio_5drive() {
|
function start_minio_5drive() {
|
||||||
start_port=$1
|
start_port=$1
|
||||||
|
|
||||||
export MINIO_ROOT_USER=minio
|
export MINIO_ROOT_USER=minio
|
||||||
export MINIO_ROOT_PASSWORD=minio123
|
export MINIO_ROOT_PASSWORD=minio123
|
||||||
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||||
export MINIO_CI_CD=1
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
MC_BUILD_DIR="mc-$RANDOM"
|
MC_BUILD_DIR="mc-$RANDOM"
|
||||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||||
echo "failed to download https://github.com/minio/mc"
|
echo "failed to download https://github.com/minio/mc"
|
||||||
|
purge "${MC_BUILD_DIR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||||
|
|
||||||
|
# remove mc source.
|
||||||
purge "${MC_BUILD_DIR}"
|
purge "${MC_BUILD_DIR}"
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
"${WORK_DIR}/mc" cp --quiet -r "buildscripts/cicd-corpus/" "${WORK_DIR}/cicd-corpus/"
|
||||||
|
|
||||||
# remove mc source.
|
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/cicd-corpus/disk{1...5}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
purge "${MC_BUILD_DIR}"
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
sleep 5
|
||||||
|
|
||||||
"${WORK_DIR}/mc" cp --quiet -r "buildscripts/cicd-corpus/" "${WORK_DIR}/cicd-corpus/"
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/cicd-corpus/disk{1...5}" > "${WORK_DIR}/server1.log" 2>&1 &
|
"${WORK_DIR}/mc" stat minio/bucket/testobj
|
||||||
pid=$!
|
|
||||||
disown $pid
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
pkill minio
|
||||||
echo "server1 log:"
|
sleep 3
|
||||||
cat "${WORK_DIR}/server1.log"
|
|
||||||
echo "FAILED"
|
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
"${WORK_DIR}/mc" stat minio/bucket/testobj
|
|
||||||
|
|
||||||
pkill minio
|
|
||||||
sleep 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
start_port=$(shuf -i 10000-65000 -n 1)
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
|
||||||
start_minio_5drive ${start_port}
|
start_minio_5drive ${start_port}
|
||||||
}
|
}
|
||||||
|
|
||||||
function purge()
|
function purge() {
|
||||||
{
|
rm -rf "$1"
|
||||||
rm -rf "$1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
( main "$@" )
|
(main "$@")
|
||||||
rv=$?
|
rv=$?
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit "$rv"
|
exit "$rv"
|
||||||
|
|||||||
+109
-104
@@ -6,146 +6,151 @@ set -x
|
|||||||
|
|
||||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
MINIO_OLD=( "$PWD/minio.RELEASE.2020-10-28T08-16-50Z" --config-dir "$MINIO_CONFIG_DIR" server )
|
MINIO_OLD=("$PWD/minio.RELEASE.2020-10-28T08-16-50Z" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||||
|
|
||||||
if [ ! -x "$PWD/minio" ]; then
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
echo "minio executable binary not found in current directory"
|
echo "minio executable binary not found in current directory"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
function download_old_release() {
|
function download_old_release() {
|
||||||
if [ ! -f minio.RELEASE.2020-10-28T08-16-50Z ]; then
|
if [ ! -f minio.RELEASE.2020-10-28T08-16-50Z ]; then
|
||||||
curl --silent -O https://dl.minio.io/server/minio/release/linux-amd64/archive/minio.RELEASE.2020-10-28T08-16-50Z
|
curl --silent -O https://dl.minio.io/server/minio/release/linux-amd64/archive/minio.RELEASE.2020-10-28T08-16-50Z
|
||||||
chmod a+x minio.RELEASE.2020-10-28T08-16-50Z
|
chmod a+x minio.RELEASE.2020-10-28T08-16-50Z
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function verify_rewrite() {
|
function verify_rewrite() {
|
||||||
start_port=$1
|
start_port=$1
|
||||||
|
|
||||||
export MINIO_ACCESS_KEY=minio
|
export MINIO_ACCESS_KEY=minio
|
||||||
export MINIO_SECRET_KEY=minio123
|
export MINIO_SECRET_KEY=minio123
|
||||||
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||||
export MINIO_CI_CD=1
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
MC_BUILD_DIR="mc-$RANDOM"
|
MC_BUILD_DIR="mc-$RANDOM"
|
||||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||||
echo "failed to download https://github.com/minio/mc"
|
echo "failed to download https://github.com/minio/mc"
|
||||||
|
purge "${MC_BUILD_DIR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||||
|
|
||||||
|
# remove mc source.
|
||||||
purge "${MC_BUILD_DIR}"
|
purge "${MC_BUILD_DIR}"
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
sleep 10
|
||||||
|
|
||||||
# remove mc source.
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
purge "${MC_BUILD_DIR}"
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" > "${WORK_DIR}/server1.log" 2>&1 &
|
"${WORK_DIR}/mc" mb minio/healing-rewrite-bucket --quiet --with-lock
|
||||||
pid=$!
|
"${WORK_DIR}/mc" cp \
|
||||||
disown $pid
|
buildscripts/verify-build.sh \
|
||||||
sleep 10
|
minio/healing-rewrite-bucket/ \
|
||||||
|
--disable-multipart --quiet
|
||||||
|
|
||||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
"${WORK_DIR}/mc" cp \
|
||||||
echo "server1 log:"
|
buildscripts/verify-build.sh \
|
||||||
cat "${WORK_DIR}/server1.log"
|
minio/healing-rewrite-bucket/ \
|
||||||
echo "FAILED"
|
--disable-multipart --quiet
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
"${WORK_DIR}/mc" mb minio/healing-rewrite-bucket --quiet --with-lock
|
"${WORK_DIR}/mc" cp \
|
||||||
"${WORK_DIR}/mc" cp \
|
buildscripts/verify-build.sh \
|
||||||
buildscripts/verify-build.sh \
|
minio/healing-rewrite-bucket/ \
|
||||||
minio/healing-rewrite-bucket/ \
|
--disable-multipart --quiet
|
||||||
--disable-multipart --quiet
|
|
||||||
|
|
||||||
"${WORK_DIR}/mc" cp \
|
kill ${pid}
|
||||||
buildscripts/verify-build.sh \
|
sleep 3
|
||||||
minio/healing-rewrite-bucket/ \
|
|
||||||
--disable-multipart --quiet
|
|
||||||
|
|
||||||
"${WORK_DIR}/mc" cp \
|
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
buildscripts/verify-build.sh \
|
pid=$!
|
||||||
minio/healing-rewrite-bucket/ \
|
disown $pid
|
||||||
--disable-multipart --quiet
|
sleep 10
|
||||||
|
|
||||||
kill ${pid}
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
sleep 3
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" > "${WORK_DIR}/server1.log" 2>&1 &
|
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
|
||||||
pid=$!
|
if ! s3-check-md5 \
|
||||||
disown $pid
|
-debug \
|
||||||
sleep 10
|
-versions \
|
||||||
|
-access-key minio \
|
||||||
|
-secret-key minio123 \
|
||||||
|
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
mkdir -p inspects
|
||||||
|
(
|
||||||
|
cd inspects
|
||||||
|
"${WORK_DIR}/mc" admin inspect minio/healing-rewrite-bucket/verify-build.sh/**
|
||||||
|
)
|
||||||
|
|
||||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
"${WORK_DIR}/mc" mb play/inspects
|
||||||
echo "server1 log:"
|
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||||
cat "${WORK_DIR}/server1.log"
|
|
||||||
echo "FAILED"
|
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
go build ./docs/debugging/s3-check-md5/
|
purge "$WORK_DIR"
|
||||||
if ! ./s3-check-md5 \
|
exit 1
|
||||||
-debug \
|
fi
|
||||||
-versions \
|
|
||||||
-access-key minio \
|
|
||||||
-secret-key minio123 \
|
|
||||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
|
||||||
echo "server1 log:"
|
|
||||||
cat "${WORK_DIR}/server1.log"
|
|
||||||
echo "FAILED"
|
|
||||||
mkdir -p inspects
|
|
||||||
(cd inspects; "${WORK_DIR}/mc" admin inspect minio/healing-rewrite-bucket/verify-build.sh/**)
|
|
||||||
|
|
||||||
"${WORK_DIR}/mc" mb play/inspects
|
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123"
|
||||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
sleep 1
|
||||||
|
|
||||||
purge "$WORK_DIR"
|
if ! s3-check-md5 \
|
||||||
exit 1
|
-debug \
|
||||||
fi
|
-versions \
|
||||||
|
-access-key minio \
|
||||||
|
-secret-key minio123 \
|
||||||
|
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
mkdir -p inspects
|
||||||
|
(
|
||||||
|
cd inspects
|
||||||
|
"${WORK_DIR}/mc" admin inspect minio/healing-rewrite-bucket/verify-build.sh/**
|
||||||
|
)
|
||||||
|
|
||||||
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123"
|
"${WORK_DIR}/mc" mb play/inspects
|
||||||
sleep 1
|
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||||
|
|
||||||
if ! ./s3-check-md5 \
|
purge "$WORK_DIR"
|
||||||
-debug \
|
exit 1
|
||||||
-versions \
|
fi
|
||||||
-access-key minio \
|
|
||||||
-secret-key minio123 \
|
|
||||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
|
||||||
echo "server1 log:"
|
|
||||||
cat "${WORK_DIR}/server1.log"
|
|
||||||
echo "FAILED"
|
|
||||||
mkdir -p inspects
|
|
||||||
(cd inspects; "${WORK_DIR}/mc" admin inspect minio/healing-rewrite-bucket/verify-build.sh/**)
|
|
||||||
|
|
||||||
"${WORK_DIR}/mc" mb play/inspects
|
kill ${pid}
|
||||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
|
||||||
|
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
kill ${pid}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
download_old_release
|
download_old_release
|
||||||
|
|
||||||
start_port=$(shuf -i 10000-65000 -n 1)
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
|
||||||
verify_rewrite ${start_port}
|
verify_rewrite ${start_port}
|
||||||
}
|
}
|
||||||
|
|
||||||
function purge()
|
function purge() {
|
||||||
{
|
rm -rf "$1"
|
||||||
rm -rf "$1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
( main "$@" )
|
(main "$@")
|
||||||
rv=$?
|
rv=$?
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit "$rv"
|
exit "$rv"
|
||||||
|
|||||||
+128
-123
@@ -6,167 +6,172 @@ set -o pipefail
|
|||||||
set -x
|
set -x
|
||||||
|
|
||||||
if [ ! -x "$PWD/minio" ]; then
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
echo "minio executable binary not found in current directory"
|
echo "minio executable binary not found in current directory"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
MINIO_OLD=( "$PWD/minio.RELEASE.2021-11-24T23-19-33Z" --config-dir "$MINIO_CONFIG_DIR" server )
|
MINIO_OLD=("$PWD/minio.RELEASE.2021-11-24T23-19-33Z" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||||
|
|
||||||
function download_old_release() {
|
function download_old_release() {
|
||||||
if [ ! -f minio.RELEASE.2021-11-24T23-19-33Z ]; then
|
if [ ! -f minio.RELEASE.2021-11-24T23-19-33Z ]; then
|
||||||
curl --silent -O https://dl.minio.io/server/minio/release/linux-amd64/archive/minio.RELEASE.2021-11-24T23-19-33Z
|
curl --silent -O https://dl.minio.io/server/minio/release/linux-amd64/archive/minio.RELEASE.2021-11-24T23-19-33Z
|
||||||
chmod a+x minio.RELEASE.2021-11-24T23-19-33Z
|
chmod a+x minio.RELEASE.2021-11-24T23-19-33Z
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_16drive() {
|
function start_minio_16drive() {
|
||||||
start_port=$1
|
start_port=$1
|
||||||
|
|
||||||
export MINIO_ROOT_USER=minio
|
export MINIO_ROOT_USER=minio
|
||||||
export MINIO_ROOT_PASSWORD=minio123
|
export MINIO_ROOT_PASSWORD=minio123
|
||||||
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||||
export _MINIO_SHARD_DISKTIME_DELTA="5s" # do not change this as its needed for tests
|
export _MINIO_SHARD_DISKTIME_DELTA="5s" # do not change this as its needed for tests
|
||||||
export MINIO_CI_CD=1
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
MC_BUILD_DIR="mc-$RANDOM"
|
MC_BUILD_DIR="mc-$RANDOM"
|
||||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||||
echo "failed to download https://github.com/minio/mc"
|
echo "failed to download https://github.com/minio/mc"
|
||||||
|
purge "${MC_BUILD_DIR}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||||
|
|
||||||
|
# remove mc source.
|
||||||
purge "${MC_BUILD_DIR}"
|
purge "${MC_BUILD_DIR}"
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
disown $pid
|
||||||
|
sleep 30
|
||||||
|
|
||||||
# remove mc source.
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
purge "${MC_BUILD_DIR}"
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" > "${WORK_DIR}/server1.log" 2>&1 &
|
shred --iterations=1 --size=5241856 - 1>"${WORK_DIR}/unaligned" 2>/dev/null
|
||||||
pid=$!
|
"${WORK_DIR}/mc" mb minio/healing-shard-bucket --quiet
|
||||||
disown $pid
|
"${WORK_DIR}/mc" cp \
|
||||||
sleep 30
|
"${WORK_DIR}/unaligned" \
|
||||||
|
minio/healing-shard-bucket/unaligned \
|
||||||
|
--disable-multipart --quiet
|
||||||
|
|
||||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
## "unaligned" object name gets consistently distributed
|
||||||
echo "server1 log:"
|
## to disks in following distribution order
|
||||||
cat "${WORK_DIR}/server1.log"
|
##
|
||||||
echo "FAILED"
|
## NOTE: if you change the name make sure to change the
|
||||||
purge "$WORK_DIR"
|
## distribution order present here
|
||||||
exit 1
|
##
|
||||||
fi
|
## [15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
|
||||||
|
|
||||||
shred --iterations=1 --size=5241856 - 1>"${WORK_DIR}/unaligned" 2>/dev/null
|
## make sure to remove the "last" data shard
|
||||||
"${WORK_DIR}/mc" mb minio/healing-shard-bucket --quiet
|
rm -rf "${WORK_DIR}/xl14/healing-shard-bucket/unaligned"
|
||||||
"${WORK_DIR}/mc" cp \
|
sleep 10
|
||||||
"${WORK_DIR}/unaligned" \
|
## Heal the shard
|
||||||
minio/healing-shard-bucket/unaligned \
|
"${WORK_DIR}/mc" admin heal --quiet --recursive minio/healing-shard-bucket
|
||||||
--disable-multipart --quiet
|
## then remove any other data shard let's pick first disk
|
||||||
|
## - 1st data shard.
|
||||||
|
rm -rf "${WORK_DIR}/xl3/healing-shard-bucket/unaligned"
|
||||||
|
sleep 10
|
||||||
|
|
||||||
## "unaligned" object name gets consistently distributed
|
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
|
||||||
## to disks in following distribution order
|
if ! s3-check-md5 \
|
||||||
##
|
-debug \
|
||||||
## NOTE: if you change the name make sure to change the
|
-access-key minio \
|
||||||
## distribution order present here
|
-secret-key minio123 \
|
||||||
##
|
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep CORRUPTED; then
|
||||||
## [15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
## make sure to remove the "last" data shard
|
pkill minio
|
||||||
rm -rf "${WORK_DIR}/xl14/healing-shard-bucket/unaligned"
|
sleep 3
|
||||||
sleep 10
|
|
||||||
## Heal the shard
|
|
||||||
"${WORK_DIR}/mc" admin heal --quiet --recursive minio/healing-shard-bucket
|
|
||||||
## then remove any other data shard let's pick first disk
|
|
||||||
## - 1st data shard.
|
|
||||||
rm -rf "${WORK_DIR}/xl3/healing-shard-bucket/unaligned"
|
|
||||||
sleep 10
|
|
||||||
|
|
||||||
go build ./docs/debugging/s3-check-md5/
|
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||||
if ! ./s3-check-md5 \
|
pid=$!
|
||||||
-debug \
|
disown $pid
|
||||||
-access-key minio \
|
sleep 30
|
||||||
-secret-key minio123 \
|
|
||||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep CORRUPTED; then
|
|
||||||
echo "server1 log:"
|
|
||||||
cat "${WORK_DIR}/server1.log"
|
|
||||||
echo "FAILED"
|
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
pkill minio
|
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||||
sleep 3
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" > "${WORK_DIR}/server1.log" 2>&1 &
|
if ! s3-check-md5 \
|
||||||
pid=$!
|
-debug \
|
||||||
disown $pid
|
-access-key minio \
|
||||||
sleep 30
|
-secret-key minio123 \
|
||||||
|
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
mkdir -p inspects
|
||||||
|
(
|
||||||
|
cd inspects
|
||||||
|
"${WORK_DIR}/mc" support inspect minio/healing-shard-bucket/unaligned/**
|
||||||
|
)
|
||||||
|
|
||||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
"${WORK_DIR}/mc" mb play/inspects
|
||||||
echo "server1 log:"
|
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||||
cat "${WORK_DIR}/server1.log"
|
|
||||||
echo "FAILED"
|
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! ./s3-check-md5 \
|
purge "$WORK_DIR"
|
||||||
-debug \
|
exit 1
|
||||||
-access-key minio \
|
fi
|
||||||
-secret-key minio123 \
|
|
||||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
|
||||||
echo "server1 log:"
|
|
||||||
cat "${WORK_DIR}/server1.log"
|
|
||||||
echo "FAILED"
|
|
||||||
mkdir -p inspects
|
|
||||||
(cd inspects; "${WORK_DIR}/mc" support inspect minio/healing-shard-bucket/unaligned/**)
|
|
||||||
|
|
||||||
"${WORK_DIR}/mc" mb play/inspects
|
"${WORK_DIR}/mc" admin heal --quiet --recursive minio/healing-shard-bucket
|
||||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
|
||||||
|
|
||||||
purge "$WORK_DIR"
|
if ! s3-check-md5 \
|
||||||
exit 1
|
-debug \
|
||||||
fi
|
-access-key minio \
|
||||||
|
-secret-key minio123 \
|
||||||
|
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
mkdir -p inspects
|
||||||
|
(
|
||||||
|
cd inspects
|
||||||
|
"${WORK_DIR}/mc" support inspect minio/healing-shard-bucket/unaligned/**
|
||||||
|
)
|
||||||
|
|
||||||
"${WORK_DIR}/mc" admin heal --quiet --recursive minio/healing-shard-bucket
|
"${WORK_DIR}/mc" mb play/inspects
|
||||||
|
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||||
|
|
||||||
if ! ./s3-check-md5 \
|
purge "$WORK_DIR"
|
||||||
-debug \
|
exit 1
|
||||||
-access-key minio \
|
fi
|
||||||
-secret-key minio123 \
|
|
||||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
|
||||||
echo "server1 log:"
|
|
||||||
cat "${WORK_DIR}/server1.log"
|
|
||||||
echo "FAILED"
|
|
||||||
mkdir -p inspects
|
|
||||||
(cd inspects; "${WORK_DIR}/mc" support inspect minio/healing-shard-bucket/unaligned/**)
|
|
||||||
|
|
||||||
"${WORK_DIR}/mc" mb play/inspects
|
pkill minio
|
||||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
sleep 3
|
||||||
|
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
pkill minio
|
|
||||||
sleep 3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
download_old_release
|
download_old_release
|
||||||
|
|
||||||
start_port=$(shuf -i 10000-65000 -n 1)
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
|
|
||||||
start_minio_16drive ${start_port}
|
start_minio_16drive ${start_port}
|
||||||
}
|
}
|
||||||
|
|
||||||
function purge()
|
function purge() {
|
||||||
{
|
rm -rf "$1"
|
||||||
rm -rf "$1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
( main "$@" )
|
(main "$@")
|
||||||
rv=$?
|
rv=$?
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit "$rv"
|
exit "$rv"
|
||||||
|
|||||||
+186
-201
@@ -6,8 +6,8 @@ set -E
|
|||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
if [ ! -x "$PWD/minio" ]; then
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
echo "minio executable binary not found in current directory"
|
echo "minio executable binary not found in current directory"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
@@ -25,285 +25,270 @@ export ENABLE_ADMIN=1
|
|||||||
export MINIO_CI_CD=1
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" )
|
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR")
|
||||||
|
|
||||||
FILE_1_MB="$MINT_DATA_DIR/datafile-1-MB"
|
FILE_1_MB="$MINT_DATA_DIR/datafile-1-MB"
|
||||||
FILE_65_MB="$MINT_DATA_DIR/datafile-65-MB"
|
FILE_65_MB="$MINT_DATA_DIR/datafile-65-MB"
|
||||||
|
|
||||||
FUNCTIONAL_TESTS="$WORK_DIR/functional-tests.sh"
|
FUNCTIONAL_TESTS="$WORK_DIR/functional-tests.sh"
|
||||||
|
|
||||||
function start_minio_fs()
|
function start_minio_fs() {
|
||||||
{
|
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
"${MINIO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-minio.log" 2>&1 &
|
||||||
"${MINIO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-minio.log" 2>&1 &
|
sleep 10
|
||||||
sleep 10
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_erasure()
|
function start_minio_erasure() {
|
||||||
{
|
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-minio.log" 2>&1 &
|
||||||
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-minio.log" 2>&1 &
|
sleep 15
|
||||||
sleep 15
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_erasure_sets()
|
function start_minio_erasure_sets() {
|
||||||
{
|
export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
|
||||||
export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
|
"${MINIO[@]}" server >"$WORK_DIR/erasure-minio-sets.log" 2>&1 &
|
||||||
"${MINIO[@]}" server > "$WORK_DIR/erasure-minio-sets.log" 2>&1 &
|
sleep 15
|
||||||
sleep 15
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_pool_erasure_sets()
|
function start_minio_pool_erasure_sets() {
|
||||||
{
|
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/pool-disk-sets{1...4} http://127.0.0.1:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
||||||
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/pool-disk-sets{1...4} http://127.0.0.1:9001${WORK_DIR}/pool-disk-sets{5...8}"
|
"${MINIO[@]}" server --address ":9000" >"$WORK_DIR/pool-minio-9000.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address ":9000" > "$WORK_DIR/pool-minio-9000.log" 2>&1 &
|
"${MINIO[@]}" server --address ":9001" >"$WORK_DIR/pool-minio-9001.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address ":9001" > "$WORK_DIR/pool-minio-9001.log" 2>&1 &
|
|
||||||
|
|
||||||
sleep 40
|
sleep 40
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_pool_erasure_sets_ipv6()
|
function start_minio_pool_erasure_sets_ipv6() {
|
||||||
{
|
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
export MINIO_ENDPOINTS="http://[::1]:9000${WORK_DIR}/pool-disk-sets-ipv6{1...4} http://[::1]:9001${WORK_DIR}/pool-disk-sets-ipv6{5...8}"
|
||||||
export MINIO_ENDPOINTS="http://[::1]:9000${WORK_DIR}/pool-disk-sets-ipv6{1...4} http://[::1]:9001${WORK_DIR}/pool-disk-sets-ipv6{5...8}"
|
"${MINIO[@]}" server --address="[::1]:9000" >"$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address="[::1]:9000" > "$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
|
"${MINIO[@]}" server --address="[::1]:9001" >"$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address="[::1]:9001" > "$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
|
|
||||||
|
|
||||||
sleep 40
|
sleep 40
|
||||||
}
|
}
|
||||||
|
|
||||||
function start_minio_dist_erasure()
|
function start_minio_dist_erasure() {
|
||||||
{
|
export MINIO_ROOT_USER=$ACCESS_KEY
|
||||||
export MINIO_ROOT_USER=$ACCESS_KEY
|
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
||||||
export MINIO_ROOT_PASSWORD=$SECRET_KEY
|
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/dist-disk1 http://127.0.0.1:9001${WORK_DIR}/dist-disk2 http://127.0.0.1:9002${WORK_DIR}/dist-disk3 http://127.0.0.1:9003${WORK_DIR}/dist-disk4"
|
||||||
export MINIO_ENDPOINTS="http://127.0.0.1:9000${WORK_DIR}/dist-disk1 http://127.0.0.1:9001${WORK_DIR}/dist-disk2 http://127.0.0.1:9002${WORK_DIR}/dist-disk3 http://127.0.0.1:9003${WORK_DIR}/dist-disk4"
|
for i in $(seq 0 3); do
|
||||||
for i in $(seq 0 3); do
|
"${MINIO[@]}" server --address ":900${i}" >"$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
|
||||||
"${MINIO[@]}" server --address ":900${i}" > "$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
|
done
|
||||||
done
|
|
||||||
|
|
||||||
sleep 40
|
sleep 40
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_fs()
|
function run_test_fs() {
|
||||||
{
|
start_minio_fs
|
||||||
start_minio_fs
|
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
pkill minio
|
pkill minio
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
cat "$WORK_DIR/fs-minio.log"
|
cat "$WORK_DIR/fs-minio.log"
|
||||||
fi
|
fi
|
||||||
rm -f "$WORK_DIR/fs-minio.log"
|
rm -f "$WORK_DIR/fs-minio.log"
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_erasure_sets()
|
function run_test_erasure_sets() {
|
||||||
{
|
start_minio_erasure_sets
|
||||||
start_minio_erasure_sets
|
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
pkill minio
|
pkill minio
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
cat "$WORK_DIR/erasure-minio-sets.log"
|
cat "$WORK_DIR/erasure-minio-sets.log"
|
||||||
fi
|
fi
|
||||||
rm -f "$WORK_DIR/erasure-minio-sets.log"
|
rm -f "$WORK_DIR/erasure-minio-sets.log"
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_pool_erasure_sets()
|
function run_test_pool_erasure_sets() {
|
||||||
{
|
start_minio_pool_erasure_sets
|
||||||
start_minio_pool_erasure_sets
|
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
pkill minio
|
pkill minio
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
echo "server$i log:"
|
echo "server$i log:"
|
||||||
cat "$WORK_DIR/pool-minio-900$i.log"
|
cat "$WORK_DIR/pool-minio-900$i.log"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
rm -f "$WORK_DIR/pool-minio-900$i.log"
|
rm -f "$WORK_DIR/pool-minio-900$i.log"
|
||||||
done
|
done
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_pool_erasure_sets_ipv6()
|
function run_test_pool_erasure_sets_ipv6() {
|
||||||
{
|
start_minio_pool_erasure_sets_ipv6
|
||||||
start_minio_pool_erasure_sets_ipv6
|
|
||||||
|
|
||||||
export SERVER_ENDPOINT="[::1]:9000"
|
export SERVER_ENDPOINT="[::1]:9000"
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
pkill minio
|
pkill minio
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
echo "server$i log:"
|
echo "server$i log:"
|
||||||
cat "$WORK_DIR/pool-minio-ipv6-900$i.log"
|
cat "$WORK_DIR/pool-minio-ipv6-900$i.log"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
for i in $(seq 0 1); do
|
for i in $(seq 0 1); do
|
||||||
rm -f "$WORK_DIR/pool-minio-ipv6-900$i.log"
|
rm -f "$WORK_DIR/pool-minio-ipv6-900$i.log"
|
||||||
done
|
done
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_erasure()
|
function run_test_erasure() {
|
||||||
{
|
start_minio_erasure
|
||||||
start_minio_erasure
|
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
pkill minio
|
pkill minio
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
cat "$WORK_DIR/erasure-minio.log"
|
cat "$WORK_DIR/erasure-minio.log"
|
||||||
fi
|
fi
|
||||||
rm -f "$WORK_DIR/erasure-minio.log"
|
rm -f "$WORK_DIR/erasure-minio.log"
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test_dist_erasure()
|
function run_test_dist_erasure() {
|
||||||
{
|
start_minio_dist_erasure
|
||||||
start_minio_dist_erasure
|
|
||||||
|
|
||||||
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
(cd "$WORK_DIR" && "$FUNCTIONAL_TESTS")
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
pkill minio
|
pkill minio
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if [ "$rv" -ne 0 ]; then
|
if [ "$rv" -ne 0 ]; then
|
||||||
echo "server1 log:"
|
echo "server1 log:"
|
||||||
cat "$WORK_DIR/dist-minio-9000.log"
|
cat "$WORK_DIR/dist-minio-9000.log"
|
||||||
echo "server2 log:"
|
echo "server2 log:"
|
||||||
cat "$WORK_DIR/dist-minio-9001.log"
|
cat "$WORK_DIR/dist-minio-9001.log"
|
||||||
echo "server3 log:"
|
echo "server3 log:"
|
||||||
cat "$WORK_DIR/dist-minio-9002.log"
|
cat "$WORK_DIR/dist-minio-9002.log"
|
||||||
echo "server4 log:"
|
echo "server4 log:"
|
||||||
cat "$WORK_DIR/dist-minio-9003.log"
|
cat "$WORK_DIR/dist-minio-9003.log"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "$WORK_DIR/dist-minio-9000.log" "$WORK_DIR/dist-minio-9001.log" "$WORK_DIR/dist-minio-9002.log" "$WORK_DIR/dist-minio-9003.log"
|
rm -f "$WORK_DIR/dist-minio-9000.log" "$WORK_DIR/dist-minio-9001.log" "$WORK_DIR/dist-minio-9002.log" "$WORK_DIR/dist-minio-9003.log"
|
||||||
|
|
||||||
return "$rv"
|
return "$rv"
|
||||||
}
|
}
|
||||||
|
|
||||||
function purge()
|
function purge() {
|
||||||
{
|
rm -rf "$1"
|
||||||
rm -rf "$1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function __init__()
|
function __init__() {
|
||||||
{
|
echo "Initializing environment"
|
||||||
echo "Initializing environment"
|
mkdir -p "$WORK_DIR"
|
||||||
mkdir -p "$WORK_DIR"
|
mkdir -p "$MINIO_CONFIG_DIR"
|
||||||
mkdir -p "$MINIO_CONFIG_DIR"
|
mkdir -p "$MINT_DATA_DIR"
|
||||||
mkdir -p "$MINT_DATA_DIR"
|
|
||||||
|
|
||||||
MC_BUILD_DIR="mc-$RANDOM"
|
MC_BUILD_DIR="mc-$RANDOM"
|
||||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||||
echo "failed to download https://github.com/minio/mc"
|
echo "failed to download https://github.com/minio/mc"
|
||||||
purge "${MC_BUILD_DIR}"
|
purge "${MC_BUILD_DIR}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||||
|
|
||||||
# remove mc source.
|
# remove mc source.
|
||||||
purge "${MC_BUILD_DIR}"
|
purge "${MC_BUILD_DIR}"
|
||||||
|
|
||||||
shred -n 1 -s 1M - 1>"$FILE_1_MB" 2>/dev/null
|
shred -n 1 -s 1M - 1>"$FILE_1_MB" 2>/dev/null
|
||||||
shred -n 1 -s 65M - 1>"$FILE_65_MB" 2>/dev/null
|
shred -n 1 -s 65M - 1>"$FILE_65_MB" 2>/dev/null
|
||||||
|
|
||||||
## version is purposefully set to '3' for minio to migrate configuration file
|
## version is purposefully set to '3' for minio to migrate configuration file
|
||||||
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' > "$MINIO_CONFIG_DIR/config.json"
|
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' >"$MINIO_CONFIG_DIR/config.json"
|
||||||
|
|
||||||
if ! wget -q -O "$FUNCTIONAL_TESTS" https://raw.githubusercontent.com/minio/mc/master/functional-tests.sh; then
|
if ! wget -q -O "$FUNCTIONAL_TESTS" https://raw.githubusercontent.com/minio/mc/master/functional-tests.sh; then
|
||||||
echo "failed to download https://raw.githubusercontent.com/minio/mc/master/functional-tests.sh"
|
echo "failed to download https://raw.githubusercontent.com/minio/mc/master/functional-tests.sh"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sed -i 's|-sS|-sSg|g' "$FUNCTIONAL_TESTS"
|
sed -i 's|-sS|-sSg|g' "$FUNCTIONAL_TESTS"
|
||||||
chmod a+x "$FUNCTIONAL_TESTS"
|
chmod a+x "$FUNCTIONAL_TESTS"
|
||||||
}
|
}
|
||||||
|
|
||||||
function main()
|
function main() {
|
||||||
{
|
echo "Testing in FS setup"
|
||||||
echo "Testing in FS setup"
|
if ! run_test_fs; then
|
||||||
if ! run_test_fs; then
|
echo "FAILED"
|
||||||
echo "FAILED"
|
purge "$WORK_DIR"
|
||||||
purge "$WORK_DIR"
|
exit 1
|
||||||
exit 1
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Testing in Erasure setup"
|
echo "Testing in Erasure setup"
|
||||||
if ! run_test_erasure; then
|
if ! run_test_erasure; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing in Distributed Erasure setup"
|
echo "Testing in Distributed Erasure setup"
|
||||||
if ! run_test_dist_erasure; then
|
if ! run_test_dist_erasure; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing in Erasure setup as sets"
|
echo "Testing in Erasure setup as sets"
|
||||||
if ! run_test_erasure_sets; then
|
if ! run_test_erasure_sets; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing in Distributed Eraure expanded setup"
|
echo "Testing in Distributed Eraure expanded setup"
|
||||||
if ! run_test_pool_erasure_sets; then
|
if ! run_test_pool_erasure_sets; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing in Distributed Erasure expanded setup with ipv6"
|
echo "Testing in Distributed Erasure expanded setup with ipv6"
|
||||||
if ! run_test_pool_erasure_sets_ipv6; then
|
if ! run_test_pool_erasure_sets_ipv6; then
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
( __init__ "$@" && main "$@" )
|
(__init__ "$@" && main "$@")
|
||||||
rv=$?
|
rv=$?
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit "$rv"
|
exit "$rv"
|
||||||
|
|||||||
@@ -4,94 +4,93 @@ set -E
|
|||||||
set -o pipefail
|
set -o pipefail
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
|
|
||||||
if [ ! -x "$PWD/minio" ]; then
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
echo "minio executable binary not found in current directory"
|
echo "minio executable binary not found in current directory"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
WORK_DIR="$(mktemp -d)"
|
WORK_DIR="$(mktemp -d)"
|
||||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||||
|
|
||||||
|
|
||||||
function start_minio() {
|
function start_minio() {
|
||||||
start_port=$1
|
start_port=$1
|
||||||
|
|
||||||
export MINIO_ROOT_USER=minio
|
export MINIO_ROOT_USER=minio
|
||||||
export MINIO_ROOT_PASSWORD=minio123
|
export MINIO_ROOT_PASSWORD=minio123
|
||||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||||
unset MINIO_CI_CD
|
unset MINIO_CI_CD
|
||||||
unset CI
|
unset CI
|
||||||
|
|
||||||
args=()
|
args=()
|
||||||
for i in $(seq 1 4); do
|
for i in $(seq 1 4); do
|
||||||
args+=("http://localhost:$[${start_port}+$i]${WORK_DIR}/mnt/disk$i/ ")
|
args+=("http://localhost:$((start_port + i))${WORK_DIR}/mnt/disk$i/ ")
|
||||||
done
|
done
|
||||||
|
|
||||||
for i in $(seq 1 4); do
|
for i in $(seq 1 4); do
|
||||||
"${MINIO[@]}" --address ":$[$start_port+$i]" ${args[@]} 2>&1 >"${WORK_DIR}/server$i.log" &
|
"${MINIO[@]}" --address ":$((start_port + i))" ${args[@]} 2>&1 >"${WORK_DIR}/server$i.log" &
|
||||||
done
|
done
|
||||||
|
|
||||||
# Wait until all nodes return 403
|
# Wait until all nodes return 403
|
||||||
for i in $(seq 1 4); do
|
for i in $(seq 1 4); do
|
||||||
while [ "$(curl -m 1 -s -o /dev/null -w "%{http_code}" http://localhost:$[$start_port+$i])" -ne "403" ]; do
|
while [ "$(curl -m 1 -s -o /dev/null -w "%{http_code}" http://localhost:$((start_port + i)))" -ne "403" ]; do
|
||||||
echo -n ".";
|
echo -n "."
|
||||||
sleep 1;
|
sleep 1
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Prepare fake disks with losetup
|
# Prepare fake disks with losetup
|
||||||
function prepare_block_devices() {
|
function prepare_block_devices() {
|
||||||
mkdir -p ${WORK_DIR}/disks/ ${WORK_DIR}/mnt/
|
set -e
|
||||||
for i in 1 2 3 4; do
|
mkdir -p ${WORK_DIR}/disks/ ${WORK_DIR}/mnt/
|
||||||
dd if=/dev/zero of=${WORK_DIR}/disks/img.$i bs=1M count=2048
|
sudo modprobe loop
|
||||||
mkfs.ext4 -F ${WORK_DIR}/disks/img.$i
|
for i in 1 2 3 4; do
|
||||||
sudo mknod /dev/minio-loopdisk$i b 7 $[256-$i]
|
dd if=/dev/zero of=${WORK_DIR}/disks/img.${i} bs=1M count=2000
|
||||||
sudo losetup /dev/minio-loopdisk$i ${WORK_DIR}/disks/img.$i
|
device=$(sudo losetup --find --show ${WORK_DIR}/disks/img.${i})
|
||||||
mkdir -p ${WORK_DIR}/mnt/disk$i/
|
sudo mkfs.ext4 -F ${device}
|
||||||
sudo mount /dev/minio-loopdisk$i ${WORK_DIR}/mnt/disk$i/
|
mkdir -p ${WORK_DIR}/mnt/disk${i}/
|
||||||
sudo chown "$(id -u):$(id -g)" /dev/minio-loopdisk$i ${WORK_DIR}/mnt/disk$i/
|
sudo mount ${device} ${WORK_DIR}/mnt/disk${i}/
|
||||||
done
|
sudo chown "$(id -u):$(id -g)" ${device} ${WORK_DIR}/mnt/disk${i}/
|
||||||
|
done
|
||||||
|
set +e
|
||||||
}
|
}
|
||||||
|
|
||||||
# Start a distributed MinIO setup, unmount one disk and check if it is formatted
|
# Start a distributed MinIO setup, unmount one disk and check if it is formatted
|
||||||
function main() {
|
function main() {
|
||||||
start_port=$(shuf -i 10000-65000 -n 1)
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
start_minio ${start_port}
|
start_minio ${start_port}
|
||||||
|
|
||||||
# Unmount the disk, after the unmount the device id
|
# Unmount the disk, after the unmount the device id
|
||||||
# /tmp/xxx/mnt/disk4 will be the same as '/' and it
|
# /tmp/xxx/mnt/disk4 will be the same as '/' and it
|
||||||
# will be detected as root disk
|
# will be detected as root disk
|
||||||
while [ "$u" != "0" ]; do
|
while [ "$u" != "0" ]; do
|
||||||
sudo umount ${WORK_DIR}/mnt/disk4/
|
sudo umount ${WORK_DIR}/mnt/disk4/
|
||||||
u=$?
|
u=$?
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
# Wait until MinIO self heal kicks in
|
# Wait until MinIO self heal kicks in
|
||||||
sleep 60
|
sleep 60
|
||||||
|
|
||||||
if [ -f ${WORK_DIR}/mnt/disk4/.minio.sys/format.json ]; then
|
if [ -f ${WORK_DIR}/mnt/disk4/.minio.sys/format.json ]; then
|
||||||
echo "A root disk is formatted unexpectedely"
|
echo "A root disk is formatted unexpectedely"
|
||||||
cat "${WORK_DIR}/server4.log"
|
cat "${WORK_DIR}/server4.log"
|
||||||
exit -1
|
exit -1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
pkill minio
|
pkill minio
|
||||||
sudo umount ${WORK_DIR}/mnt/disk{1..3}/
|
sudo umount ${WORK_DIR}/mnt/disk{1..3}/
|
||||||
sudo rm /dev/minio-loopdisk*
|
sudo rm /dev/minio-loopdisk*
|
||||||
rm -rf "$WORK_DIR"
|
rm -rf "$WORK_DIR"
|
||||||
}
|
}
|
||||||
|
|
||||||
( prepare_block_devices )
|
(prepare_block_devices)
|
||||||
( main "$@" )
|
(main "$@")
|
||||||
rv=$?
|
rv=$?
|
||||||
|
|
||||||
cleanup
|
cleanup
|
||||||
exit "$rv"
|
exit "$rv"
|
||||||
|
|
||||||
|
|||||||
+98
-102
@@ -5,139 +5,135 @@ set -E
|
|||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
if [ ! -x "$PWD/minio" ]; then
|
if [ ! -x "$PWD/minio" ]; then
|
||||||
echo "minio executable binary not found in current directory"
|
echo "minio executable binary not found in current directory"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||||
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
|
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||||
|
|
||||||
function start_minio_3_node() {
|
function start_minio_3_node() {
|
||||||
export MINIO_ROOT_USER=minio
|
export MINIO_ROOT_USER=minio
|
||||||
export MINIO_ROOT_PASSWORD=minio123
|
export MINIO_ROOT_PASSWORD=minio123
|
||||||
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
export MINIO_ERASURE_SET_DRIVE_COUNT=6
|
||||||
export MINIO_CI_CD=1
|
export MINIO_CI_CD=1
|
||||||
|
|
||||||
start_port=$2
|
start_port=$2
|
||||||
args=""
|
args=""
|
||||||
for i in $(seq 1 3); do
|
|
||||||
args="$args http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/1/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/2/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/3/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/4/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/5/ http://127.0.0.1:$[$start_port+$i]${WORK_DIR}/$i/6/"
|
|
||||||
done
|
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$[$start_port+1]" $args > "${WORK_DIR}/dist-minio-server1.log" 2>&1 &
|
|
||||||
pid1=$!
|
|
||||||
disown ${pid1}
|
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$[$start_port+2]" $args > "${WORK_DIR}/dist-minio-server2.log" 2>&1 &
|
|
||||||
pid2=$!
|
|
||||||
disown $pid2
|
|
||||||
|
|
||||||
"${MINIO[@]}" --address ":$[$start_port+3]" $args > "${WORK_DIR}/dist-minio-server3.log" 2>&1 &
|
|
||||||
pid3=$!
|
|
||||||
disown $pid3
|
|
||||||
|
|
||||||
sleep "$1"
|
|
||||||
|
|
||||||
if ! ps -p $pid1 1>&2 > /dev/null; then
|
|
||||||
echo "server1 log:"
|
|
||||||
cat "${WORK_DIR}/dist-minio-server1.log"
|
|
||||||
echo "FAILED"
|
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! ps -p $pid2 1>&2 > /dev/null; then
|
|
||||||
echo "server2 log:"
|
|
||||||
cat "${WORK_DIR}/dist-minio-server2.log"
|
|
||||||
echo "FAILED"
|
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! ps -p $pid3 1>&2 > /dev/null; then
|
|
||||||
echo "server3 log:"
|
|
||||||
cat "${WORK_DIR}/dist-minio-server3.log"
|
|
||||||
echo "FAILED"
|
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! pkill minio; then
|
|
||||||
for i in $(seq 1 3); do
|
for i in $(seq 1 3); do
|
||||||
echo "server$i log:"
|
args="$args http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/1/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/2/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/3/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/4/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/5/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/6/"
|
||||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
|
||||||
done
|
done
|
||||||
echo "FAILED"
|
|
||||||
purge "$WORK_DIR"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
sleep 1;
|
"${MINIO[@]}" --address ":$((start_port + 1))" $args >"${WORK_DIR}/dist-minio-server1.log" 2>&1 &
|
||||||
if pgrep minio; then
|
pid1=$!
|
||||||
# forcibly killing, to proceed further properly.
|
disown ${pid1}
|
||||||
if ! pkill -9 minio; then
|
|
||||||
echo "no minio process running anymore, proceed."
|
"${MINIO[@]}" --address ":$((start_port + 2))" $args >"${WORK_DIR}/dist-minio-server2.log" 2>&1 &
|
||||||
|
pid2=$!
|
||||||
|
disown $pid2
|
||||||
|
|
||||||
|
"${MINIO[@]}" --address ":$((start_port + 3))" $args >"${WORK_DIR}/dist-minio-server3.log" 2>&1 &
|
||||||
|
pid3=$!
|
||||||
|
disown $pid3
|
||||||
|
|
||||||
|
sleep "$1"
|
||||||
|
|
||||||
|
if ! ps -p $pid1 1>&2 >/dev/null; then
|
||||||
|
echo "server1 log:"
|
||||||
|
cat "${WORK_DIR}/dist-minio-server1.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if ! ps -p $pid2 1>&2 >/dev/null; then
|
||||||
|
echo "server2 log:"
|
||||||
|
cat "${WORK_DIR}/dist-minio-server2.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! ps -p $pid3 1>&2 >/dev/null; then
|
||||||
|
echo "server3 log:"
|
||||||
|
cat "${WORK_DIR}/dist-minio-server3.log"
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! pkill minio; then
|
||||||
|
for i in $(seq 1 3); do
|
||||||
|
echo "server$i log:"
|
||||||
|
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||||
|
done
|
||||||
|
echo "FAILED"
|
||||||
|
purge "$WORK_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
if pgrep minio; then
|
||||||
|
# forcibly killing, to proceed further properly.
|
||||||
|
if ! pkill -9 minio; then
|
||||||
|
echo "no minio process running anymore, proceed."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
function check_online() {
|
function check_online() {
|
||||||
if grep -q 'Unable to initialize sub-systems' ${WORK_DIR}/dist-minio-*.log; then
|
if grep -q 'Unable to initialize sub-systems' ${WORK_DIR}/dist-minio-*.log; then
|
||||||
echo "1"
|
echo "1"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function purge()
|
function purge() {
|
||||||
{
|
rm -rf "$1"
|
||||||
rm -rf "$1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function __init__()
|
function __init__() {
|
||||||
{
|
echo "Initializing environment"
|
||||||
echo "Initializing environment"
|
mkdir -p "$WORK_DIR"
|
||||||
mkdir -p "$WORK_DIR"
|
mkdir -p "$MINIO_CONFIG_DIR"
|
||||||
mkdir -p "$MINIO_CONFIG_DIR"
|
|
||||||
|
|
||||||
## version is purposefully set to '3' for minio to migrate configuration file
|
## version is purposefully set to '3' for minio to migrate configuration file
|
||||||
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' > "$MINIO_CONFIG_DIR/config.json"
|
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' >"$MINIO_CONFIG_DIR/config.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
function perform_test() {
|
function perform_test() {
|
||||||
start_minio_3_node 120 $2
|
start_minio_3_node 120 $2
|
||||||
|
|
||||||
echo "Testing Distributed Erasure setup healing of drives"
|
echo "Testing Distributed Erasure setup healing of drives"
|
||||||
echo "Remove the contents of the disks belonging to '${1}' erasure set"
|
echo "Remove the contents of the disks belonging to '${1}' erasure set"
|
||||||
|
|
||||||
rm -rf ${WORK_DIR}/${1}/*/
|
rm -rf ${WORK_DIR}/${1}/*/
|
||||||
|
|
||||||
start_minio_3_node 120 $2
|
start_minio_3_node 120 $2
|
||||||
|
|
||||||
rv=$(check_online)
|
rv=$(check_online)
|
||||||
if [ "$rv" == "1" ]; then
|
if [ "$rv" == "1" ]; then
|
||||||
for i in $(seq 1 3); do
|
for i in $(seq 1 3); do
|
||||||
echo "server$i log:"
|
echo "server$i log:"
|
||||||
cat "${WORK_DIR}/dist-minio-server$i.log"
|
cat "${WORK_DIR}/dist-minio-server$i.log"
|
||||||
done
|
done
|
||||||
pkill -9 minio
|
pkill -9 minio
|
||||||
echo "FAILED"
|
echo "FAILED"
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function main()
|
function main() {
|
||||||
{
|
# use same ports for all tests
|
||||||
# use same ports for all tests
|
start_port=$(shuf -i 10000-65000 -n 1)
|
||||||
start_port=$(shuf -i 10000-65000 -n 1)
|
|
||||||
|
|
||||||
perform_test "2" ${start_port}
|
perform_test "2" ${start_port}
|
||||||
perform_test "1" ${start_port}
|
perform_test "1" ${start_port}
|
||||||
perform_test "3" ${start_port}
|
perform_test "3" ${start_port}
|
||||||
}
|
}
|
||||||
|
|
||||||
( __init__ "$@" && main "$@" )
|
(__init__ "$@" && main "$@")
|
||||||
rv=$?
|
rv=$?
|
||||||
purge "$WORK_DIR"
|
purge "$WORK_DIR"
|
||||||
exit "$rv"
|
exit "$rv"
|
||||||
|
|||||||
+4
-4
@@ -22,10 +22,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/mux"
|
||||||
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Data types used for returning dummy access control
|
// Data types used for returning dummy access control
|
||||||
@@ -90,8 +90,8 @@ func (api objectAPIHandlers) PutBucketACLHandler(w http.ResponseWriter, r *http.
|
|||||||
if aclHeader == "" {
|
if aclHeader == "" {
|
||||||
acl := &accessControlPolicy{}
|
acl := &accessControlPolicy{}
|
||||||
if err = xmlDecoder(r.Body, acl, r.ContentLength); err != nil {
|
if err = xmlDecoder(r.Body, acl, r.ContentLength); err != nil {
|
||||||
if err == io.EOF {
|
if terr, ok := err.(*xml.SyntaxError); ok && terr.Msg == io.EOF.Error() {
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingSecurityHeader),
|
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedXML),
|
||||||
r.URL)
|
r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+208
-266
@@ -29,11 +29,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/klauspost/compress/zip"
|
"github.com/klauspost/compress/zip"
|
||||||
"github.com/minio/kes"
|
"github.com/minio/kes-go"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
||||||
@@ -41,8 +40,8 @@ import (
|
|||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -56,11 +55,9 @@ const (
|
|||||||
// specified in the quota configuration will be applied by default
|
// specified in the quota configuration will be applied by default
|
||||||
// to enforce total quota for the specified bucket.
|
// to enforce total quota for the specified bucket.
|
||||||
func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "PutBucketQuotaConfig")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SetBucketQuotaAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketQuotaAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -85,11 +82,6 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if quotaConfig.Type == "fifo" {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketQuotaConfigFile, data)
|
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketQuotaConfigFile, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
@@ -102,15 +94,12 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
Quota: data,
|
Quota: data,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
}
|
}
|
||||||
if quotaConfig.Quota == 0 {
|
if quotaConfig.Size == 0 || quotaConfig.Quota == 0 {
|
||||||
bucketMeta.Quota = nil
|
bucketMeta.Quota = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call site replication hook.
|
// Call site replication hook.
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, bucketMeta); err != nil {
|
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, bucketMeta))
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write success response.
|
// Write success response.
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
@@ -118,11 +107,9 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
|
|
||||||
// GetBucketQuotaConfigHandler - gets bucket quota configuration
|
// GetBucketQuotaConfigHandler - gets bucket quota configuration
|
||||||
func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetBucketQuotaConfig")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.GetBucketQuotaAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketQuotaAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -153,20 +140,14 @@ func (a adminAPIHandlers) GetBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
|||||||
|
|
||||||
// SetRemoteTargetHandler - sets a remote target for bucket
|
// SetRemoteTargetHandler - sets a remote target for bucket
|
||||||
func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetBucketTarget")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := pathClean(vars["bucket"])
|
bucket := pathClean(vars["bucket"])
|
||||||
update := r.Form.Get("update") == "true"
|
update := r.Form.Get("update") == "true"
|
||||||
|
|
||||||
if globalIsGateway {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -177,7 +158,7 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cred, _, _, s3Err := validateAdminSignature(ctx, r, "")
|
cred, _, s3Err := validateAdminSignature(ctx, r, "")
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -206,12 +187,28 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
if update {
|
if update {
|
||||||
ops = madmin.GetTargetUpdateOps(r.Form)
|
ops = madmin.GetTargetUpdateOps(r.Form)
|
||||||
} else {
|
} else {
|
||||||
target.Arn = globalBucketTargetSys.getRemoteARN(bucket, &target)
|
var exists bool // true if arn exists
|
||||||
|
target.Arn, exists = globalBucketTargetSys.getRemoteARN(bucket, &target, "")
|
||||||
|
if exists && target.Arn != "" { // return pre-existing ARN
|
||||||
|
data, err := json.Marshal(target.Arn)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Write success response.
|
||||||
|
writeSuccessResponseJSON(w, data)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if target.Arn == "" {
|
if target.Arn == "" {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if globalSiteReplicationSys.isEnabled() && !update {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrRemoteTargetDenyAddError, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if update {
|
if update {
|
||||||
// overlay the updates on existing target
|
// overlay the updates on existing target
|
||||||
tgt := globalBucketTargetSys.GetRemoteBucketTargetByArn(ctx, bucket, target.Arn)
|
tgt := globalBucketTargetSys.GetRemoteBucketTargetByArn(ctx, bucket, target.Arn)
|
||||||
@@ -222,10 +219,14 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
for _, op := range ops {
|
for _, op := range ops {
|
||||||
switch op {
|
switch op {
|
||||||
case madmin.CredentialsUpdateType:
|
case madmin.CredentialsUpdateType:
|
||||||
tgt.Credentials = target.Credentials
|
if !globalSiteReplicationSys.isEnabled() {
|
||||||
tgt.TargetBucket = target.TargetBucket
|
// credentials update is possible only in bucket replication. User will never
|
||||||
tgt.Secure = target.Secure
|
// know the site replicator creds.
|
||||||
tgt.Endpoint = target.Endpoint
|
tgt.Credentials = target.Credentials
|
||||||
|
tgt.TargetBucket = target.TargetBucket
|
||||||
|
tgt.Secure = target.Secure
|
||||||
|
tgt.Endpoint = target.Endpoint
|
||||||
|
}
|
||||||
case madmin.SyncUpdateType:
|
case madmin.SyncUpdateType:
|
||||||
tgt.ReplicationSync = target.ReplicationSync
|
tgt.ReplicationSync = target.ReplicationSync
|
||||||
case madmin.ProxyUpdateType:
|
case madmin.ProxyUpdateType:
|
||||||
@@ -282,19 +283,14 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
// ListRemoteTargetsHandler - lists remote target(s) for a bucket or gets a target
|
// ListRemoteTargetsHandler - lists remote target(s) for a bucket or gets a target
|
||||||
// for a particular ARN type
|
// for a particular ARN type
|
||||||
func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListBucketTargets")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := pathClean(vars["bucket"])
|
bucket := pathClean(vars["bucket"])
|
||||||
arnType := vars["type"]
|
arnType := vars["type"]
|
||||||
|
|
||||||
if globalIsGateway {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.GetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.GetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -321,19 +317,14 @@ func (a adminAPIHandlers) ListRemoteTargetsHandler(w http.ResponseWriter, r *htt
|
|||||||
|
|
||||||
// RemoveRemoteTargetHandler - removes a remote target for bucket with specified ARN
|
// RemoveRemoteTargetHandler - removes a remote target for bucket with specified ARN
|
||||||
func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "RemoveBucketTarget")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := pathClean(vars["bucket"])
|
bucket := pathClean(vars["bucket"])
|
||||||
arn := vars["arn"]
|
arn := vars["arn"]
|
||||||
|
|
||||||
if globalIsGateway {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SetBucketTargetAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SetBucketTargetAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -369,16 +360,11 @@ func (a adminAPIHandlers) RemoveRemoteTargetHandler(w http.ResponseWriter, r *ht
|
|||||||
|
|
||||||
// ExportBucketMetadataHandler - exports all bucket metadata as a zipped file
|
// ExportBucketMetadataHandler - exports all bucket metadata as a zipped file
|
||||||
func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ExportBucketMetadata")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
bucket := pathClean(r.Form.Get("bucket"))
|
bucket := pathClean(r.Form.Get("bucket"))
|
||||||
if globalIsGateway {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ExportBucketMetadataAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ExportBucketMetadataAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -406,7 +392,8 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
// of bucket metadata
|
// of bucket metadata
|
||||||
zipWriter := zip.NewWriter(w)
|
zipWriter := zip.NewWriter(w)
|
||||||
defer zipWriter.Close()
|
defer zipWriter.Close()
|
||||||
rawDataFn := func(r io.Reader, filename string, sz int) error {
|
|
||||||
|
rawDataFn := func(r io.Reader, filename string, sz int) {
|
||||||
header, zerr := zip.FileInfoHeader(dummyFileInfo{
|
header, zerr := zip.FileInfoHeader(dummyFileInfo{
|
||||||
name: filename,
|
name: filename,
|
||||||
size: int64(sz),
|
size: int64(sz),
|
||||||
@@ -415,20 +402,13 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
isDir: false,
|
isDir: false,
|
||||||
sys: nil,
|
sys: nil,
|
||||||
})
|
})
|
||||||
if zerr != nil {
|
if zerr == nil {
|
||||||
logger.LogIf(ctx, zerr)
|
header.Method = zip.Deflate
|
||||||
return nil
|
zwriter, zerr := zipWriter.CreateHeader(header)
|
||||||
|
if zerr == nil {
|
||||||
|
io.Copy(zwriter, r)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
header.Method = zip.Deflate
|
|
||||||
zwriter, zerr := zipWriter.CreateHeader(header)
|
|
||||||
if zerr != nil {
|
|
||||||
logger.LogIf(ctx, zerr)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if _, err := io.Copy(zwriter, r); err != nil {
|
|
||||||
logger.LogIf(ctx, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgFiles := []string{
|
cfgFiles := []string{
|
||||||
@@ -460,12 +440,9 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bucketLifecycleConfig:
|
case bucketLifecycleConfig:
|
||||||
config, err := globalBucketMetadataSys.GetLifecycleConfig(bucket)
|
config, _, err := globalBucketMetadataSys.GetLifecycleConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, BucketLifecycleNotFound{Bucket: bucket}) {
|
if errors.Is(err, BucketLifecycleNotFound{Bucket: bucket}) {
|
||||||
continue
|
continue
|
||||||
@@ -479,10 +456,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bucketQuotaConfigFile:
|
case bucketQuotaConfigFile:
|
||||||
config, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucket)
|
config, _, err := globalBucketMetadataSys.GetQuotaConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -497,10 +471,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bucketSSEConfig:
|
case bucketSSEConfig:
|
||||||
config, _, err := globalBucketMetadataSys.GetSSEConfig(bucket)
|
config, _, err := globalBucketMetadataSys.GetSSEConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -515,10 +486,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bucketTaggingConfig:
|
case bucketTaggingConfig:
|
||||||
config, _, err := globalBucketMetadataSys.GetTaggingConfig(bucket)
|
config, _, err := globalBucketMetadataSys.GetTaggingConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -533,10 +501,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case objectLockConfig:
|
case objectLockConfig:
|
||||||
config, _, err := globalBucketMetadataSys.GetObjectLockConfig(bucket)
|
config, _, err := globalBucketMetadataSys.GetObjectLockConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -552,10 +517,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bucketVersioningConfig:
|
case bucketVersioningConfig:
|
||||||
config, _, err := globalBucketMetadataSys.GetVersioningConfig(bucket)
|
config, _, err := globalBucketMetadataSys.GetVersioningConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -571,10 +533,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bucketReplicationConfig:
|
case bucketReplicationConfig:
|
||||||
config, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket)
|
config, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -589,11 +548,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case bucketTargetsFile:
|
case bucketTargetsFile:
|
||||||
config, err := globalBucketMetadataSys.GetBucketTargetsConfig(bucket)
|
config, err := globalBucketMetadataSys.GetBucketTargetsConfig(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -609,10 +564,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rawDataFn(bytes.NewReader(configData), cfgPath, len(configData)); err != nil {
|
rawDataFn(bytes.NewReader(configData), cfgPath, len(configData))
|
||||||
writeErrorResponse(ctx, w, exportError(ctx, err, cfgFile, bucket), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -657,16 +609,10 @@ func (i *importMetaReport) SetStatus(bucket, fname string, err error) {
|
|||||||
// 2. Replication config - is omitted from import as remote target credentials are not available from exported data for security reasons.
|
// 2. Replication config - is omitted from import as remote target credentials are not available from exported data for security reasons.
|
||||||
// 3. lifecycle config - if transition rules are present, tier name needs to have been defined.
|
// 3. lifecycle config - if transition rules are present, tier name needs to have been defined.
|
||||||
func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ImportBucketMetadata")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
if globalIsGateway {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ImportBucketMetadataAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ImportBucketMetadataAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -681,12 +627,31 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bucketMap := make(map[string]struct{}, 1)
|
|
||||||
rpt := importMetaReport{
|
rpt := importMetaReport{
|
||||||
madmin.BucketMetaImportErrs{
|
madmin.BucketMetaImportErrs{
|
||||||
Buckets: make(map[string]madmin.BucketStatus, len(zr.File)),
|
Buckets: make(map[string]madmin.BucketStatus, len(zr.File)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bucketMap := make(map[string]*BucketMetadata, len(zr.File))
|
||||||
|
|
||||||
|
updatedAt := UTCNow()
|
||||||
|
|
||||||
|
for _, file := range zr.File {
|
||||||
|
slc := strings.Split(file.Name, slashSeparator)
|
||||||
|
if len(slc) != 2 { // expecting bucket/configfile in the zipfile
|
||||||
|
rpt.SetStatus(file.Name, "", fmt.Errorf("malformed zip - expecting format bucket/<config.json>"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bucket := slc[0]
|
||||||
|
meta, err := readBucketMetadata(ctx, objectAPI, bucket)
|
||||||
|
if err == nil {
|
||||||
|
bucketMap[bucket] = &meta
|
||||||
|
} else if err != errConfigNotFound {
|
||||||
|
rpt.SetStatus(bucket, "", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// import object lock config if any - order of import matters here.
|
// import object lock config if any - order of import matters here.
|
||||||
for _, file := range zr.File {
|
for _, file := range zr.File {
|
||||||
slc := strings.Split(file.Name, slashSeparator)
|
slc := strings.Split(file.Name, slashSeparator)
|
||||||
@@ -695,8 +660,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bucket, fileName := slc[0], slc[1]
|
bucket, fileName := slc[0], slc[1]
|
||||||
switch fileName {
|
if fileName == objectLockConfig {
|
||||||
case objectLockConfig:
|
|
||||||
reader, err := file.Open()
|
reader, err := file.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
@@ -715,16 +679,17 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
}
|
}
|
||||||
if _, ok := bucketMap[bucket]; !ok {
|
if _, ok := bucketMap[bucket]; !ok {
|
||||||
opts := MakeBucketOptions{
|
opts := MakeBucketOptions{
|
||||||
LockEnabled: config.ObjectLockEnabled == "Enabled",
|
LockEnabled: config.Enabled(),
|
||||||
}
|
}
|
||||||
err = objectAPI.MakeBucketWithLocation(ctx, bucket, opts)
|
err = objectAPI.MakeBucket(ctx, bucket, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(BucketExists); !ok {
|
if _, ok := err.(BucketExists); !ok {
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bucketMap[bucket] = struct{}{}
|
v := newBucketMetadata(bucket)
|
||||||
|
bucketMap[bucket] = &v
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deny object locking configuration settings on existing buckets without object lock enabled.
|
// Deny object locking configuration settings on existing buckets without object lock enabled.
|
||||||
@@ -733,27 +698,9 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, objectLockConfig, configData)
|
bucketMap[bucket].ObjectLockConfigXML = configData
|
||||||
if err != nil {
|
bucketMap[bucket].ObjectLockConfigUpdatedAt = updatedAt
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
|
||||||
// Call site replication hook.
|
|
||||||
//
|
|
||||||
// We encode the xml bytes as base64 to ensure there are no encoding
|
|
||||||
// errors.
|
|
||||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
|
||||||
Type: madmin.SRBucketMetaTypeObjectLockConfig,
|
|
||||||
Bucket: bucket,
|
|
||||||
ObjectLockConfig: &cfgStr,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
}); err != nil {
|
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -765,8 +712,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bucket, fileName := slc[0], slc[1]
|
bucket, fileName := slc[0], slc[1]
|
||||||
switch fileName {
|
if fileName == bucketVersioningConfig {
|
||||||
case bucketVersioningConfig:
|
|
||||||
reader, err := file.Open()
|
reader, err := file.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
@@ -778,13 +724,14 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, ok := bucketMap[bucket]; !ok {
|
if _, ok := bucketMap[bucket]; !ok {
|
||||||
if err = objectAPI.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}); err != nil {
|
if err = objectAPI.MakeBucket(ctx, bucket, MakeBucketOptions{}); err != nil {
|
||||||
if _, ok := err.(BucketExists); !ok {
|
if _, ok := err.(BucketExists); !ok {
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bucketMap[bucket] = struct{}{}
|
v := newBucketMetadata(bucket)
|
||||||
|
bucketMap[bucket] = &v
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalSiteReplicationSys.isEnabled() && v.Suspended() {
|
if globalSiteReplicationSys.isEnabled() && v.Suspended() {
|
||||||
@@ -807,10 +754,8 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketVersioningConfig, configData); err != nil {
|
bucketMap[bucket].VersioningConfigXML = configData
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
bucketMap[bucket].VersioningConfigUpdatedAt = updatedAt
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -828,16 +773,18 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bucket, fileName := slc[0], slc[1]
|
bucket, fileName := slc[0], slc[1]
|
||||||
|
|
||||||
// create bucket if it does not exist yet.
|
// create bucket if it does not exist yet.
|
||||||
if _, ok := bucketMap[bucket]; !ok {
|
if _, ok := bucketMap[bucket]; !ok {
|
||||||
err = objectAPI.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
|
err = objectAPI.MakeBucket(ctx, bucket, MakeBucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(BucketExists); !ok {
|
if _, ok := err.(BucketExists); !ok {
|
||||||
rpt.SetStatus(bucket, "", err)
|
rpt.SetStatus(bucket, "", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bucketMap[bucket] = struct{}{}
|
v := newBucketMetadata(bucket)
|
||||||
|
bucketMap[bucket] = &v
|
||||||
}
|
}
|
||||||
if _, ok := bucketMap[bucket]; !ok {
|
if _, ok := bucketMap[bucket]; !ok {
|
||||||
continue
|
continue
|
||||||
@@ -856,12 +803,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketNotificationConfig, configData); err != nil {
|
bucketMap[bucket].NotificationConfigXML = configData
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rulesMap := config.ToRulesMap()
|
|
||||||
globalEventNotifier.AddRulesMap(bucket, rulesMap)
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
case bucketPolicyConfig:
|
case bucketPolicyConfig:
|
||||||
// Error out if Content-Length is beyond allowed size.
|
// Error out if Content-Length is beyond allowed size.
|
||||||
@@ -876,7 +818,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyBytes), bucket)
|
bucketPolicy, err := policy.ParseBucketPolicyConfig(bytes.NewReader(bucketPolicyBytes), bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
continue
|
continue
|
||||||
@@ -884,7 +826,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
|
|
||||||
// Version in policy must not be empty
|
// Version in policy must not be empty
|
||||||
if bucketPolicy.Version == "" {
|
if bucketPolicy.Version == "" {
|
||||||
rpt.SetStatus(bucket, fileName, fmt.Errorf(ErrMalformedPolicy.String()))
|
rpt.SetStatus(bucket, fileName, fmt.Errorf(ErrPolicyInvalidVersion.String()))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -894,22 +836,9 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketPolicyConfig, configData)
|
bucketMap[bucket].PolicyConfigJSON = configData
|
||||||
if err != nil {
|
bucketMap[bucket].PolicyConfigUpdatedAt = updatedAt
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
// Call site replication hook.
|
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
|
||||||
Type: madmin.SRBucketMetaTypePolicy,
|
|
||||||
Bucket: bucket,
|
|
||||||
Policy: bucketPolicyBytes,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
}); err != nil {
|
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case bucketLifecycleConfig:
|
case bucketLifecycleConfig:
|
||||||
bucketLifecycle, err := lifecycle.ParseLifecycleConfig(io.LimitReader(reader, sz))
|
bucketLifecycle, err := lifecycle.ParseLifecycleConfig(io.LimitReader(reader, sz))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -935,10 +864,8 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketLifecycleConfig, configData); err != nil {
|
bucketMap[bucket].LifecycleConfigXML = configData
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
bucketMap[bucket].LifecycleConfigUpdatedAt = updatedAt
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
case bucketSSEConfig:
|
case bucketSSEConfig:
|
||||||
// Parse bucket encryption xml
|
// Parse bucket encryption xml
|
||||||
@@ -973,29 +900,9 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the bucket encryption configuration in the object layer
|
bucketMap[bucket].EncryptionConfigXML = configData
|
||||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketSSEConfig, configData)
|
bucketMap[bucket].EncryptionConfigUpdatedAt = updatedAt
|
||||||
if err != nil {
|
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
|
||||||
// Call site replication hook.
|
|
||||||
//
|
|
||||||
// We encode the xml bytes as base64 to ensure there are no encoding
|
|
||||||
// errors.
|
|
||||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
|
||||||
Type: madmin.SRBucketMetaTypeSSEConfig,
|
|
||||||
Bucket: bucket,
|
|
||||||
SSEConfig: &cfgStr,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
}); err != nil {
|
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
case bucketTaggingConfig:
|
case bucketTaggingConfig:
|
||||||
tags, err := tags.ParseBucketXML(io.LimitReader(reader, sz))
|
tags, err := tags.ParseBucketXML(io.LimitReader(reader, sz))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1009,27 +916,9 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketTaggingConfig, configData)
|
bucketMap[bucket].TaggingConfigXML = configData
|
||||||
if err != nil {
|
bucketMap[bucket].TaggingConfigUpdatedAt = updatedAt
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
|
||||||
// Call site replication hook.
|
|
||||||
//
|
|
||||||
// We encode the xml bytes as base64 to ensure there are no encoding
|
|
||||||
// errors.
|
|
||||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
|
||||||
Type: madmin.SRBucketMetaTypeTags,
|
|
||||||
Bucket: bucket,
|
|
||||||
Tags: &cfgStr,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
}); err != nil {
|
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case bucketQuotaConfigFile:
|
case bucketQuotaConfigFile:
|
||||||
data, err := io.ReadAll(reader)
|
data, err := io.ReadAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1037,42 +926,49 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
quotaConfig, err := parseBucketQuota(bucket, data)
|
_, err = parseBucketQuota(bucket, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
rpt.SetStatus(bucket, fileName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if quotaConfig.Type == "fifo" {
|
bucketMap[bucket].QuotaConfigJSON = data
|
||||||
rpt.SetStatus(bucket, fileName, fmt.Errorf("Detected older 'fifo' quota config, 'fifo' feature is removed and not supported anymore"))
|
bucketMap[bucket].QuotaConfigUpdatedAt = updatedAt
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketQuotaConfigFile, data)
|
|
||||||
if err != nil {
|
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
rpt.SetStatus(bucket, fileName, nil)
|
rpt.SetStatus(bucket, fileName, nil)
|
||||||
|
|
||||||
bucketMeta := madmin.SRBucketMeta{
|
|
||||||
Type: madmin.SRBucketMetaTypeQuotaConfig,
|
|
||||||
Bucket: bucket,
|
|
||||||
Quota: data,
|
|
||||||
UpdatedAt: updatedAt,
|
|
||||||
}
|
|
||||||
if quotaConfig.Quota == 0 {
|
|
||||||
bucketMeta.Quota = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call site replication hook.
|
|
||||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, bucketMeta); err != nil {
|
|
||||||
rpt.SetStatus(bucket, fileName, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enc := func(b []byte) *string {
|
||||||
|
if b == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
v := base64.StdEncoding.EncodeToString(b)
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
for bucket, meta := range bucketMap {
|
||||||
|
err := globalBucketMetadataSys.save(ctx, *meta)
|
||||||
|
if err != nil {
|
||||||
|
rpt.SetStatus(bucket, "", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Call site replication hook.
|
||||||
|
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||||
|
Bucket: bucket,
|
||||||
|
Quota: meta.QuotaConfigJSON,
|
||||||
|
Policy: meta.PolicyConfigJSON,
|
||||||
|
Versioning: enc(meta.VersioningConfigXML),
|
||||||
|
Tags: enc(meta.TaggingConfigXML),
|
||||||
|
ObjectLockConfig: enc(meta.ObjectLockConfigXML),
|
||||||
|
SSEConfig: enc(meta.EncryptionConfigXML),
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}); err != nil {
|
||||||
|
rpt.SetStatus(bucket, "", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
rptData, err := json.Marshal(rpt.BucketMetaImportErrs)
|
rptData, err := json.Marshal(rpt.BucketMetaImportErrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
@@ -1083,28 +979,15 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReplicationDiffHandler - POST returns info on unreplicated versions for a remote target ARN
|
// ReplicationDiffHandler - POST returns info on unreplicated versions for a remote target ARN
|
||||||
// to the connected HTTP client. This is a MinIO only extension
|
// to the connected HTTP client.
|
||||||
func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ReplicationDiff")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
if globalIsGateway {
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ReplicationDiff)
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current object layer instance.
|
|
||||||
objectAPI := newObjectLayerFn()
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// check if user has permissions to perform this operation
|
|
||||||
if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketVersionsAction, bucket, ""); s3Error != ErrNone {
|
|
||||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1157,3 +1040,62 @@ func (a adminAPIHandlers) ReplicationDiffHandler(w http.ResponseWriter, r *http.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReplicationMRFHandler - POST returns info on entries in the MRF backlog for a node or all nodes
|
||||||
|
func (a adminAPIHandlers) ReplicationMRFHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
bucket := vars["bucket"]
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ReplicationDiff)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bucket exists.
|
||||||
|
if bucket != "" {
|
||||||
|
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
q := r.Form
|
||||||
|
node := q.Get("node")
|
||||||
|
|
||||||
|
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
defer keepAliveTicker.Stop()
|
||||||
|
|
||||||
|
mrfCh, err := globalNotificationSys.GetReplicationMRF(ctx, bucket, node)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case entry, ok := <-mrfCh:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := enc.Encode(entry); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(mrfCh) == 0 {
|
||||||
|
// Flush if nothing is queued
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
case <-keepAliveTicker.C:
|
||||||
|
if len(mrfCh) > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := w.Write([]byte(" ")); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+35
-37
@@ -23,18 +23,18 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/minio/kes"
|
"github.com/minio/kes-go"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// validateAdminReq will validate request against and return whether it is allowed.
|
// validateAdminReq will validate request against and return whether it is allowed.
|
||||||
// If any of the supplied actions are allowed it will be successful.
|
// If any of the supplied actions are allowed it will be successful.
|
||||||
// If nil ObjectLayer is returned, the operation is not permitted.
|
// If nil ObjectLayer is returned, the operation is not permitted.
|
||||||
// When nil ObjectLayer has been returned an error has always been sent to w.
|
// When nil ObjectLayer has been returned an error has always been sent to w.
|
||||||
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request, actions ...iampolicy.AdminAction) (ObjectLayer, auth.Credentials) {
|
func validateAdminReq(ctx context.Context, w http.ResponseWriter, r *http.Request, actions ...policy.AdminAction) (ObjectLayer, auth.Credentials) {
|
||||||
// Get current object layer instance.
|
// Get current object layer instance.
|
||||||
objectAPI := newObjectLayerFn()
|
objectAPI := newObjectLayerFn()
|
||||||
if objectAPI == nil || globalNotificationSys == nil {
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
@@ -78,13 +78,19 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
|
|
||||||
var apiErr APIError
|
var apiErr APIError
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case iampolicy.Error:
|
case policy.Error:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioMalformedIAMPolicy",
|
Code: "XMinioMalformedIAMPolicy",
|
||||||
Description: e.Error(),
|
Description: e.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
case config.Error:
|
case config.ErrConfigNotFound:
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioConfigNotFoundError",
|
||||||
|
Description: e.Error(),
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
}
|
||||||
|
case config.ErrConfigGeneric:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioConfigError",
|
Code: "XMinioConfigError",
|
||||||
Description: e.Error(),
|
Description: e.Error(),
|
||||||
@@ -124,6 +130,18 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
Description: err.Error(),
|
Description: err.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
|
case errors.Is(err, errDecommissionRebalanceAlreadyRunning):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioDecommissionNotAllowed",
|
||||||
|
Description: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
case errors.Is(err, errRebalanceDecommissionAlreadyRunning):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioRebalanceNotAllowed",
|
||||||
|
Description: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
case errors.Is(err, errConfigNotFound):
|
case errors.Is(err, errConfigNotFound):
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioConfigError",
|
Code: "XMinioConfigError",
|
||||||
@@ -136,15 +154,9 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
Description: err.Error(),
|
Description: err.Error(),
|
||||||
HTTPStatusCode: http.StatusForbidden,
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
}
|
}
|
||||||
case errors.Is(err, errIAMServiceAccount):
|
case errors.Is(err, errIAMServiceAccountNotAllowed):
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioIAMServiceAccount",
|
Code: "XMinioIAMServiceAccountNotAllowed",
|
||||||
Description: err.Error(),
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
}
|
|
||||||
case errors.Is(err, errIAMServiceAccountUsed):
|
|
||||||
apiErr = APIError{
|
|
||||||
Code: "XMinioIAMServiceAccountUsed",
|
|
||||||
Description: err.Error(),
|
Description: err.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
@@ -156,10 +168,16 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
}
|
}
|
||||||
case errors.Is(err, errPolicyInUse):
|
case errors.Is(err, errPolicyInUse):
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioAdminPolicyInUse",
|
Code: "XMinioIAMPolicyInUse",
|
||||||
Description: "The policy cannot be removed, as it is in use",
|
Description: "The policy cannot be removed, as it is in use",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
|
case errors.Is(err, errSessionPolicyTooLarge):
|
||||||
|
apiErr = APIError{
|
||||||
|
Code: "XMinioIAMServiceAccountSessionPolicyTooLarge",
|
||||||
|
Description: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
case errors.Is(err, kes.ErrKeyExists):
|
case errors.Is(err, kes.ErrKeyExists):
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioKMSKeyExists",
|
Code: "XMinioKMSKeyExists",
|
||||||
@@ -192,24 +210,6 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
Description: err.Error(),
|
Description: err.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
case errors.Is(err, errTierBackendInUse):
|
|
||||||
apiErr = APIError{
|
|
||||||
Code: "XMinioAdminTierBackendInUse",
|
|
||||||
Description: err.Error(),
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
}
|
|
||||||
case errors.Is(err, errTierBackendNotEmpty):
|
|
||||||
apiErr = APIError{
|
|
||||||
Code: "XMinioAdminTierBackendNotEmpty",
|
|
||||||
Description: err.Error(),
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
}
|
|
||||||
case errors.Is(err, errTierInsufficientCreds):
|
|
||||||
apiErr = APIError{
|
|
||||||
Code: "XMinioAdminTierInsufficientCreds",
|
|
||||||
Description: err.Error(),
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
}
|
|
||||||
case errIsTierPermError(err):
|
case errIsTierPermError(err):
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioAdminTierInsufficientPermissions",
|
Code: "XMinioAdminTierInsufficientPermissions",
|
||||||
@@ -226,12 +226,10 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
|||||||
// toAdminAPIErrCode - converts errErasureWriteQuorum error to admin API
|
// toAdminAPIErrCode - converts errErasureWriteQuorum error to admin API
|
||||||
// specific error.
|
// specific error.
|
||||||
func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode {
|
func toAdminAPIErrCode(ctx context.Context, err error) APIErrorCode {
|
||||||
switch err {
|
if errors.Is(err, errErasureWriteQuorum) {
|
||||||
case errErasureWriteQuorum:
|
|
||||||
return ErrAdminConfigNoQuorum
|
return ErrAdminConfigNoQuorum
|
||||||
default:
|
|
||||||
return toAPIErrorCode(ctx, err)
|
|
||||||
}
|
}
|
||||||
|
return toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// wraps export error for more context
|
// wraps export error for more context
|
||||||
|
|||||||
+136
-91
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -26,27 +26,25 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/madmin-go"
|
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/config/cache"
|
|
||||||
"github.com/minio/minio/internal/config/etcd"
|
"github.com/minio/minio/internal/config/etcd"
|
||||||
xldap "github.com/minio/minio/internal/config/identity/ldap"
|
xldap "github.com/minio/minio/internal/config/identity/ldap"
|
||||||
"github.com/minio/minio/internal/config/identity/openid"
|
"github.com/minio/minio/internal/config/identity/openid"
|
||||||
idplugin "github.com/minio/minio/internal/config/identity/plugin"
|
idplugin "github.com/minio/minio/internal/config/identity/plugin"
|
||||||
polplugin "github.com/minio/minio/internal/config/policy/plugin"
|
polplugin "github.com/minio/minio/internal/config/policy/plugin"
|
||||||
"github.com/minio/minio/internal/config/storageclass"
|
"github.com/minio/minio/internal/config/storageclass"
|
||||||
|
"github.com/minio/minio/internal/config/subnet"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/mux"
|
||||||
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv
|
// DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv
|
||||||
func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteConfigKV")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -71,7 +69,7 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := readServerConfig(ctx, objectAPI)
|
cfg, err := readServerConfig(ctx, objectAPI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -82,11 +80,15 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validateConfig(cfg, subSys); err != nil {
|
if err = validateConfig(ctx, cfg, subSys); err != nil {
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if subnet proxy being deleted and if so the value of proxy of subnet
|
||||||
|
// target of logger webhook configuration also should be deleted
|
||||||
|
loggerWebhookProxyDeleted := setLoggerWebhookSubnetProxy(subSys, cfg)
|
||||||
|
|
||||||
if err = saveServerConfig(ctx, objectAPI, cfg); err != nil {
|
if err = saveServerConfig(ctx, objectAPI, cfg); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -101,6 +103,10 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
dynamic := config.SubSystemsDynamic.Contains(subSys)
|
dynamic := config.SubSystemsDynamic.Contains(subSys)
|
||||||
if dynamic {
|
if dynamic {
|
||||||
applyDynamic(ctx, objectAPI, cfg, subSys, r, w)
|
applyDynamic(ctx, objectAPI, cfg, subSys, r, w)
|
||||||
|
if subSys == config.SubnetSubSys && loggerWebhookProxyDeleted {
|
||||||
|
// Logger webhook proxy deleted, apply the dynamic changes
|
||||||
|
applyDynamic(ctx, objectAPI, cfg, config.LoggerWebhookSubSys, r, w)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,13 +123,32 @@ func applyDynamic(ctx context.Context, objectAPI ObjectLayer, cfg config.Config,
|
|||||||
w.Header().Set(madmin.ConfigAppliedHeader, madmin.ConfigAppliedTrue)
|
w.Header().Set(madmin.ConfigAppliedHeader, madmin.ConfigAppliedTrue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type badConfigErr struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error - return the error message
|
||||||
|
func (bce badConfigErr) Error() string {
|
||||||
|
return bce.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap the error to its underlying error.
|
||||||
|
func (bce badConfigErr) Unwrap() error {
|
||||||
|
return bce.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
type setConfigResult struct {
|
||||||
|
Cfg config.Config
|
||||||
|
SubSys string
|
||||||
|
Dynamic bool
|
||||||
|
LoggerWebhookCfgUpdated bool
|
||||||
|
}
|
||||||
|
|
||||||
// SetConfigKVHandler - PUT /minio/admin/v3/set-config-kv
|
// SetConfigKVHandler - PUT /minio/admin/v3/set-config-kv
|
||||||
func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetConfigKV")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -142,60 +167,81 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := readServerConfig(ctx, objectAPI)
|
result, err := setConfigKV(ctx, objectAPI, kvBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
switch err.(type) {
|
||||||
|
case badConfigErr:
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
|
default:
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic, err := cfg.ReadConfig(bytes.NewReader(kvBytes))
|
if result.Dynamic {
|
||||||
if err != nil {
|
applyDynamic(ctx, objectAPI, result.Cfg, result.SubSys, r, w)
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
// If logger webhook config updated (proxy due to callhome), explicitly dynamically
|
||||||
return
|
// apply the config
|
||||||
}
|
if result.LoggerWebhookCfgUpdated {
|
||||||
|
applyDynamic(ctx, objectAPI, result.Cfg, config.LoggerWebhookSubSys, r, w)
|
||||||
subSys, _, _, err := config.GetSubSys(string(kvBytes))
|
}
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = validateConfig(cfg, subSys); err != nil {
|
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the actual server config on disk.
|
|
||||||
if err = saveServerConfig(ctx, objectAPI, cfg); err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write to the config input KV to history.
|
|
||||||
if err = saveServerConfigHistory(ctx, objectAPI, kvBytes); err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if dynamic {
|
|
||||||
applyDynamic(ctx, objectAPI, cfg, subSys, r, w)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setConfigKV(ctx context.Context, objectAPI ObjectLayer, kvBytes []byte) (result setConfigResult, err error) {
|
||||||
|
result.Cfg, err = readServerConfig(ctx, objectAPI, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Dynamic, err = result.Cfg.ReadConfig(bytes.NewReader(kvBytes))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result.SubSys, _, _, err = config.GetSubSys(string(kvBytes))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tgts, err := config.ParseConfigTargetID(bytes.NewReader(kvBytes))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx = context.WithValue(ctx, config.ContextKeyForTargetFromConfig, tgts)
|
||||||
|
if verr := validateConfig(ctx, result.Cfg, result.SubSys); verr != nil {
|
||||||
|
err = badConfigErr{Err: verr}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if subnet proxy being set and if so set the same value to proxy of subnet
|
||||||
|
// target of logger webhook configuration
|
||||||
|
result.LoggerWebhookCfgUpdated = setLoggerWebhookSubnetProxy(result.SubSys, result.Cfg)
|
||||||
|
|
||||||
|
// Update the actual server config on disk.
|
||||||
|
if err = saveServerConfig(ctx, objectAPI, result.Cfg); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the config input KV to history.
|
||||||
|
err = saveServerConfigHistory(ctx, objectAPI, kvBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// GetConfigKVHandler - GET /minio/admin/v3/get-config-kv?key={key}
|
// GetConfigKVHandler - GET /minio/admin/v3/get-config-kv?key={key}
|
||||||
//
|
//
|
||||||
// `key` can be one of three forms:
|
// `key` can be one of three forms:
|
||||||
// 1. `subsys:target` -> request for config of a single subsystem and target pair.
|
// 1. `subsys:target` -> request for config of a single subsystem and target pair.
|
||||||
// 2. `subsys:` -> request for config of a single subsystem and the default target.
|
// 2. `subsys:` -> request for config of a single subsystem and the default target.
|
||||||
// 3. `subsys` -> request for config of all targets for the given subsystem.
|
// 3. `subsys` -> request for config of all targets for the given subsystem.
|
||||||
|
//
|
||||||
|
// This is a reporting API and config secrets are redacted in the response.
|
||||||
func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetConfigKV")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -217,7 +263,7 @@ func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subSysConfigs, err := cfg.GetSubsysInfo(subSys, target)
|
subSysConfigs, err := cfg.GetSubsysInfo(subSys, target, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -225,7 +271,7 @@ func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
var s strings.Builder
|
var s strings.Builder
|
||||||
for _, subSysConfig := range subSysConfigs {
|
for _, subSysConfig := range subSysConfigs {
|
||||||
subSysConfig.AddString(&s, false)
|
subSysConfig.WriteTo(&s, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
password := cred.SecretKey
|
password := cred.SecretKey
|
||||||
@@ -239,11 +285,9 @@ func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ClearConfigHistoryKV")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -274,11 +318,9 @@ func (a adminAPIHandlers) ClearConfigHistoryKVHandler(w http.ResponseWriter, r *
|
|||||||
|
|
||||||
// RestoreConfigHistoryKVHandler - restores a config with KV settings for the given KV id.
|
// RestoreConfigHistoryKVHandler - restores a config with KV settings for the given KV id.
|
||||||
func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "RestoreConfigHistoryKV")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -296,7 +338,7 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := readServerConfig(ctx, objectAPI)
|
cfg, err := readServerConfig(ctx, objectAPI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -307,7 +349,7 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validateConfig(cfg, ""); err != nil {
|
if err = validateConfig(ctx, cfg, ""); err != nil {
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -322,11 +364,9 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
|
|||||||
|
|
||||||
// ListConfigHistoryKVHandler - lists all the KV ids.
|
// ListConfigHistoryKVHandler - lists all the KV ids.
|
||||||
func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListConfigHistoryKV")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -362,11 +402,9 @@ func (a adminAPIHandlers) ListConfigHistoryKVHandler(w http.ResponseWriter, r *h
|
|||||||
|
|
||||||
// HelpConfigKVHandler - GET /minio/admin/v3/help-config-kv?subSys={subSys}&key={key}
|
// HelpConfigKVHandler - GET /minio/admin/v3/help-config-kv?subSys={subSys}&key={key}
|
||||||
func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "HelpConfigKV")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -389,11 +427,9 @@ func (a adminAPIHandlers) HelpConfigKVHandler(w http.ResponseWriter, r *http.Req
|
|||||||
|
|
||||||
// SetConfigHandler - PUT /minio/admin/v3/config
|
// SetConfigHandler - PUT /minio/admin/v3/config
|
||||||
func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SetConfig")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -418,7 +454,7 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validateConfig(cfg, ""); err != nil {
|
if err = validateConfig(ctx, cfg, ""); err != nil {
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -439,13 +475,13 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetConfigHandler - GET /minio/admin/v3/config
|
// GetConfigHandler - GET /minio/admin/v3/config
|
||||||
// Get config.json of this minio setup.
|
//
|
||||||
|
// This endpoint is mainly for exporting and backing up the configuration.
|
||||||
|
// Secrets are not redacted.
|
||||||
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "GetConfig")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -454,21 +490,15 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
|
|
||||||
var s strings.Builder
|
var s strings.Builder
|
||||||
hkvs := config.HelpSubSysMap[""]
|
hkvs := config.HelpSubSysMap[""]
|
||||||
var count int
|
|
||||||
for _, hkv := range hkvs {
|
|
||||||
count += len(cfg[hkv.Key])
|
|
||||||
}
|
|
||||||
for _, hkv := range hkvs {
|
for _, hkv := range hkvs {
|
||||||
// We ignore the error below, as we cannot get one.
|
// We ignore the error below, as we cannot get one.
|
||||||
cfgSubsysItems, _ := cfg.GetSubsysInfo(hkv.Key, "")
|
cfgSubsysItems, _ := cfg.GetSubsysInfo(hkv.Key, "", false)
|
||||||
|
|
||||||
for _, item := range cfgSubsysItems {
|
for _, item := range cfgSubsysItems {
|
||||||
off := item.Config.Get(config.Enable) == config.EnableOff
|
off := item.Config.Get(config.Enable) == config.EnableOff
|
||||||
switch hkv.Key {
|
switch hkv.Key {
|
||||||
case config.EtcdSubSys:
|
case config.EtcdSubSys:
|
||||||
off = !etcd.Enabled(item.Config)
|
off = !etcd.Enabled(item.Config)
|
||||||
case config.CacheSubSys:
|
|
||||||
off = !cache.Enabled(item.Config)
|
|
||||||
case config.StorageClassSubSys:
|
case config.StorageClassSubSys:
|
||||||
off = !storageclass.Enabled(item.Config)
|
off = !storageclass.Enabled(item.Config)
|
||||||
case config.PolicyPluginSubSys:
|
case config.PolicyPluginSubSys:
|
||||||
@@ -478,11 +508,11 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
case config.IdentityLDAPSubSys:
|
case config.IdentityLDAPSubSys:
|
||||||
off = !xldap.Enabled(item.Config)
|
off = !xldap.Enabled(item.Config)
|
||||||
case config.IdentityTLSSubSys:
|
case config.IdentityTLSSubSys:
|
||||||
off = !globalSTSTLSConfig.Enabled
|
off = !globalIAMSys.STSTLSConfig.Enabled
|
||||||
case config.IdentityPluginSubSys:
|
case config.IdentityPluginSubSys:
|
||||||
off = !idplugin.Enabled(item.Config)
|
off = !idplugin.Enabled(item.Config)
|
||||||
}
|
}
|
||||||
item.AddString(&s, off)
|
item.WriteTo(&s, off)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,3 +525,18 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
|
|||||||
|
|
||||||
writeSuccessResponseJSON(w, econfigData)
|
writeSuccessResponseJSON(w, econfigData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setLoggerWebhookSubnetProxy - Sets the logger webhook's subnet proxy value to
|
||||||
|
// one being set for subnet proxy
|
||||||
|
func setLoggerWebhookSubnetProxy(subSys string, cfg config.Config) bool {
|
||||||
|
if subSys == config.SubnetSubSys || subSys == config.LoggerWebhookSubSys {
|
||||||
|
subnetWebhookCfg := cfg[config.LoggerWebhookSubSys][subnet.LoggerWebhookName]
|
||||||
|
loggerWebhookSubnetProxy := subnetWebhookCfg.Get(logger.Proxy)
|
||||||
|
subnetProxy := cfg[config.SubnetSubSys][config.Default].Get(logger.Proxy)
|
||||||
|
if loggerWebhookSubnetProxy != subnetProxy {
|
||||||
|
subnetWebhookCfg.Set(logger.Proxy, subnetProxy)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
+266
-145
@@ -20,32 +20,25 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/madmin-go"
|
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
|
cfgldap "github.com/minio/minio/internal/config/identity/ldap"
|
||||||
"github.com/minio/minio/internal/config/identity/openid"
|
"github.com/minio/minio/internal/config/identity/openid"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/mux"
|
||||||
|
"github.com/minio/pkg/v2/ldap"
|
||||||
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// List of implemented ID config types.
|
func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, isUpdate bool) {
|
||||||
var idCfgTypes = set.CreateStringSet("openid")
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
// SetIdentityProviderCfg:
|
|
||||||
//
|
|
||||||
// PUT <admin-prefix>/id-cfg?type=openid&name=dex1
|
|
||||||
func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := newContext(r, w, "SetIdentityCfg")
|
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -56,6 +49,14 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure body content type is opaque to ensure that request body has not
|
||||||
|
// been interpreted as form data.
|
||||||
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
if contentType != "application/octet-stream" {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
password := cred.SecretKey
|
password := cred.SecretKey
|
||||||
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -64,43 +65,49 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgType := mux.Vars(r)["type"]
|
idpCfgType := mux.Vars(r)["type"]
|
||||||
if !idCfgTypes.Contains(cfgType) {
|
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||||
// TODO: change this to invalid type error when implementation
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||||
// is complete.
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfgDataBuilder strings.Builder
|
var subSys string
|
||||||
switch cfgType {
|
switch idpCfgType {
|
||||||
case "openid":
|
case madmin.OpenidIDPCfg:
|
||||||
fmt.Fprintf(&cfgDataBuilder, "identity_openid")
|
subSys = madmin.IdentityOpenIDSubSys
|
||||||
|
case madmin.LDAPIDPCfg:
|
||||||
|
subSys = madmin.IdentityLDAPSubSys
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure body content type is opaque.
|
|
||||||
contentType := r.Header.Get("Content-Type")
|
|
||||||
if contentType != "application/octet-stream" {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subsystem configuration name could be empty.
|
|
||||||
cfgName := mux.Vars(r)["name"]
|
cfgName := mux.Vars(r)["name"]
|
||||||
|
cfgTarget := madmin.Default
|
||||||
if cfgName != "" {
|
if cfgName != "" {
|
||||||
fmt.Fprintf(&cfgDataBuilder, "%s%s", config.SubSystemSeparator, cfgName)
|
cfgTarget = cfgName
|
||||||
|
if idpCfgType == madmin.LDAPIDPCfg && cfgName != madmin.Default {
|
||||||
|
// LDAP does not support multiple configurations. So cfgName must be
|
||||||
|
// empty or `madmin.Default`.
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigLDAPNonDefaultConfigName), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(&cfgDataBuilder, "%s%s", config.KvSpaceSeparator, string(reqBytes))
|
// Check that this is a valid Create vs Update API call.
|
||||||
|
s := globalServerConfig.Clone()
|
||||||
cfgData := cfgDataBuilder.String()
|
if apiErrCode := handleCreateUpdateValidation(s, subSys, cfgTarget, isUpdate); apiErrCode != ErrNone {
|
||||||
subSys, _, _, err := config.GetSubSys(cfgData)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(apiErrCode), r.URL)
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := readServerConfig(ctx, objectAPI)
|
cfgData := ""
|
||||||
|
{
|
||||||
|
tgtSuffix := ""
|
||||||
|
if cfgTarget != madmin.Default {
|
||||||
|
tgtSuffix = config.SubSystemSeparator + cfgTarget
|
||||||
|
}
|
||||||
|
cfgData = subSys + tgtSuffix + config.KvSpaceSeparator + string(reqBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := readServerConfig(ctx, objectAPI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -114,11 +121,21 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
|
|
||||||
// IDP config is not dynamic. Sanity check.
|
// IDP config is not dynamic. Sanity check.
|
||||||
if dynamic {
|
if dynamic {
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), err.Error(), r.URL)
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInternalError), "", r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = validateConfig(cfg, subSys); err != nil {
|
if err = validateConfig(ctx, cfg, subSys); err != nil {
|
||||||
|
|
||||||
|
var validationErr ldap.Validation
|
||||||
|
if errors.As(err, &validationErr) {
|
||||||
|
// If we got an LDAP validation error, we need to send appropriate
|
||||||
|
// error message back to client (likely mc).
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigLDAPValidation),
|
||||||
|
validationErr.FormatError(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -138,86 +155,98 @@ func (a adminAPIHandlers) SetIdentityProviderCfg(w http.ResponseWriter, r *http.
|
|||||||
writeSuccessResponseHeadersOnly(w)
|
writeSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIdentityProviderCfg:
|
func handleCreateUpdateValidation(s config.Config, subSys, cfgTarget string, isUpdate bool) APIErrorCode {
|
||||||
//
|
if cfgTarget != madmin.Default {
|
||||||
// GET <admin-prefix>/id-cfg?type=openid&name=dex_test
|
// This cannot give an error at this point.
|
||||||
//
|
subSysTargets, _ := s.GetAvailableTargets(subSys)
|
||||||
// GetIdentityProviderCfg returns a list of configured IDPs on the server if
|
subSysTargetsSet := set.CreateStringSet(subSysTargets...)
|
||||||
// name is empty. If name is non-empty, returns the configuration details for
|
if isUpdate && !subSysTargetsSet.Contains(cfgTarget) {
|
||||||
// the IDP of the given type and configuration name. The configuration name for
|
return ErrAdminConfigIDPCfgNameDoesNotExist
|
||||||
// the default ("un-named") configuration target is `_`.
|
}
|
||||||
func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
if !isUpdate && subSysTargetsSet.Contains(cfgTarget) {
|
||||||
ctx := newContext(r, w, "GetIdentityProviderCfg")
|
return ErrAdminConfigIDPCfgNameAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
return ErrNone
|
||||||
|
}
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
// For the default configuration name, since it will always be an available
|
||||||
|
// target, we need to check if a configuration value has been set previously
|
||||||
|
// to figure out if this is a valid create or update API call.
|
||||||
|
|
||||||
|
// This cannot really error (FIXME: improve the type for GetConfigInfo)
|
||||||
|
var cfgInfos []madmin.IDPCfgInfo
|
||||||
|
switch subSys {
|
||||||
|
case madmin.IdentityOpenIDSubSys:
|
||||||
|
cfgInfos, _ = globalIAMSys.OpenIDConfig.GetConfigInfo(s, cfgTarget)
|
||||||
|
case madmin.IdentityLDAPSubSys:
|
||||||
|
cfgInfos, _ = globalIAMSys.LDAPConfig.GetConfigInfo(s, cfgTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfgInfos) > 0 && !isUpdate {
|
||||||
|
return ErrAdminConfigIDPCfgNameAlreadyExists
|
||||||
|
}
|
||||||
|
if len(cfgInfos) == 0 && isUpdate {
|
||||||
|
return ErrAdminConfigIDPCfgNameDoesNotExist
|
||||||
|
}
|
||||||
|
return ErrNone
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddIdentityProviderCfg: adds a new IDP config for openid/ldap.
|
||||||
|
//
|
||||||
|
// PUT <admin-prefix>/idp-cfg/openid/dex1 -> create named config `dex1`
|
||||||
|
//
|
||||||
|
// PUT <admin-prefix>/idp-cfg/openid/_ -> create (default) named config `_`
|
||||||
|
func (a adminAPIHandlers) AddIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
addOrUpdateIDPHandler(ctx, w, r, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateIdentityProviderCfg: updates an existing IDP config for openid/ldap.
|
||||||
|
//
|
||||||
|
// POST <admin-prefix>/idp-cfg/openid/dex1 -> update named config `dex1`
|
||||||
|
//
|
||||||
|
// POST <admin-prefix>/idp-cfg/openid/_ -> update (default) named config `_`
|
||||||
|
func (a adminAPIHandlers) UpdateIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
addOrUpdateIDPHandler(ctx, w, r, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListIdentityProviderCfg:
|
||||||
|
//
|
||||||
|
// GET <admin-prefix>/idp-cfg/openid -> lists openid provider configs.
|
||||||
|
func (a adminAPIHandlers) ListIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgType := mux.Vars(r)["type"]
|
|
||||||
cfgName := r.Form.Get("name")
|
|
||||||
password := cred.SecretKey
|
password := cred.SecretKey
|
||||||
|
|
||||||
if !idCfgTypes.Contains(cfgType) {
|
idpCfgType := mux.Vars(r)["type"]
|
||||||
// TODO: change this to invalid type error when implementation
|
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||||
// is complete.
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no cfgName is provided, we list.
|
var cfgList []madmin.IDPListItem
|
||||||
if cfgName == "" {
|
var err error
|
||||||
a.listIdentityProviders(ctx, w, r, cfgType, password)
|
switch idpCfgType {
|
||||||
return
|
case madmin.OpenidIDPCfg:
|
||||||
}
|
cfg := globalServerConfig.Clone()
|
||||||
|
cfgList, err = globalIAMSys.OpenIDConfig.GetConfigList(cfg)
|
||||||
|
case madmin.LDAPIDPCfg:
|
||||||
|
cfg := globalServerConfig.Clone()
|
||||||
|
cfgList, err = globalIAMSys.LDAPConfig.GetConfigList(cfg)
|
||||||
|
|
||||||
cfg := globalServerConfig.Clone()
|
|
||||||
|
|
||||||
cfgInfos, err := globalOpenIDConfig.GetConfigInfo(cfg, cfgName)
|
|
||||||
if err != nil {
|
|
||||||
if err == openid.ErrProviderConfigNotFound {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
res := madmin.IDPConfig{
|
|
||||||
Type: cfgType,
|
|
||||||
Name: cfgName,
|
|
||||||
Info: cfgInfos,
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(res)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
econfigData, err := madmin.EncryptData(password, data)
|
|
||||||
if err != nil {
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
writeSuccessResponseJSON(w, econfigData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a adminAPIHandlers) listIdentityProviders(ctx context.Context, w http.ResponseWriter, r *http.Request, cfgType, password string) {
|
|
||||||
// var subSys string
|
|
||||||
switch cfgType {
|
|
||||||
case "openid":
|
|
||||||
// subSys = config.IdentityOpenIDSubSys
|
|
||||||
default:
|
default:
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := globalServerConfig.Clone()
|
|
||||||
cfgList, err := globalOpenIDConfig.GetConfigList(cfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
@@ -238,33 +267,37 @@ func (a adminAPIHandlers) listIdentityProviders(ctx context.Context, w http.Resp
|
|||||||
writeSuccessResponseJSON(w, econfigData)
|
writeSuccessResponseJSON(w, econfigData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteIdentityProviderCfg:
|
// GetIdentityProviderCfg:
|
||||||
//
|
//
|
||||||
// DELETE <admin-prefix>/id-cfg?type=openid&name=dex_test
|
// GET <admin-prefix>/idp-cfg/openid/dex_test
|
||||||
func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) GetIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "DeleteIdentityProviderCfg")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ConfigUpdateAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfgType := mux.Vars(r)["type"]
|
idpCfgType := mux.Vars(r)["type"]
|
||||||
cfgName := mux.Vars(r)["name"]
|
cfgName := mux.Vars(r)["name"]
|
||||||
if !idCfgTypes.Contains(cfgType) {
|
password := cred.SecretKey
|
||||||
// TODO: change this to invalid type error when implementation
|
|
||||||
// is complete.
|
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := globalServerConfig.Clone()
|
cfg := globalServerConfig.Clone()
|
||||||
|
var cfgInfos []madmin.IDPCfgInfo
|
||||||
cfgInfos, err := globalOpenIDConfig.GetConfigInfo(cfg, cfgName)
|
var err error
|
||||||
|
switch idpCfgType {
|
||||||
|
case madmin.OpenidIDPCfg:
|
||||||
|
cfgInfos, err = globalIAMSys.OpenIDConfig.GetConfigInfo(cfg, cfgName)
|
||||||
|
case madmin.LDAPIDPCfg:
|
||||||
|
cfgInfos, err = globalIAMSys.LDAPConfig.GetConfigInfo(cfg, cfgName)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == openid.ErrProviderConfigNotFound {
|
if errors.Is(err, openid.ErrProviderConfigNotFound) || errors.Is(err, cfgldap.ErrProviderConfigNotFound) {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -273,39 +306,127 @@ func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hasEnv := false
|
res := madmin.IDPConfig{
|
||||||
for _, ci := range cfgInfos {
|
Type: idpCfgType,
|
||||||
if ci.IsCfg && ci.IsEnv {
|
Name: cfgName,
|
||||||
hasEnv = true
|
Info: cfgInfos,
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
data, err := json.Marshal(res)
|
||||||
if hasEnv {
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigEnvOverridden), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var subSys string
|
|
||||||
switch cfgType {
|
|
||||||
case "openid":
|
|
||||||
subSys = config.IdentityOpenIDSubSys
|
|
||||||
default:
|
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err = readServerConfig(ctx, objectAPI)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = cfg.DelKVS(fmt.Sprintf("%s:%s", subSys, cfgName)); err != nil {
|
econfigData, err := madmin.EncryptData(password, data)
|
||||||
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = validateConfig(cfg, subSys); err != nil {
|
|
||||||
|
writeSuccessResponseJSON(w, econfigData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteIdentityProviderCfg:
|
||||||
|
//
|
||||||
|
// DELETE <admin-prefix>/idp-cfg/openid/dex_test
|
||||||
|
func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ConfigUpdateAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idpCfgType := mux.Vars(r)["type"]
|
||||||
|
cfgName := mux.Vars(r)["name"]
|
||||||
|
if !madmin.ValidIDPConfigTypes.Contains(idpCfgType) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigInvalidIDPType), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgCopy := globalServerConfig.Clone()
|
||||||
|
var subSys string
|
||||||
|
switch idpCfgType {
|
||||||
|
case madmin.OpenidIDPCfg:
|
||||||
|
subSys = config.IdentityOpenIDSubSys
|
||||||
|
cfgInfos, err := globalIAMSys.OpenIDConfig.GetConfigInfo(cfgCopy, cfgName)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, openid.ErrProviderConfigNotFound) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hasEnv := false
|
||||||
|
for _, ci := range cfgInfos {
|
||||||
|
if ci.IsCfg && ci.IsEnv {
|
||||||
|
hasEnv = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasEnv {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigEnvOverridden), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case madmin.LDAPIDPCfg:
|
||||||
|
subSys = config.IdentityLDAPSubSys
|
||||||
|
cfgInfos, err := globalIAMSys.LDAPConfig.GetConfigInfo(cfgCopy, cfgName)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, openid.ErrProviderConfigNotFound) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminNoSuchConfigTarget), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hasEnv := false
|
||||||
|
for _, ci := range cfgInfos {
|
||||||
|
if ci.IsCfg && ci.IsEnv {
|
||||||
|
hasEnv = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasEnv {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigEnvOverridden), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := readServerConfig(ctx, objectAPI, nil)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgKey := fmt.Sprintf("%s:%s", subSys, cfgName)
|
||||||
|
if cfgName == madmin.Default {
|
||||||
|
cfgKey = subSys
|
||||||
|
}
|
||||||
|
if err = cfg.DelKVS(cfgKey); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = validateConfig(ctx, cfg, subSys); err != nil {
|
||||||
|
|
||||||
|
var validationErr ldap.Validation
|
||||||
|
if errors.As(err, &validationErr) {
|
||||||
|
// If we got an LDAP validation error, we need to send appropriate
|
||||||
|
// error message back to client (likely mc).
|
||||||
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigLDAPValidation),
|
||||||
|
validationErr.FormatError(), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/minio/madmin-go/v3"
|
||||||
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/mux"
|
||||||
|
"github.com/minio/pkg/v2/policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies.
|
||||||
|
//
|
||||||
|
// GET <admin-prefix>/idp/ldap/policy-entities?[query-params]
|
||||||
|
//
|
||||||
|
// Query params:
|
||||||
|
//
|
||||||
|
// user=... -> repeatable query parameter, specifying users to query for
|
||||||
|
// policy mapping
|
||||||
|
//
|
||||||
|
// group=... -> repeatable query parameter, specifying groups to query for
|
||||||
|
// policy mapping
|
||||||
|
//
|
||||||
|
// policy=... -> repeatable query parameter, specifying policy to query for
|
||||||
|
// user/group mapping
|
||||||
|
//
|
||||||
|
// When all query parameters are omitted, returns mappings for all policies.
|
||||||
|
func (a adminAPIHandlers) ListLDAPPolicyMappingEntities(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
// Check authorization.
|
||||||
|
|
||||||
|
objectAPI, cred := validateAdminReq(ctx, w, r,
|
||||||
|
policy.ListGroupsAdminAction, policy.ListUsersAdminAction, policy.ListUserPoliciesAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate API arguments.
|
||||||
|
|
||||||
|
q := madmin.PolicyEntitiesQuery{
|
||||||
|
Users: r.Form["user"],
|
||||||
|
Groups: r.Form["group"],
|
||||||
|
Policy: r.Form["policy"],
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query IAM
|
||||||
|
|
||||||
|
res, err := globalIAMSys.QueryLDAPPolicyEntities(r.Context(), q)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode result and send response.
|
||||||
|
|
||||||
|
data, err := json.Marshal(res)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
password := cred.SecretKey
|
||||||
|
econfigData, err := madmin.EncryptData(password, data)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseJSON(w, econfigData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachDetachPolicyLDAP attaches or detaches policies from an LDAP entity
|
||||||
|
// (user or group).
|
||||||
|
//
|
||||||
|
// POST <admin-prefix>/idp/ldap/policy/{operation}
|
||||||
|
func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
// Check authorization.
|
||||||
|
|
||||||
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.UpdatePolicyAssociationAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.ContentLength > maxEConfigJSONSize || r.ContentLength == -1 {
|
||||||
|
// More than maxConfigSize bytes were available
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigTooLarge), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure body content type is opaque to ensure that request body has not
|
||||||
|
// been interpreted as form data.
|
||||||
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
if contentType != "application/octet-stream" {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate operation
|
||||||
|
operation := mux.Vars(r)["operation"]
|
||||||
|
if operation != "attach" && operation != "detach" {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminInvalidArgument), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isAttach := operation == "attach"
|
||||||
|
|
||||||
|
// Validate API arguments in body.
|
||||||
|
password := cred.SecretKey
|
||||||
|
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err, logger.Application)
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var par madmin.PolicyAssociationReq
|
||||||
|
err = json.Unmarshal(reqBytes, &par)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := par.IsValid(); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call IAM subsystem
|
||||||
|
updatedAt, addedOrRemoved, _, err := globalIAMSys.PolicyDBUpdateLDAP(ctx, isAttach, par)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody := madmin.PolicyAssociationResp{
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}
|
||||||
|
if isAttach {
|
||||||
|
respBody.PoliciesAttached = addedOrRemoved
|
||||||
|
} else {
|
||||||
|
respBody.PoliciesDetached = addedOrRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(respBody)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedData, err := madmin.EncryptData(password, data)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, encryptedData)
|
||||||
|
}
|
||||||
+187
-27
@@ -19,20 +19,25 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/mux"
|
||||||
|
"github.com/minio/pkg/v2/policy"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errRebalanceDecommissionAlreadyRunning = errors.New("Rebalance cannot be started, decommission is already in progress")
|
||||||
|
errDecommissionRebalanceAlreadyRunning = errors.New("Decommission cannot be started, rebalance is already in progress")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "StartDecommission")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.DecommissionAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DecommissionAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -43,23 +48,53 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pools, ok := objectAPI.(*erasureServerPools)
|
z, ok := objectAPI.(*erasureServerPools)
|
||||||
if !ok {
|
if !ok || len(z.serverPools) == 1 {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if z.IsDecommissionRunning() {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errDecommissionAlreadyRunning), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if z.IsRebalanceStarted() {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceAlreadyStarted), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
v := vars["pool"]
|
v := vars["pool"]
|
||||||
|
|
||||||
idx := globalEndpoints.GetPoolIdx(v)
|
pools := strings.Split(v, ",")
|
||||||
if idx == -1 {
|
poolIndices := make([]int, 0, len(pools))
|
||||||
// We didn't find any matching pools, invalid input
|
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
for _, pool := range pools {
|
||||||
return
|
idx := globalEndpoints.GetPoolIdx(pool)
|
||||||
|
if idx == -1 {
|
||||||
|
// We didn't find any matching pools, invalid input
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var pool *erasureSets
|
||||||
|
for pidx := range z.serverPools {
|
||||||
|
if pidx == idx {
|
||||||
|
pool = z.serverPools[idx]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pool == nil {
|
||||||
|
// We didn't find any matching pools, invalid input
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
poolIndices = append(poolIndices, idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ep := globalEndpoints[idx].Endpoints[0]; !ep.IsLocal {
|
if len(poolIndices) > 0 && !globalEndpoints[poolIndices[0]].Endpoints[0].IsLocal {
|
||||||
|
ep := globalEndpoints[poolIndices[0]].Endpoints[0]
|
||||||
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
||||||
if proxyEp.Endpoint.Host == ep.Host {
|
if proxyEp.Endpoint.Host == ep.Host {
|
||||||
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
||||||
@@ -69,18 +104,16 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pools.Decommission(r.Context(), idx); err != nil {
|
if err := z.Decommission(r.Context(), poolIndices...); err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "CancelDecommission")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.DecommissionAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.DecommissionAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -124,11 +157,9 @@ func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "StatusPool")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerInfoAdminAction, policy.DecommissionAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction, iampolicy.DecommissionAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -167,11 +198,9 @@ func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "ListPools")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerInfoAdminAction, policy.DecommissionAdminAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.ServerInfoAdminAction, iampolicy.DecommissionAdminAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -200,3 +229,134 @@ func (a adminAPIHandlers) ListPools(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
logger.LogIf(r.Context(), json.NewEncoder(w).Encode(poolsStatus))
|
logger.LogIf(r.Context(), json.NewEncoder(w).Encode(poolsStatus))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a adminAPIHandlers) RebalanceStart(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB rebalance-start admin API is always coordinated from first pool's
|
||||||
|
// first node. The following is required to serialize (the effects of)
|
||||||
|
// concurrent rebalance-start commands.
|
||||||
|
if ep := globalEndpoints[0].Endpoints[0]; !ep.IsLocal {
|
||||||
|
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
||||||
|
if proxyEp.Endpoint.Host == ep.Host {
|
||||||
|
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pools, ok := objectAPI.(*erasureServerPools)
|
||||||
|
if !ok || len(pools.serverPools) == 1 {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pools.IsDecommissionRunning() {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errRebalanceDecommissionAlreadyRunning), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pools.IsRebalanceStarted() {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceAlreadyStarted), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketInfos, err := objectAPI.ListBuckets(ctx, BucketOptions{})
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buckets := make([]string, 0, len(bucketInfos))
|
||||||
|
for _, bInfo := range bucketInfos {
|
||||||
|
buckets = append(buckets, bInfo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
var id string
|
||||||
|
if id, err = pools.initRebalanceMeta(ctx, buckets); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebalance routine is run on the first node of any pool participating in rebalance.
|
||||||
|
pools.StartRebalance()
|
||||||
|
|
||||||
|
b, err := json.Marshal(struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}{ID: id})
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccessResponseJSON(w, b)
|
||||||
|
// Notify peers to load rebalance.bin and start rebalance routine if they happen to be
|
||||||
|
// participating pool's leader node
|
||||||
|
globalNotificationSys.LoadRebalanceMeta(ctx, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a adminAPIHandlers) RebalanceStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxy rebalance-status to first pool first node, so that users see a
|
||||||
|
// consistent view of rebalance progress even though different rebalancing
|
||||||
|
// pools may temporarily have out of date info on the others.
|
||||||
|
if ep := globalEndpoints[0].Endpoints[0]; !ep.IsLocal {
|
||||||
|
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
||||||
|
if proxyEp.Endpoint.Host == ep.Host {
|
||||||
|
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pools, ok := objectAPI.(*erasureServerPools)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, err := rebalanceStatus(ctx, pools)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, errRebalanceNotStarted) || errors.Is(err, errConfigNotFound) {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceNotStarted), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("failed to fetch rebalance status: %w", err))
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.LogIf(r.Context(), json.NewEncoder(w).Encode(rs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.RebalanceAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pools, ok := objectAPI.(*erasureServerPools)
|
||||||
|
if !ok {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel any ongoing rebalance operation
|
||||||
|
globalNotificationSys.StopRebalance(r.Context())
|
||||||
|
writeSuccessResponseHeadersOnly(w)
|
||||||
|
logger.LogIf(ctx, pools.saveRebalanceStats(GlobalContext, 0, rebalSaveStoppedAt))
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,27 +20,27 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/gob"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v3"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/mux"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add
|
// SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add
|
||||||
func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationAdd")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -72,11 +72,9 @@ func (a adminAPIHandlers) SiteReplicationAdd(w http.ResponseWriter, r *http.Requ
|
|||||||
// used internally to tell current cluster to enable SR with
|
// used internally to tell current cluster to enable SR with
|
||||||
// the provided peer clusters and service account.
|
// the provided peer clusters and service account.
|
||||||
func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SRPeerJoin")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -96,11 +94,9 @@ func (a adminAPIHandlers) SRPeerJoin(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// SRPeerBucketOps - PUT /minio/admin/v3/site-replication/bucket-ops?bucket=x&operation=y
|
// SRPeerBucketOps - PUT /minio/admin/v3/site-replication/bucket-ops?bucket=x&operation=y
|
||||||
func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SRPeerBucketOps")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationOperationAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -114,37 +110,23 @@ func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request
|
|||||||
default:
|
default:
|
||||||
err = errSRInvalidRequest(errInvalidArgument)
|
err = errSRInvalidRequest(errInvalidArgument)
|
||||||
case madmin.MakeWithVersioningBktOp:
|
case madmin.MakeWithVersioningBktOp:
|
||||||
_, isLockEnabled := r.Form["lockEnabled"]
|
createdAt, cerr := time.Parse(time.RFC3339Nano, strings.TrimSpace(r.Form.Get("createdAt")))
|
||||||
_, isVersioningEnabled := r.Form["versioningEnabled"]
|
|
||||||
_, isForceCreate := r.Form["forceCreate"]
|
|
||||||
createdAtStr := strings.TrimSpace(r.Form.Get("createdAt"))
|
|
||||||
createdAt, cerr := time.Parse(time.RFC3339Nano, createdAtStr)
|
|
||||||
if cerr != nil {
|
if cerr != nil {
|
||||||
createdAt = timeSentinel
|
createdAt = timeSentinel
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := MakeBucketOptions{
|
opts := MakeBucketOptions{
|
||||||
Location: r.Form.Get("location"),
|
LockEnabled: r.Form.Get("lockEnabled") == "true",
|
||||||
LockEnabled: isLockEnabled,
|
VersioningEnabled: r.Form.Get("versioningEnabled") == "true",
|
||||||
VersioningEnabled: isVersioningEnabled,
|
ForceCreate: r.Form.Get("forceCreate") == "true",
|
||||||
ForceCreate: isForceCreate,
|
|
||||||
CreatedAt: createdAt,
|
CreatedAt: createdAt,
|
||||||
}
|
}
|
||||||
err = globalSiteReplicationSys.PeerBucketMakeWithVersioningHandler(ctx, bucket, opts)
|
err = globalSiteReplicationSys.PeerBucketMakeWithVersioningHandler(ctx, bucket, opts)
|
||||||
case madmin.ConfigureReplBktOp:
|
case madmin.ConfigureReplBktOp:
|
||||||
err = globalSiteReplicationSys.PeerBucketConfigureReplHandler(ctx, bucket)
|
err = globalSiteReplicationSys.PeerBucketConfigureReplHandler(ctx, bucket)
|
||||||
case madmin.DeleteBucketBktOp:
|
case madmin.DeleteBucketBktOp, madmin.ForceDeleteBucketBktOp:
|
||||||
_, noRecreate := r.Form["noRecreate"]
|
|
||||||
err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, DeleteBucketOptions{
|
err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, DeleteBucketOptions{
|
||||||
Force: false,
|
Force: operation == madmin.ForceDeleteBucketBktOp,
|
||||||
NoRecreate: noRecreate,
|
|
||||||
SRDeleteOp: getSRBucketDeleteOp(true),
|
|
||||||
})
|
|
||||||
case madmin.ForceDeleteBucketBktOp:
|
|
||||||
_, noRecreate := r.Form["noRecreate"]
|
|
||||||
err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, DeleteBucketOptions{
|
|
||||||
Force: true,
|
|
||||||
NoRecreate: noRecreate,
|
|
||||||
SRDeleteOp: getSRBucketDeleteOp(true),
|
SRDeleteOp: getSRBucketDeleteOp(true),
|
||||||
})
|
})
|
||||||
case madmin.PurgeDeletedBucketOp:
|
case madmin.PurgeDeletedBucketOp:
|
||||||
@@ -159,11 +141,9 @@ func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request
|
|||||||
|
|
||||||
// SRPeerReplicateIAMItem - PUT /minio/admin/v3/site-replication/iam-item
|
// SRPeerReplicateIAMItem - PUT /minio/admin/v3/site-replication/iam-item
|
||||||
func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SRPeerReplicateIAMItem")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationOperationAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -182,7 +162,7 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
|
|||||||
if item.Policy == nil {
|
if item.Policy == nil {
|
||||||
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil, item.UpdatedAt)
|
err = globalSiteReplicationSys.PeerAddPolicyHandler(ctx, item.Name, nil, item.UpdatedAt)
|
||||||
} else {
|
} else {
|
||||||
policy, perr := iampolicy.ParseConfig(bytes.NewReader(item.Policy))
|
policy, perr := policy.ParseConfig(bytes.NewReader(item.Policy))
|
||||||
if perr != nil {
|
if perr != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, perr), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, perr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -213,11 +193,9 @@ func (a adminAPIHandlers) SRPeerReplicateIAMItem(w http.ResponseWriter, r *http.
|
|||||||
|
|
||||||
// SRPeerReplicateBucketItem - PUT /minio/admin/v3/site-replication/bucket-meta
|
// SRPeerReplicateBucketItem - PUT /minio/admin/v3/site-replication/bucket-meta
|
||||||
func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SRPeerReplicateBucketItem")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationOperationAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationOperationAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -228,15 +206,20 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if item.Bucket == "" {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errSRInvalidRequest(errInvalidArgument)), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
switch item.Type {
|
switch item.Type {
|
||||||
default:
|
default:
|
||||||
err = errSRInvalidRequest(errInvalidArgument)
|
err = globalSiteReplicationSys.PeerBucketMetadataUpdateHandler(ctx, item)
|
||||||
case madmin.SRBucketMetaTypePolicy:
|
case madmin.SRBucketMetaTypePolicy:
|
||||||
if item.Policy == nil {
|
if item.Policy == nil {
|
||||||
err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, nil, item.UpdatedAt)
|
err = globalSiteReplicationSys.PeerBucketPolicyHandler(ctx, item.Bucket, nil, item.UpdatedAt)
|
||||||
} else {
|
} else {
|
||||||
bktPolicy, berr := policy.ParseConfig(bytes.NewReader(item.Policy), item.Bucket)
|
bktPolicy, berr := policy.ParseBucketPolicyConfig(bytes.NewReader(item.Policy), item.Bucket)
|
||||||
if berr != nil {
|
if berr != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, berr), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, berr), r.URL)
|
||||||
return
|
return
|
||||||
@@ -257,7 +240,7 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = globalSiteReplicationSys.PeerBucketQuotaConfigHandler(ctx, item.Bucket, quotaConfig, item.UpdatedAt); err != nil {
|
if err = globalSiteReplicationSys.PeerBucketQuotaConfigHandler(ctx, item.Bucket, quotaConfig, item.UpdatedAt); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,11 +262,9 @@ func (a adminAPIHandlers) SRPeerReplicateBucketItem(w http.ResponseWriter, r *ht
|
|||||||
|
|
||||||
// SiteReplicationInfo - GET /minio/admin/v3/site-replication/info
|
// SiteReplicationInfo - GET /minio/admin/v3/site-replication/info
|
||||||
func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationInfo")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationInfoAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -301,11 +282,9 @@ func (a adminAPIHandlers) SiteReplicationInfo(w http.ResponseWriter, r *http.Req
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a adminAPIHandlers) SRPeerGetIDPSettings(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerGetIDPSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationGetIDPSettings")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -340,11 +319,9 @@ func parseJSONBody(ctx context.Context, body io.Reader, v interface{}, encryptio
|
|||||||
|
|
||||||
// SiteReplicationStatus - GET /minio/admin/v3/site-replication/status
|
// SiteReplicationStatus - GET /minio/admin/v3/site-replication/status
|
||||||
func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationStatus")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationInfoAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -371,11 +348,9 @@ func (a adminAPIHandlers) SiteReplicationStatus(w http.ResponseWriter, r *http.R
|
|||||||
|
|
||||||
// SiteReplicationMetaInfo - GET /minio/admin/v3/site-replication/metainfo
|
// SiteReplicationMetaInfo - GET /minio/admin/v3/site-replication/metainfo
|
||||||
func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationMetaInfo")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationInfoAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationInfoAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -395,10 +370,9 @@ func (a adminAPIHandlers) SiteReplicationMetaInfo(w http.ResponseWriter, r *http
|
|||||||
|
|
||||||
// SiteReplicationEdit - PUT /minio/admin/v3/site-replication/edit
|
// SiteReplicationEdit - PUT /minio/admin/v3/site-replication/edit
|
||||||
func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationEdit")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, cred := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
objectAPI, cred := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -427,10 +401,9 @@ func (a adminAPIHandlers) SiteReplicationEdit(w http.ResponseWriter, r *http.Req
|
|||||||
//
|
//
|
||||||
// used internally to tell current cluster to update endpoint for peer
|
// used internally to tell current cluster to update endpoint for peer
|
||||||
func (a adminAPIHandlers) SRPeerEdit(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerEdit(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SRPeerEdit")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationAddAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationAddAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -457,16 +430,15 @@ func getSRStatusOptions(r *http.Request) (opts madmin.SRStatusOptions) {
|
|||||||
opts.Entity = madmin.GetSREntityType(q.Get("entity"))
|
opts.Entity = madmin.GetSREntityType(q.Get("entity"))
|
||||||
opts.EntityValue = q.Get("entityvalue")
|
opts.EntityValue = q.Get("entityvalue")
|
||||||
opts.ShowDeleted = q.Get("showDeleted") == "true"
|
opts.ShowDeleted = q.Get("showDeleted") == "true"
|
||||||
|
opts.Metrics = q.Get("metrics") == "true"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SiteReplicationRemove - PUT /minio/admin/v3/site-replication/remove
|
// SiteReplicationRemove - PUT /minio/admin/v3/site-replication/remove
|
||||||
func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SiteReplicationRemove")
|
ctx := r.Context()
|
||||||
|
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationRemoveAction)
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationRemoveAction)
|
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -495,10 +467,9 @@ func (a adminAPIHandlers) SiteReplicationRemove(w http.ResponseWriter, r *http.R
|
|||||||
//
|
//
|
||||||
// used internally to tell current cluster to update endpoint for peer
|
// used internally to tell current cluster to update endpoint for peer
|
||||||
func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
|
func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := newContext(r, w, "SRPeerRemove")
|
ctx := r.Context()
|
||||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
|
||||||
|
|
||||||
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.SiteReplicationRemoveAction)
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationRemoveAction)
|
||||||
if objectAPI == nil {
|
if objectAPI == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -515,3 +486,85 @@ func (a adminAPIHandlers) SRPeerRemove(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SiteReplicationResyncOp - PUT /minio/admin/v3/site-replication/resync/op
|
||||||
|
func (a adminAPIHandlers) SiteReplicationResyncOp(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, policy.SiteReplicationResyncAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var peerSite madmin.PeerInfo
|
||||||
|
if err := parseJSONBody(ctx, r.Body, &peerSite, ""); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
op := madmin.SiteResyncOp(vars["operation"])
|
||||||
|
var (
|
||||||
|
status madmin.SRResyncOpStatus
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
switch op {
|
||||||
|
case madmin.SiteResyncStart:
|
||||||
|
status, err = globalSiteReplicationSys.startResync(ctx, objectAPI, peerSite)
|
||||||
|
case madmin.SiteResyncCancel:
|
||||||
|
status, err = globalSiteReplicationSys.cancelResync(ctx, objectAPI, peerSite)
|
||||||
|
default:
|
||||||
|
err = errSRInvalidRequest(errInvalidArgument)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(status)
|
||||||
|
if err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSuccessResponseJSON(w, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SiteReplicationDevNull - everything goes to io.Discard
|
||||||
|
// [POST] /minio/admin/v3/site-replication/devnull
|
||||||
|
func (a adminAPIHandlers) SiteReplicationDevNull(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
globalSiteNetPerfRX.Connect()
|
||||||
|
defer globalSiteNetPerfRX.Disconnect()
|
||||||
|
|
||||||
|
connectTime := time.Now()
|
||||||
|
for {
|
||||||
|
n, err := io.CopyN(io.Discard, r.Body, 128*humanize.KiByte)
|
||||||
|
atomic.AddUint64(&globalSiteNetPerfRX.RX, uint64(n))
|
||||||
|
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||||
|
// If there is a disconnection before globalNetPerfMinDuration (we give a margin of error of 1 sec)
|
||||||
|
// would mean the network is not stable. Logging here will help in debugging network issues.
|
||||||
|
if time.Since(connectTime) < (globalNetPerfMinDuration - time.Second) {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SiteReplicationNetPerf - everything goes to io.Discard
|
||||||
|
// [POST] /minio/admin/v3/site-replication/netperf
|
||||||
|
func (a adminAPIHandlers) SiteReplicationNetPerf(w http.ResponseWriter, r *http.Request) {
|
||||||
|
durationStr := r.Form.Get(peerRESTDuration)
|
||||||
|
duration, _ := time.ParseDuration(durationStr)
|
||||||
|
if duration < globalNetPerfMinDuration {
|
||||||
|
duration = globalNetPerfMinDuration
|
||||||
|
}
|
||||||
|
result := siteNetperf(r.Context(), duration)
|
||||||
|
logger.LogIf(r.Context(), gob.NewEncoder(w).Encode(result))
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v3"
|
||||||
minio "github.com/minio/minio-go/v7"
|
minio "github.com/minio/minio-go/v7"
|
||||||
|
"github.com/minio/pkg/v2/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) {
|
func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) {
|
||||||
@@ -129,18 +129,21 @@ func (s *TestSuiteIAM) TestDeleteUserRace(c *check) {
|
|||||||
secretKeys[i] = secretKey
|
secretKeys[i] = secretKey
|
||||||
}
|
}
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
g := errgroup.Group{}
|
||||||
for i := 0; i < userCount; i++ {
|
for i := 0; i < userCount; i++ {
|
||||||
wg.Add(1)
|
g.Go(func(i int) func() error {
|
||||||
go func(i int) {
|
return func() error {
|
||||||
defer wg.Done()
|
uClient := s.getUserClient(c, accessKeys[i], secretKeys[i], "")
|
||||||
uClient := s.getUserClient(c, accessKeys[i], secretKeys[i], "")
|
err := s.adm.RemoveUser(ctx, accessKeys[i])
|
||||||
err := s.adm.RemoveUser(ctx, accessKeys[i])
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
c.Fatalf("unable to remove user: %v", err)
|
}
|
||||||
|
c.mustNotListObjects(ctx, uClient, bucket)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
c.mustNotListObjects(ctx, uClient, bucket)
|
}(i), i)
|
||||||
}(i)
|
}
|
||||||
|
if errs := g.Wait(); len(errs) > 0 {
|
||||||
|
c.Fatalf("unable to remove users: %v", errs)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
|
||||||
}
|
}
|
||||||
|
|||||||
+559
-293
File diff suppressed because it is too large
Load Diff
@@ -27,20 +27,19 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
cr "github.com/minio/minio-go/v7/pkg/credentials"
|
|
||||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio-go/v7/pkg/signer"
|
"github.com/minio/minio-go/v7/pkg/signer"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
|
"github.com/minio/pkg/v2/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -122,7 +121,7 @@ var iamTestSuites = func() []*TestSuiteIAM {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EnvTestEtcdBackend = "ETCD_SERVER"
|
EnvTestEtcdBackend = "_MINIO_ETCD_TEST_SERVER"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *TestSuiteIAM) setUpEtcd(c *check, etcdServer string) {
|
func (s *TestSuiteIAM) setUpEtcd(c *check, etcdServer string) {
|
||||||
@@ -145,7 +144,7 @@ func (s *TestSuiteIAM) setUpEtcd(c *check, etcdServer string) {
|
|||||||
func (s *TestSuiteIAM) SetUpSuite(c *check) {
|
func (s *TestSuiteIAM) SetUpSuite(c *check) {
|
||||||
// If etcd backend is specified and etcd server is not present, the test
|
// If etcd backend is specified and etcd server is not present, the test
|
||||||
// is skipped.
|
// is skipped.
|
||||||
etcdServer := os.Getenv(EnvTestEtcdBackend)
|
etcdServer := env.Get(EnvTestEtcdBackend, "")
|
||||||
if s.withEtcdBackend && etcdServer == "" {
|
if s.withEtcdBackend && etcdServer == "" {
|
||||||
c.Skip("Skipping etcd backend IAM test as no etcd server is configured.")
|
c.Skip("Skipping etcd backend IAM test as no etcd server is configured.")
|
||||||
}
|
}
|
||||||
@@ -825,7 +824,7 @@ func (s *TestSuiteIAM) TestGroupAddRemove(c *check) {
|
|||||||
if set.CreateStringSet(groups...).Contains(group) {
|
if set.CreateStringSet(groups...).Contains(group) {
|
||||||
c.Fatalf("created group still present!")
|
c.Fatalf("created group still present!")
|
||||||
}
|
}
|
||||||
groupInfo, err = s.adm.GetGroupDescription(ctx, group)
|
_, err = s.adm.GetGroupDescription(ctx, group)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.Fatalf("group appears to exist")
|
c.Fatalf("group appears to exist")
|
||||||
}
|
}
|
||||||
@@ -877,7 +876,7 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByUser(c *check) {
|
|||||||
|
|
||||||
// Create an madmin client with user creds
|
// Create an madmin client with user creds
|
||||||
userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
||||||
Creds: cr.NewStaticV4(accessKey, secretKey, ""),
|
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
|
||||||
Secure: s.secure,
|
Secure: s.secure,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -984,9 +983,9 @@ func (s *TestSuiteIAM) SetUpAccMgmtPlugin(c *check) {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
pluginEndpoint := os.Getenv("POLICY_PLUGIN_ENDPOINT")
|
pluginEndpoint := env.Get("_MINIO_POLICY_PLUGIN_ENDPOINT", "")
|
||||||
if pluginEndpoint == "" {
|
if pluginEndpoint == "" {
|
||||||
c.Skip("POLICY_PLUGIN_ENDPOINT not given - skipping.")
|
c.Skip("_MINIO_POLICY_PLUGIN_ENDPOINT not given - skipping.")
|
||||||
}
|
}
|
||||||
|
|
||||||
configCmds := []string{
|
configCmds := []string{
|
||||||
@@ -1072,7 +1071,7 @@ func (s *TestSuiteIAM) TestAccMgmtPlugin(c *check) {
|
|||||||
|
|
||||||
// Create an madmin client with user creds
|
// Create an madmin client with user creds
|
||||||
userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
userAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
||||||
Creds: cr.NewStaticV4(accessKey, secretKey, ""),
|
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
|
||||||
Secure: s.secure,
|
Secure: s.secure,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1136,7 +1135,7 @@ func (s *TestSuiteIAM) TestAccMgmtPlugin(c *check) {
|
|||||||
c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket)
|
c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket)
|
||||||
|
|
||||||
// 6. Check that service account **can** be created for some other user.
|
// 6. Check that service account **can** be created for some other user.
|
||||||
// This is possible because of the policy enforced in the plugin.
|
// This is possible because the policy enforced in the plugin.
|
||||||
c.mustCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient)
|
c.mustCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1231,6 +1230,19 @@ func (c *check) mustGetObject(ctx context.Context, client *minio.Client, bucket,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *check) mustHeadObject(ctx context.Context, client *minio.Client, bucket, object string, tagCount int) {
|
||||||
|
c.Helper()
|
||||||
|
|
||||||
|
oinfo, err := client.StatObject(ctx, bucket, object, minio.StatObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("user was unable to download the object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if oinfo.UserTagCount != tagCount {
|
||||||
|
c.Fatalf("expected tagCount: %d, got %d", tagCount, oinfo.UserTagCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *check) mustListObjects(ctx context.Context, client *minio.Client, bucket string) {
|
func (c *check) mustListObjects(ctx context.Context, client *minio.Client, bucket string) {
|
||||||
c.Helper()
|
c.Helper()
|
||||||
res := client.ListObjects(ctx, bucket, minio.ListObjectsOptions{})
|
res := client.ListObjects(ctx, bucket, minio.ListObjectsOptions{})
|
||||||
@@ -1248,6 +1260,52 @@ func (c *check) mustListBuckets(ctx context.Context, client *minio.Client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *check) mustNotDelete(ctx context.Context, client *minio.Client, bucket string, vid string) {
|
||||||
|
c.Helper()
|
||||||
|
|
||||||
|
err := client.RemoveObject(ctx, bucket, "some-object", minio.RemoveObjectOptions{VersionID: vid})
|
||||||
|
if err == nil {
|
||||||
|
c.Fatalf("user must not be allowed to delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.RemoveObject(ctx, bucket, "some-object", minio.RemoveObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatal("user must be able to create delete marker")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *check) mustDownload(ctx context.Context, client *minio.Client, bucket string) {
|
||||||
|
c.Helper()
|
||||||
|
rd, err := client.GetObject(ctx, bucket, "some-object", minio.GetObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("download did not succeed got %#v", err)
|
||||||
|
}
|
||||||
|
if _, err = io.Copy(io.Discard, rd); err != nil {
|
||||||
|
c.Fatalf("download did not succeed got %#v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *check) mustUploadReturnVersions(ctx context.Context, client *minio.Client, bucket string) []string {
|
||||||
|
c.Helper()
|
||||||
|
versions := []string{}
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
ui, err := client.PutObject(ctx, bucket, "some-object", bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("upload did not succeed got %#v", err)
|
||||||
|
}
|
||||||
|
versions = append(versions, ui.VersionID)
|
||||||
|
}
|
||||||
|
return versions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *check) mustUpload(ctx context.Context, client *minio.Client, bucket string) {
|
||||||
|
c.Helper()
|
||||||
|
_, err := client.PutObject(ctx, bucket, "some-object", bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("upload did not succeed got %#v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *check) mustNotUpload(ctx context.Context, client *minio.Client, bucket string) {
|
func (c *check) mustNotUpload(ctx context.Context, client *minio.Client, bucket string) {
|
||||||
c.Helper()
|
c.Helper()
|
||||||
_, err := client.PutObject(ctx, bucket, "some-object", bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{})
|
_, err := client.PutObject(ctx, bucket, "some-object", bytes.NewBuffer([]byte("stuff")), 5, minio.PutObjectOptions{})
|
||||||
@@ -1270,7 +1328,11 @@ func (c *check) assertSvcAccAppearsInListing(ctx context.Context, madmClient *ma
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatalf("unable to list svc accounts: %v", err)
|
c.Fatalf("unable to list svc accounts: %v", err)
|
||||||
}
|
}
|
||||||
if !set.CreateStringSet(listResp.Accounts...).Contains(svcAK) {
|
var accessKeys []string
|
||||||
|
for _, item := range listResp.Accounts {
|
||||||
|
accessKeys = append(accessKeys, item.AccessKey)
|
||||||
|
}
|
||||||
|
if !set.CreateStringSet(accessKeys...).Contains(svcAK) {
|
||||||
c.Fatalf("service account did not appear in listing!")
|
c.Fatalf("service account did not appear in listing!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+630
-544
File diff suppressed because it is too large
Load Diff
+150
-5
@@ -21,17 +21,19 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/madmin-go"
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
|
"github.com/minio/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// adminErasureTestBed - encapsulates subsystems that need to be setup for
|
// adminErasureTestBed - encapsulates subsystems that need to be setup for
|
||||||
@@ -71,9 +73,9 @@ func prepareAdminErasureTestBed(ctx context.Context) (*adminErasureTestBed, erro
|
|||||||
// Initialize boot time
|
// Initialize boot time
|
||||||
globalBootTime = UTCNow()
|
globalBootTime = UTCNow()
|
||||||
|
|
||||||
globalEndpoints = mustGetPoolEndpoints(erasureDirs...)
|
globalEndpoints = mustGetPoolEndpoints(0, erasureDirs...)
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
initConfigSubsystem(ctx, objLayer)
|
initConfigSubsystem(ctx, objLayer)
|
||||||
|
|
||||||
@@ -106,7 +108,7 @@ func initTestErasureObjLayer(ctx context.Context) (ObjectLayer, []string, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
endpoints := mustGetPoolEndpoints(erasureDirs...)
|
endpoints := mustGetPoolEndpoints(0, erasureDirs...)
|
||||||
globalPolicySys = NewPolicySys()
|
globalPolicySys = NewPolicySys()
|
||||||
objLayer, err := newErasureServerPools(ctx, endpoints)
|
objLayer, err := newErasureServerPools(ctx, endpoints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -380,3 +382,146 @@ func TestExtractHealInitParams(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type byResourceUID struct{ madmin.LockEntries }
|
||||||
|
|
||||||
|
func (b byResourceUID) Less(i, j int) bool {
|
||||||
|
toUniqLock := func(entry madmin.LockEntry) string {
|
||||||
|
return fmt.Sprintf("%s/%s", entry.Resource, entry.ID)
|
||||||
|
}
|
||||||
|
return toUniqLock(b.LockEntries[i]) < toUniqLock(b.LockEntries[j])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTopLockEntries(t *testing.T) {
|
||||||
|
locksHeld := make(map[string][]lockRequesterInfo)
|
||||||
|
var owners []string
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
owners = append(owners, fmt.Sprintf("node-%d", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate DeleteObjects of 10 objects in a single request. i.e same lock
|
||||||
|
// request UID, but 10 different resource names associated with it.
|
||||||
|
var lris []lockRequesterInfo
|
||||||
|
uuid := mustGetUUID()
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
resource := fmt.Sprintf("bucket/delete-object-%d", i)
|
||||||
|
lri := lockRequesterInfo{
|
||||||
|
Name: resource,
|
||||||
|
Writer: true,
|
||||||
|
UID: uuid,
|
||||||
|
Owner: owners[i%len(owners)],
|
||||||
|
Group: true,
|
||||||
|
Quorum: 3,
|
||||||
|
}
|
||||||
|
lris = append(lris, lri)
|
||||||
|
locksHeld[resource] = []lockRequesterInfo{lri}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a few concurrent read locks to the mix
|
||||||
|
for i := 0; i < 50; i++ {
|
||||||
|
resource := fmt.Sprintf("bucket/get-object-%d", i)
|
||||||
|
lri := lockRequesterInfo{
|
||||||
|
Name: resource,
|
||||||
|
UID: mustGetUUID(),
|
||||||
|
Owner: owners[i%len(owners)],
|
||||||
|
Quorum: 2,
|
||||||
|
}
|
||||||
|
lris = append(lris, lri)
|
||||||
|
locksHeld[resource] = append(locksHeld[resource], lri)
|
||||||
|
// concurrent read lock, same resource different uid
|
||||||
|
lri.UID = mustGetUUID()
|
||||||
|
lris = append(lris, lri)
|
||||||
|
locksHeld[resource] = append(locksHeld[resource], lri)
|
||||||
|
}
|
||||||
|
|
||||||
|
var peerLocks []*PeerLocks
|
||||||
|
for _, owner := range owners {
|
||||||
|
peerLocks = append(peerLocks, &PeerLocks{
|
||||||
|
Addr: owner,
|
||||||
|
Locks: locksHeld,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var exp madmin.LockEntries
|
||||||
|
for _, lri := range lris {
|
||||||
|
lockType := func(lri lockRequesterInfo) string {
|
||||||
|
if lri.Writer {
|
||||||
|
return "WRITE"
|
||||||
|
}
|
||||||
|
return "READ"
|
||||||
|
}
|
||||||
|
exp = append(exp, madmin.LockEntry{
|
||||||
|
Resource: lri.Name,
|
||||||
|
Type: lockType(lri),
|
||||||
|
ServerList: owners,
|
||||||
|
Owner: lri.Owner,
|
||||||
|
ID: lri.UID,
|
||||||
|
Quorum: lri.Quorum,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
peerLocks []*PeerLocks
|
||||||
|
expected madmin.LockEntries
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
peerLocks: peerLocks,
|
||||||
|
expected: exp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// printEntries := func(entries madmin.LockEntries) {
|
||||||
|
// for i, entry := range entries {
|
||||||
|
// fmt.Printf("%d: %s %s %s %s %v %d\n", i, entry.Resource, entry.ID, entry.Owner, entry.Type, entry.ServerList, entry.Elapsed)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
check := func(exp, got madmin.LockEntries) (int, bool) {
|
||||||
|
if len(exp) != len(got) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
sort.Slice(exp, byResourceUID{exp}.Less)
|
||||||
|
sort.Slice(got, byResourceUID{got}.Less)
|
||||||
|
// printEntries(exp)
|
||||||
|
// printEntries(got)
|
||||||
|
for i, e := range exp {
|
||||||
|
if !e.Timestamp.Equal(got[i].Timestamp) {
|
||||||
|
return i, false
|
||||||
|
}
|
||||||
|
// Skip checking elapsed since it's time sensitive.
|
||||||
|
// if e.Elapsed != got[i].Elapsed {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
if e.Resource != got[i].Resource {
|
||||||
|
return i, false
|
||||||
|
}
|
||||||
|
if e.Type != got[i].Type {
|
||||||
|
return i, false
|
||||||
|
}
|
||||||
|
if e.Source != got[i].Source {
|
||||||
|
return i, false
|
||||||
|
}
|
||||||
|
if e.Owner != got[i].Owner {
|
||||||
|
return i, false
|
||||||
|
}
|
||||||
|
if e.ID != got[i].ID {
|
||||||
|
return i, false
|
||||||
|
}
|
||||||
|
if len(e.ServerList) != len(got[i].ServerList) {
|
||||||
|
return i, false
|
||||||
|
}
|
||||||
|
for j := range e.ServerList {
|
||||||
|
if e.ServerList[j] != got[i].ServerList[j] {
|
||||||
|
return i, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
got := topLockEntries(tc.peerLocks, false)
|
||||||
|
if idx, ok := check(tc.expected, got); !ok {
|
||||||
|
t.Fatalf("%d: mismatch at %d \n expected %#v but got %#v", i, idx, tc.expected[idx], got[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+40
-31
@@ -27,7 +27,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -100,14 +100,14 @@ type allHealState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newHealState - initialize global heal state management
|
// newHealState - initialize global heal state management
|
||||||
func newHealState(cleanup bool) *allHealState {
|
func newHealState(ctx context.Context, cleanup bool) *allHealState {
|
||||||
hstate := &allHealState{
|
hstate := &allHealState{
|
||||||
healSeqMap: make(map[string]*healSequence),
|
healSeqMap: make(map[string]*healSequence),
|
||||||
healLocalDisks: make(map[Endpoint]bool),
|
healLocalDisks: make(map[Endpoint]bool),
|
||||||
healStatus: make(map[string]healingTracker),
|
healStatus: make(map[string]healingTracker),
|
||||||
}
|
}
|
||||||
if cleanup {
|
if cleanup {
|
||||||
go hstate.periodicHealSeqsClean(GlobalContext)
|
go hstate.periodicHealSeqsClean(ctx)
|
||||||
}
|
}
|
||||||
return hstate
|
return hstate
|
||||||
}
|
}
|
||||||
@@ -176,11 +176,12 @@ func (ahs *allHealState) getHealLocalDiskEndpoints() Endpoints {
|
|||||||
return endpoints
|
return endpoints
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ahs *allHealState) markDiskForHealing(ep Endpoint) {
|
// Set, in the memory, the state of the disk as currently healing or not
|
||||||
|
func (ahs *allHealState) setDiskHealingStatus(ep Endpoint, healing bool) {
|
||||||
ahs.Lock()
|
ahs.Lock()
|
||||||
defer ahs.Unlock()
|
defer ahs.Unlock()
|
||||||
|
|
||||||
ahs.healLocalDisks[ep] = true
|
ahs.healLocalDisks[ep] = healing
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ahs *allHealState) pushHealLocalDisks(healLocalDisks ...Endpoint) {
|
func (ahs *allHealState) pushHealLocalDisks(healLocalDisks ...Endpoint) {
|
||||||
@@ -222,8 +223,8 @@ func (ahs *allHealState) periodicHealSeqsClean(ctx context.Context) {
|
|||||||
// getHealSequenceByToken - Retrieve a heal sequence by token. The second
|
// getHealSequenceByToken - Retrieve a heal sequence by token. The second
|
||||||
// argument returns if a heal sequence actually exists.
|
// argument returns if a heal sequence actually exists.
|
||||||
func (ahs *allHealState) getHealSequenceByToken(token string) (h *healSequence, exists bool) {
|
func (ahs *allHealState) getHealSequenceByToken(token string) (h *healSequence, exists bool) {
|
||||||
ahs.Lock()
|
ahs.RLock()
|
||||||
defer ahs.Unlock()
|
defer ahs.RUnlock()
|
||||||
for _, healSeq := range ahs.healSeqMap {
|
for _, healSeq := range ahs.healSeqMap {
|
||||||
if healSeq.clientToken == token {
|
if healSeq.clientToken == token {
|
||||||
return healSeq, true
|
return healSeq, true
|
||||||
@@ -235,8 +236,8 @@ func (ahs *allHealState) getHealSequenceByToken(token string) (h *healSequence,
|
|||||||
// getHealSequence - Retrieve a heal sequence by path. The second
|
// getHealSequence - Retrieve a heal sequence by path. The second
|
||||||
// argument returns if a heal sequence actually exists.
|
// argument returns if a heal sequence actually exists.
|
||||||
func (ahs *allHealState) getHealSequence(path string) (h *healSequence, exists bool) {
|
func (ahs *allHealState) getHealSequence(path string) (h *healSequence, exists bool) {
|
||||||
ahs.Lock()
|
ahs.RLock()
|
||||||
defer ahs.Unlock()
|
defer ahs.RUnlock()
|
||||||
h, exists = ahs.healSeqMap[path]
|
h, exists = ahs.healSeqMap[path]
|
||||||
return h, exists
|
return h, exists
|
||||||
}
|
}
|
||||||
@@ -252,7 +253,7 @@ func (ahs *allHealState) stopHealSequence(path string) ([]byte, APIError) {
|
|||||||
} else {
|
} else {
|
||||||
clientToken := he.clientToken
|
clientToken := he.clientToken
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
clientToken = fmt.Sprintf("%s@%d", he.clientToken, GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
clientToken = fmt.Sprintf("%s:%d", he.clientToken, GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
||||||
}
|
}
|
||||||
|
|
||||||
hsp = madmin.HealStopSuccess{
|
hsp = madmin.HealStopSuccess{
|
||||||
@@ -326,7 +327,7 @@ func (ahs *allHealState) LaunchNewHealSequence(h *healSequence, objAPI ObjectLay
|
|||||||
|
|
||||||
clientToken := h.clientToken
|
clientToken := h.clientToken
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
clientToken = fmt.Sprintf("%s@%d", h.clientToken, GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
clientToken = fmt.Sprintf("%s:%d", h.clientToken, GetProxyEndpointLocalIndex(globalProxyEndpoints))
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := json.Marshal(madmin.HealStartSuccess{
|
b, err := json.Marshal(madmin.HealStartSuccess{
|
||||||
@@ -396,6 +397,7 @@ type healSource struct {
|
|||||||
bucket string
|
bucket string
|
||||||
object string
|
object string
|
||||||
versionID string
|
versionID string
|
||||||
|
noWait bool // a non blocking call, if task queue is full return right away.
|
||||||
opts *madmin.HealOpts // optional heal option overrides default setting
|
opts *madmin.HealOpts // optional heal option overrides default setting
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,9 +407,6 @@ type healSequence struct {
|
|||||||
// bucket, and object on which heal seq. was initiated
|
// bucket, and object on which heal seq. was initiated
|
||||||
bucket, object string
|
bucket, object string
|
||||||
|
|
||||||
// A channel of entities with heal result
|
|
||||||
respCh chan healResult
|
|
||||||
|
|
||||||
// Report healing progress
|
// Report healing progress
|
||||||
reportProgress bool
|
reportProgress bool
|
||||||
|
|
||||||
@@ -470,7 +469,6 @@ func newHealSequence(ctx context.Context, bucket, objPrefix, clientAddr string,
|
|||||||
clientToken := mustGetUUID()
|
clientToken := mustGetUUID()
|
||||||
|
|
||||||
return &healSequence{
|
return &healSequence{
|
||||||
respCh: make(chan healResult),
|
|
||||||
bucket: bucket,
|
bucket: bucket,
|
||||||
object: objPrefix,
|
object: objPrefix,
|
||||||
reportProgress: true,
|
reportProgress: true,
|
||||||
@@ -685,13 +683,6 @@ func (h *healSequence) healSequenceStart(objAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) logHeal(healType madmin.HealItemType) {
|
|
||||||
h.mutex.Lock()
|
|
||||||
h.scannedItemsMap[healType]++
|
|
||||||
h.lastHealActivity = UTCNow()
|
|
||||||
h.mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
|
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
|
||||||
// Send heal request
|
// Send heal request
|
||||||
task := healTask{
|
task := healTask{
|
||||||
@@ -699,7 +690,6 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
object: source.object,
|
object: source.object,
|
||||||
versionID: source.versionID,
|
versionID: source.versionID,
|
||||||
opts: h.settings,
|
opts: h.settings,
|
||||||
respCh: h.respCh,
|
|
||||||
}
|
}
|
||||||
if source.opts != nil {
|
if source.opts != nil {
|
||||||
task.opts = *source.opts
|
task.opts = *source.opts
|
||||||
@@ -712,6 +702,24 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
h.lastHealActivity = UTCNow()
|
h.lastHealActivity = UTCNow()
|
||||||
h.mutex.Unlock()
|
h.mutex.Unlock()
|
||||||
|
|
||||||
|
if source.noWait {
|
||||||
|
select {
|
||||||
|
case globalBackgroundHealRoutine.tasks <- task:
|
||||||
|
if serverDebugLog {
|
||||||
|
logger.Info("Task in the queue: %#v", task)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// task queue is full, no more workers, we shall move on and heal later.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Don't wait for result
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// respCh must be set to wait for result.
|
||||||
|
// We make it size 1, so a result can always be written
|
||||||
|
// even if we aren't listening.
|
||||||
|
task.respCh = make(chan healResult, 1)
|
||||||
select {
|
select {
|
||||||
case globalBackgroundHealRoutine.tasks <- task:
|
case globalBackgroundHealRoutine.tasks <- task:
|
||||||
if serverDebugLog {
|
if serverDebugLog {
|
||||||
@@ -721,8 +729,9 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// task queued, now wait for the response.
|
||||||
select {
|
select {
|
||||||
case res := <-h.respCh:
|
case res := <-task.respCh:
|
||||||
if !h.reportProgress {
|
if !h.reportProgress {
|
||||||
if errors.Is(res.err, errSkipFile) { // this is only sent usually by nopHeal
|
if errors.Is(res.err, errSkipFile) { // this is only sent usually by nopHeal
|
||||||
return nil
|
return nil
|
||||||
@@ -768,6 +777,11 @@ func (h *healSequence) healDiskMeta(objAPI ObjectLayer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *healSequence) healItems(objAPI ObjectLayer, bucketsOnly bool) error {
|
func (h *healSequence) healItems(objAPI ObjectLayer, bucketsOnly bool) error {
|
||||||
|
if h.clientToken == bgHealingUUID {
|
||||||
|
// For background heal do nothing.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if err := h.healDiskMeta(objAPI); err != nil {
|
if err := h.healDiskMeta(objAPI); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -853,13 +867,8 @@ func (h *healSequence) healBucket(objAPI ObjectLayer, bucket string, bucketsOnly
|
|||||||
|
|
||||||
if !h.settings.Recursive {
|
if !h.settings.Recursive {
|
||||||
if h.object != "" {
|
if h.object != "" {
|
||||||
// Check if an object named as the objPrefix exists,
|
if err := h.healObject(bucket, h.object, ""); err != nil {
|
||||||
// and if so heal it.
|
return err
|
||||||
oi, err := objAPI.GetObjectInfo(h.ctx, bucket, h.object, ObjectOptions{})
|
|
||||||
if err == nil {
|
|
||||||
if err = h.healObject(bucket, h.object, oi.VersionID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+250
-113
@@ -19,20 +19,127 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/klauspost/compress/gzhttp"
|
"github.com/klauspost/compress/gzhttp"
|
||||||
"github.com/klauspost/compress/gzip"
|
"github.com/klauspost/compress/gzip"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
adminPathPrefix = minioReservedBucketPath + "/admin"
|
adminPathPrefix = minioReservedBucketPath + "/admin"
|
||||||
adminAPIVersion = madmin.AdminAPIVersion
|
adminAPIVersion = madmin.AdminAPIVersion
|
||||||
adminAPIVersionPrefix = SlashSeparator + adminAPIVersion
|
adminAPIVersionPrefix = SlashSeparator + adminAPIVersion
|
||||||
|
adminAPISiteReplicationDevNull = "/site-replication/devnull"
|
||||||
|
adminAPISiteReplicationNetPerf = "/site-replication/netperf"
|
||||||
|
adminAPIClientDevNull = "/speedtest/client/devnull"
|
||||||
|
adminAPIClientDevExtraTime = "/speedtest/client/devnull/extratime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var gzipHandler = func() func(http.Handler) http.HandlerFunc {
|
||||||
|
gz, err := gzhttp.NewWrapper(gzhttp.MinSize(1000), gzhttp.CompressionLevel(gzip.BestSpeed))
|
||||||
|
if err != nil {
|
||||||
|
// Static params, so this is very unlikely.
|
||||||
|
logger.Fatal(err, "Unable to initialize server")
|
||||||
|
}
|
||||||
|
return gz
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Set of handler options as bit flags
|
||||||
|
type hFlag uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// this flag disables gzip compression of responses
|
||||||
|
noGZFlag = 1 << iota
|
||||||
|
|
||||||
|
// this flag enables tracing body and headers instead of just headers
|
||||||
|
traceAllFlag
|
||||||
|
|
||||||
|
// pass this flag to skip checking if object layer is available
|
||||||
|
noObjLayerFlag
|
||||||
|
)
|
||||||
|
|
||||||
|
// Has checks if the the given flag is enabled in `h`.
|
||||||
|
func (h hFlag) Has(flag hFlag) bool {
|
||||||
|
// Use bitwise-AND and check if the result is non-zero.
|
||||||
|
return h&flag != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHandlerName(f http.HandlerFunc) string {
|
||||||
|
name := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
|
||||||
|
name = strings.TrimPrefix(name, "github.com/minio/minio/cmd.adminAPIHandlers.")
|
||||||
|
name = strings.TrimSuffix(name, "Handler-fm")
|
||||||
|
name = strings.TrimSuffix(name, "-fm")
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// adminMiddleware performs some common admin handler functionality for all
|
||||||
|
// handlers:
|
||||||
|
//
|
||||||
|
// - updates request context with `logger.ReqInfo` and api name based on the
|
||||||
|
// name of the function handler passed (this handler must be a method of
|
||||||
|
// `adminAPIHandlers`).
|
||||||
|
//
|
||||||
|
// - sets up call to send AuditLog
|
||||||
|
//
|
||||||
|
// Note that, while this is a middleware function (i.e. it takes a handler
|
||||||
|
// function and returns one), due to flags being passed based on required
|
||||||
|
// conditions, it is done per-"handler function registration" in the router.
|
||||||
|
//
|
||||||
|
// When no flags are passed, gzip compression, http tracing of headers and
|
||||||
|
// checking of object layer availability are all enabled. Use flags to modify
|
||||||
|
// this behavior.
|
||||||
|
func adminMiddleware(f http.HandlerFunc, flags ...hFlag) http.HandlerFunc {
|
||||||
|
// Collect all flags with bitwise-OR and assign operator
|
||||||
|
var handlerFlags hFlag
|
||||||
|
for _, flag := range flags {
|
||||||
|
handlerFlags |= flag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get name of the handler using reflection. NOTE: The passed in handler
|
||||||
|
// function must be a method of `adminAPIHandlers` for this extraction to
|
||||||
|
// work as expected.
|
||||||
|
handlerName := getHandlerName(f)
|
||||||
|
|
||||||
|
var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Update request context with `logger.ReqInfo`.
|
||||||
|
r = r.WithContext(newContext(r, w, handlerName))
|
||||||
|
|
||||||
|
defer logger.AuditLog(r.Context(), w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
// Check if object layer is available, if not return error early.
|
||||||
|
if !handlerFlags.Has(noObjLayerFlag) {
|
||||||
|
objectAPI := newObjectLayerFn()
|
||||||
|
if objectAPI == nil || globalNotificationSys == nil {
|
||||||
|
writeErrorResponseJSON(r.Context(), w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply http tracing "middleware" based on presence of flag.
|
||||||
|
var f2 http.HandlerFunc
|
||||||
|
if handlerFlags.Has(traceAllFlag) {
|
||||||
|
f2 = httpTraceAll(f)
|
||||||
|
} else {
|
||||||
|
f2 = httpTraceHdrs(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// call the final handler
|
||||||
|
f2(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable compression of responses based on presence of flag.
|
||||||
|
if !handlerFlags.Has(noGZFlag) {
|
||||||
|
handler = gzipHandler(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler
|
||||||
|
}
|
||||||
|
|
||||||
// adminAPIHandlers provides HTTP handlers for MinIO admin API.
|
// adminAPIHandlers provides HTTP handlers for MinIO admin API.
|
||||||
type adminAPIHandlers struct{}
|
type adminAPIHandlers struct{}
|
||||||
|
|
||||||
@@ -46,234 +153,264 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
adminAPIVersionPrefix,
|
adminAPIVersionPrefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
gz, err := gzhttp.NewWrapper(gzhttp.MinSize(1000), gzhttp.CompressionLevel(gzip.BestSpeed))
|
|
||||||
if err != nil {
|
|
||||||
// Static params, so this is very unlikely.
|
|
||||||
logger.Fatal(err, "Unable to initialize server")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, adminVersion := range adminVersions {
|
for _, adminVersion := range adminVersions {
|
||||||
// Restart and stop MinIO service.
|
// Restart and stop MinIO service.
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/service").HandlerFunc(gz(httpTraceAll(adminAPI.ServiceHandler))).Queries("action", "{action:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/service").HandlerFunc(adminMiddleware(adminAPI.ServiceHandler, traceAllFlag)).Queries("action", "{action:.*}")
|
||||||
// Update MinIO servers.
|
// Update MinIO servers.
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update").HandlerFunc(gz(httpTraceAll(adminAPI.ServerUpdateHandler))).Queries("updateURL", "{updateURL:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update").HandlerFunc(adminMiddleware(adminAPI.ServerUpdateHandler, traceAllFlag)).Queries("updateURL", "{updateURL:.*}")
|
||||||
|
|
||||||
// Info operations
|
// Info operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/info").HandlerFunc(gz(httpTraceAll(adminAPI.ServerInfoHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/info").HandlerFunc(adminMiddleware(adminAPI.ServerInfoHandler, traceAllFlag, noObjLayerFlag))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/inspect-data").HandlerFunc(httpTraceHdrs(adminAPI.InspectDataHandler)).Queries("volume", "{volume:.*}", "file", "{file:.*}")
|
adminRouter.Methods(http.MethodGet, http.MethodPost).Path(adminVersion + "/inspect-data").HandlerFunc(adminMiddleware(adminAPI.InspectDataHandler, noGZFlag, traceAllFlag))
|
||||||
|
|
||||||
// StorageInfo operations
|
// StorageInfo operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/storageinfo").HandlerFunc(gz(httpTraceAll(adminAPI.StorageInfoHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/storageinfo").HandlerFunc(adminMiddleware(adminAPI.StorageInfoHandler, traceAllFlag))
|
||||||
// DataUsageInfo operations
|
// DataUsageInfo operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/datausageinfo").HandlerFunc(gz(httpTraceAll(adminAPI.DataUsageInfoHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/datausageinfo").HandlerFunc(adminMiddleware(adminAPI.DataUsageInfoHandler, traceAllFlag))
|
||||||
// Metrics operation
|
// Metrics operation
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/metrics").HandlerFunc(gz(httpTraceAll(adminAPI.MetricsHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/metrics").HandlerFunc(adminMiddleware(adminAPI.MetricsHandler, traceAllFlag))
|
||||||
|
|
||||||
if globalIsDistErasure || globalIsErasure {
|
if globalIsDistErasure || globalIsErasure {
|
||||||
// Heal operations
|
// Heal operations
|
||||||
|
|
||||||
// Heal processing endpoint.
|
// Heal processing endpoint.
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/").HandlerFunc(gz(httpTraceAll(adminAPI.HealHandler)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/").HandlerFunc(adminMiddleware(adminAPI.HealHandler, traceAllFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}").HandlerFunc(gz(httpTraceAll(adminAPI.HealHandler)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}").HandlerFunc(adminMiddleware(adminAPI.HealHandler, traceAllFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}/{prefix:.*}").HandlerFunc(gz(httpTraceAll(adminAPI.HealHandler)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}/{prefix:.*}").HandlerFunc(adminMiddleware(adminAPI.HealHandler, traceAllFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/background-heal/status").HandlerFunc(gz(httpTraceAll(adminAPI.BackgroundHealStatusHandler)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/background-heal/status").HandlerFunc(adminMiddleware(adminAPI.BackgroundHealStatusHandler, traceAllFlag))
|
||||||
|
|
||||||
// Pool operations
|
// Pool operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/pools/list").HandlerFunc(gz(httpTraceAll(adminAPI.ListPools)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/pools/list").HandlerFunc(adminMiddleware(adminAPI.ListPools, traceAllFlag))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/pools/status").HandlerFunc(gz(httpTraceAll(adminAPI.StatusPool))).Queries("pool", "{pool:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/pools/status").HandlerFunc(adminMiddleware(adminAPI.StatusPool, traceAllFlag)).Queries("pool", "{pool:.*}")
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/decommission").HandlerFunc(gz(httpTraceAll(adminAPI.StartDecommission))).Queries("pool", "{pool:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/decommission").HandlerFunc(adminMiddleware(adminAPI.StartDecommission, traceAllFlag)).Queries("pool", "{pool:.*}")
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/cancel").HandlerFunc(gz(httpTraceAll(adminAPI.CancelDecommission))).Queries("pool", "{pool:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/cancel").HandlerFunc(adminMiddleware(adminAPI.CancelDecommission, traceAllFlag)).Queries("pool", "{pool:.*}")
|
||||||
|
|
||||||
|
// Rebalance operations
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/rebalance/start").HandlerFunc(adminMiddleware(adminAPI.RebalanceStart, traceAllFlag))
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/rebalance/status").HandlerFunc(adminMiddleware(adminAPI.RebalanceStatus, traceAllFlag))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/rebalance/stop").HandlerFunc(adminMiddleware(adminAPI.RebalanceStop, traceAllFlag))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profiling operations - deprecated API
|
// Profiling operations - deprecated API
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/profiling/start").HandlerFunc(gz(httpTraceAll(adminAPI.StartProfilingHandler))).
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/profiling/start").HandlerFunc(adminMiddleware(adminAPI.StartProfilingHandler, traceAllFlag, noObjLayerFlag)).
|
||||||
Queries("profilerType", "{profilerType:.*}")
|
Queries("profilerType", "{profilerType:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/profiling/download").HandlerFunc(gz(httpTraceAll(adminAPI.DownloadProfilingHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/profiling/download").HandlerFunc(adminMiddleware(adminAPI.DownloadProfilingHandler, traceAllFlag, noObjLayerFlag))
|
||||||
// Profiling operations
|
// Profiling operations
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/profile").HandlerFunc(gz(httpTraceAll(adminAPI.ProfileHandler)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/profile").HandlerFunc(adminMiddleware(adminAPI.ProfileHandler, traceAllFlag, noObjLayerFlag))
|
||||||
|
|
||||||
// Config KV operations.
|
// Config KV operations.
|
||||||
if enableConfigOps {
|
if enableConfigOps {
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-config-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetConfigKVHandler))).Queries("key", "{key:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-config-kv").HandlerFunc(adminMiddleware(adminAPI.GetConfigKVHandler)).Queries("key", "{key:.*}")
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/set-config-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetConfigKVHandler)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/set-config-kv").HandlerFunc(adminMiddleware(adminAPI.SetConfigKVHandler))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/del-config-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.DelConfigKVHandler)))
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/del-config-kv").HandlerFunc(adminMiddleware(adminAPI.DelConfigKVHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable config help in all modes.
|
// Enable config help in all modes.
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/help-config-kv").HandlerFunc(gz(httpTraceAll(adminAPI.HelpConfigKVHandler))).Queries("subSys", "{subSys:.*}", "key", "{key:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/help-config-kv").HandlerFunc(adminMiddleware(adminAPI.HelpConfigKVHandler, traceAllFlag)).Queries("subSys", "{subSys:.*}", "key", "{key:.*}")
|
||||||
|
|
||||||
// Config KV history operations.
|
// Config KV history operations.
|
||||||
if enableConfigOps {
|
if enableConfigOps {
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-config-history-kv").HandlerFunc(gz(httpTraceAll(adminAPI.ListConfigHistoryKVHandler))).Queries("count", "{count:[0-9]+}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-config-history-kv").HandlerFunc(adminMiddleware(adminAPI.ListConfigHistoryKVHandler, traceAllFlag)).Queries("count", "{count:[0-9]+}")
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/clear-config-history-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.ClearConfigHistoryKVHandler))).Queries("restoreId", "{restoreId:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/clear-config-history-kv").HandlerFunc(adminMiddleware(adminAPI.ClearConfigHistoryKVHandler)).Queries("restoreId", "{restoreId:.*}")
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/restore-config-history-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.RestoreConfigHistoryKVHandler))).Queries("restoreId", "{restoreId:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/restore-config-history-kv").HandlerFunc(adminMiddleware(adminAPI.RestoreConfigHistoryKVHandler)).Queries("restoreId", "{restoreId:.*}")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config import/export bulk operations
|
// Config import/export bulk operations
|
||||||
if enableConfigOps {
|
if enableConfigOps {
|
||||||
// Get config
|
// Get config
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/config").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetConfigHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/config").HandlerFunc(adminMiddleware(adminAPI.GetConfigHandler))
|
||||||
// Set config
|
// Set config
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/config").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetConfigHandler)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/config").HandlerFunc(adminMiddleware(adminAPI.SetConfigHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- IAM APIs --
|
// -- IAM APIs --
|
||||||
|
|
||||||
// Add policy IAM
|
// Add policy IAM
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-canned-policy").HandlerFunc(gz(httpTraceAll(adminAPI.AddCannedPolicy))).Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-canned-policy").HandlerFunc(adminMiddleware(adminAPI.AddCannedPolicy, traceAllFlag)).Queries("name", "{name:.*}")
|
||||||
|
|
||||||
// Add user IAM
|
// Add user IAM
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/accountinfo").HandlerFunc(gz(httpTraceAll(adminAPI.AccountInfoHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/accountinfo").HandlerFunc(adminMiddleware(adminAPI.AccountInfoHandler, traceAllFlag))
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-user").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddUser))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-user").HandlerFunc(adminMiddleware(adminAPI.AddUser)).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-status").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetUserStatus))).Queries("accessKey", "{accessKey:.*}").Queries("status", "{status:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-status").HandlerFunc(adminMiddleware(adminAPI.SetUserStatus)).Queries("accessKey", "{accessKey:.*}").Queries("status", "{status:.*}")
|
||||||
|
|
||||||
// Service accounts ops
|
// Service accounts ops
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/add-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddServiceAccount)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/add-service-account").HandlerFunc(adminMiddleware(adminAPI.AddServiceAccount))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.UpdateServiceAccount))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update-service-account").HandlerFunc(adminMiddleware(adminAPI.UpdateServiceAccount)).Queries("accessKey", "{accessKey:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.InfoServiceAccount))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-service-account").HandlerFunc(adminMiddleware(adminAPI.InfoServiceAccount)).Queries("accessKey", "{accessKey:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListServiceAccounts)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(adminMiddleware(adminAPI.ListServiceAccounts))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteServiceAccount))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(adminMiddleware(adminAPI.DeleteServiceAccount)).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
|
// STS accounts ops
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/temporary-account-info").HandlerFunc(adminMiddleware(adminAPI.TemporaryAccountInfo)).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
// Info policy IAM latest
|
// Info policy IAM latest
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.InfoCannedPolicy))).Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(adminMiddleware(adminAPI.InfoCannedPolicy)).Queries("name", "{name:.*}")
|
||||||
// List policies latest
|
// List policies latest
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListBucketPolicies))).Queries("bucket", "{bucket:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-canned-policies").HandlerFunc(adminMiddleware(adminAPI.ListBucketPolicies)).Queries("bucket", "{bucket:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListCannedPolicies)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(adminMiddleware(adminAPI.ListCannedPolicies))
|
||||||
|
|
||||||
|
// Builtin IAM policy associations
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/builtin/policy-entities").HandlerFunc(adminMiddleware(adminAPI.ListPolicyMappingEntities))
|
||||||
|
|
||||||
// Remove policy IAM
|
// Remove policy IAM
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveCannedPolicy))).Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-canned-policy").HandlerFunc(adminMiddleware(adminAPI.RemoveCannedPolicy)).Queries("name", "{name:.*}")
|
||||||
|
|
||||||
// Set user or group policy
|
// Set user or group policy
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-or-group-policy").
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-or-group-policy").
|
||||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.SetPolicyForUserOrGroup))).
|
HandlerFunc(adminMiddleware(adminAPI.SetPolicyForUserOrGroup)).
|
||||||
Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}")
|
Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}")
|
||||||
|
|
||||||
|
// Attach/Detach policies to/from user or group
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/builtin/policy/{operation}").HandlerFunc(adminMiddleware(adminAPI.AttachDetachPolicyBuiltin))
|
||||||
|
|
||||||
// Remove user IAM
|
// Remove user IAM
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveUser))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(adminMiddleware(adminAPI.RemoveUser)).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
// List users
|
// List users
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-users").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListBucketUsers))).Queries("bucket", "{bucket:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-users").HandlerFunc(adminMiddleware(adminAPI.ListBucketUsers)).Queries("bucket", "{bucket:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-users").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListUsers)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-users").HandlerFunc(adminMiddleware(adminAPI.ListUsers))
|
||||||
|
|
||||||
// User info
|
// User info
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/user-info").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetUserInfo))).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/user-info").HandlerFunc(adminMiddleware(adminAPI.GetUserInfo)).Queries("accessKey", "{accessKey:.*}")
|
||||||
// Add/Remove members from group
|
// Add/Remove members from group
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/update-group-members").HandlerFunc(gz(httpTraceHdrs(adminAPI.UpdateGroupMembers)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/update-group-members").HandlerFunc(adminMiddleware(adminAPI.UpdateGroupMembers))
|
||||||
|
|
||||||
// Get Group
|
// Get Group
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/group").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetGroup))).Queries("group", "{group:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/group").HandlerFunc(adminMiddleware(adminAPI.GetGroup)).Queries("group", "{group:.*}")
|
||||||
|
|
||||||
// List Groups
|
// List Groups
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/groups").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListGroups)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/groups").HandlerFunc(adminMiddleware(adminAPI.ListGroups))
|
||||||
|
|
||||||
// Set Group Status
|
// Set Group Status
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-group-status").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetGroupStatus))).Queries("group", "{group:.*}").Queries("status", "{status:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-group-status").HandlerFunc(adminMiddleware(adminAPI.SetGroupStatus)).Queries("group", "{group:.*}").Queries("status", "{status:.*}")
|
||||||
|
|
||||||
// Export IAM info to zipped file
|
// Export IAM info to zipped file
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-iam").HandlerFunc(httpTraceHdrs(adminAPI.ExportIAM))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-iam").HandlerFunc(adminMiddleware(adminAPI.ExportIAM, noGZFlag))
|
||||||
|
|
||||||
// Import IAM info
|
// Import IAM info
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-iam").HandlerFunc(httpTraceHdrs(adminAPI.ImportIAM))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-iam").HandlerFunc(adminMiddleware(adminAPI.ImportIAM, noGZFlag))
|
||||||
|
|
||||||
// IDentity Provider configuration APIs
|
// IDentity Provider configuration APIs
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/idp-config").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetIdentityProviderCfg))).Queries("type", "{type:.*}").Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.AddIdentityProviderCfg))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/idp-config").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetIdentityProviderCfg))).Queries("type", "{type:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.UpdateIdentityProviderCfg))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/idp-config").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteIdentityProviderCfg))).Queries("type", "{type:.*}").Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}").HandlerFunc(adminMiddleware(adminAPI.ListIdentityProviderCfg))
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.GetIdentityProviderCfg))
|
||||||
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.DeleteIdentityProviderCfg))
|
||||||
|
|
||||||
|
// LDAP IAM operations
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(adminMiddleware(adminAPI.ListLDAPPolicyMappingEntities))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/ldap/policy/{operation}").HandlerFunc(adminMiddleware(adminAPI.AttachDetachPolicyLDAP))
|
||||||
// -- END IAM APIs --
|
// -- END IAM APIs --
|
||||||
|
|
||||||
// GetBucketQuotaConfig
|
// GetBucketQuotaConfig
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-bucket-quota").HandlerFunc(
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-bucket-quota").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.GetBucketQuotaConfigHandler))).Queries("bucket", "{bucket:.*}")
|
adminMiddleware(adminAPI.GetBucketQuotaConfigHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
// PutBucketQuotaConfig
|
// PutBucketQuotaConfig
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-bucket-quota").HandlerFunc(
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-bucket-quota").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.PutBucketQuotaConfigHandler))).Queries("bucket", "{bucket:.*}")
|
adminMiddleware(adminAPI.PutBucketQuotaConfigHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
|
|
||||||
// Bucket replication operations
|
// Bucket replication operations
|
||||||
// GetBucketTargetHandler
|
// GetBucketTargetHandler
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-remote-targets").HandlerFunc(
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-remote-targets").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.ListRemoteTargetsHandler))).Queries("bucket", "{bucket:.*}", "type", "{type:.*}")
|
adminMiddleware(adminAPI.ListRemoteTargetsHandler)).Queries("bucket", "{bucket:.*}", "type", "{type:.*}")
|
||||||
// SetRemoteTargetHandler
|
// SetRemoteTargetHandler
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.SetRemoteTargetHandler))).Queries("bucket", "{bucket:.*}")
|
adminMiddleware(adminAPI.SetRemoteTargetHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
// RemoveRemoteTargetHandler
|
// RemoveRemoteTargetHandler
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler))).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
adminMiddleware(adminAPI.RemoveRemoteTargetHandler)).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
||||||
// ReplicationDiff - MinIO extension API
|
// ReplicationDiff - MinIO extension API
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/replication/diff").HandlerFunc(
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/replication/diff").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.ReplicationDiffHandler))).Queries("bucket", "{bucket:.*}")
|
adminMiddleware(adminAPI.ReplicationDiffHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
|
// ReplicationMRFHandler - MinIO extension API
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/replication/mrf").HandlerFunc(
|
||||||
|
adminMiddleware(adminAPI.ReplicationMRFHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
|
|
||||||
|
// Batch job operations
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/start-job").HandlerFunc(
|
||||||
|
adminMiddleware(adminAPI.StartBatchJob))
|
||||||
|
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-jobs").HandlerFunc(
|
||||||
|
adminMiddleware(adminAPI.ListBatchJobs))
|
||||||
|
|
||||||
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/describe-job").HandlerFunc(
|
||||||
|
adminMiddleware(adminAPI.DescribeBatchJob))
|
||||||
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/cancel-job").HandlerFunc(
|
||||||
|
adminMiddleware(adminAPI.CancelBatchJob))
|
||||||
|
|
||||||
// Bucket migration operations
|
// Bucket migration operations
|
||||||
// ExportBucketMetaHandler
|
// ExportBucketMetaHandler
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-bucket-metadata").HandlerFunc(
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/export-bucket-metadata").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.ExportBucketMetadataHandler)))
|
adminMiddleware(adminAPI.ExportBucketMetadataHandler))
|
||||||
// ImportBucketMetaHandler
|
// ImportBucketMetaHandler
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-bucket-metadata").HandlerFunc(
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/import-bucket-metadata").HandlerFunc(
|
||||||
gz(httpTraceHdrs(adminAPI.ImportBucketMetadataHandler)))
|
adminMiddleware(adminAPI.ImportBucketMetadataHandler))
|
||||||
|
|
||||||
// Remote Tier management operations
|
// Remote Tier management operations
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/tier").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddTierHandler)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/tier").HandlerFunc(adminMiddleware(adminAPI.AddTierHandler))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.EditTierHandler)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/tier/{tier}").HandlerFunc(adminMiddleware(adminAPI.EditTierHandler))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListTierHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier").HandlerFunc(adminMiddleware(adminAPI.ListTierHandler))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveTierHandler)))
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/tier/{tier}").HandlerFunc(adminMiddleware(adminAPI.RemoveTierHandler))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.VerifyTierHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier/{tier}").HandlerFunc(adminMiddleware(adminAPI.VerifyTierHandler))
|
||||||
// Tier stats
|
// Tier stats
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier-stats").HandlerFunc(gz(httpTraceHdrs(adminAPI.TierStatsHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier-stats").HandlerFunc(adminMiddleware(adminAPI.TierStatsHandler))
|
||||||
|
|
||||||
// Cluster Replication APIs
|
// Cluster Replication APIs
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/add").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationAdd)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/add").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationAdd))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/remove").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationRemove)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/remove").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationRemove))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/info").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationInfo)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/info").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationInfo))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/metainfo").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationMetaInfo)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/metainfo").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationMetaInfo))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/status").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationStatus)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/status").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationStatus))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + adminAPISiteReplicationDevNull).HandlerFunc(adminMiddleware(adminAPI.SiteReplicationDevNull, noObjLayerFlag))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + adminAPISiteReplicationNetPerf).HandlerFunc(adminMiddleware(adminAPI.SiteReplicationNetPerf, noObjLayerFlag))
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/join").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerJoin)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/join").HandlerFunc(adminMiddleware(adminAPI.SRPeerJoin))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/site-replication/peer/bucket-ops").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerBucketOps))).Queries("bucket", "{bucket:.*}").Queries("operation", "{operation:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/site-replication/peer/bucket-ops").HandlerFunc(adminMiddleware(adminAPI.SRPeerBucketOps)).Queries("bucket", "{bucket:.*}").Queries("operation", "{operation:.*}")
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/iam-item").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerReplicateIAMItem)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/iam-item").HandlerFunc(adminMiddleware(adminAPI.SRPeerReplicateIAMItem))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/bucket-meta").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerReplicateBucketItem)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/bucket-meta").HandlerFunc(adminMiddleware(adminAPI.SRPeerReplicateBucketItem))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/peer/idp-settings").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerGetIDPSettings)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/site-replication/peer/idp-settings").HandlerFunc(adminMiddleware(adminAPI.SRPeerGetIDPSettings))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SiteReplicationEdit)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/edit").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationEdit))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/edit").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerEdit)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/edit").HandlerFunc(adminMiddleware(adminAPI.SRPeerEdit))
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/remove").HandlerFunc(gz(httpTraceHdrs(adminAPI.SRPeerRemove)))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/site-replication/peer/remove").HandlerFunc(adminMiddleware(adminAPI.SRPeerRemove))
|
||||||
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/site-replication/resync/op").HandlerFunc(adminMiddleware(adminAPI.SiteReplicationResyncOp)).Queries("operation", "{operation:.*}")
|
||||||
|
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
// Top locks
|
// Top locks
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/top/locks").HandlerFunc(gz(httpTraceHdrs(adminAPI.TopLocksHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/top/locks").HandlerFunc(adminMiddleware(adminAPI.TopLocksHandler))
|
||||||
// Force unlocks paths
|
// Force unlocks paths
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/force-unlock").
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/force-unlock").
|
||||||
Queries("paths", "{paths:.*}").HandlerFunc(gz(httpTraceHdrs(adminAPI.ForceUnlockHandler)))
|
Queries("paths", "{paths:.*}").HandlerFunc(adminMiddleware(adminAPI.ForceUnlockHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest").HandlerFunc(httpTraceHdrs(adminAPI.SpeedTestHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest").HandlerFunc(adminMiddleware(adminAPI.ObjectSpeedTestHandler, noGZFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/object").HandlerFunc(httpTraceHdrs(adminAPI.ObjectSpeedTestHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/object").HandlerFunc(adminMiddleware(adminAPI.ObjectSpeedTestHandler, noGZFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/drive").HandlerFunc(httpTraceHdrs(adminAPI.DriveSpeedtestHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/drive").HandlerFunc(adminMiddleware(adminAPI.DriveSpeedtestHandler, noGZFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/net").HandlerFunc(httpTraceHdrs(adminAPI.NetperfHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/net").HandlerFunc(adminMiddleware(adminAPI.NetperfHandler, noGZFlag))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest/site").HandlerFunc(adminMiddleware(adminAPI.SitePerfHandler, noGZFlag))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + adminAPIClientDevNull).HandlerFunc(adminMiddleware(adminAPI.ClientDevNull, noGZFlag))
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + adminAPIClientDevExtraTime).HandlerFunc(adminMiddleware(adminAPI.ClientDevNullExtraTime, noGZFlag))
|
||||||
|
|
||||||
// HTTP Trace
|
// HTTP Trace
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/trace").HandlerFunc(gz(http.HandlerFunc(adminAPI.TraceHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/trace").HandlerFunc(adminMiddleware(adminAPI.TraceHandler, noObjLayerFlag))
|
||||||
|
|
||||||
// Console Logs
|
// Console Logs
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/log").HandlerFunc(gz(httpTraceAll(adminAPI.ConsoleLogHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/log").HandlerFunc(adminMiddleware(adminAPI.ConsoleLogHandler, traceAllFlag))
|
||||||
|
|
||||||
// -- KMS APIs --
|
// -- KMS APIs --
|
||||||
//
|
//
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/kms/status").HandlerFunc(gz(httpTraceAll(adminAPI.KMSStatusHandler)))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/kms/status").HandlerFunc(adminMiddleware(adminAPI.KMSStatusHandler, traceAllFlag))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/kms/key/create").HandlerFunc(gz(httpTraceAll(adminAPI.KMSCreateKeyHandler))).Queries("key-id", "{key-id:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/kms/key/create").HandlerFunc(adminMiddleware(adminAPI.KMSCreateKeyHandler, traceAllFlag)).Queries("key-id", "{key-id:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/kms/key/status").HandlerFunc(gz(httpTraceAll(adminAPI.KMSKeyStatusHandler)))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/kms/key/status").HandlerFunc(adminMiddleware(adminAPI.KMSKeyStatusHandler, traceAllFlag))
|
||||||
|
|
||||||
if !globalIsGateway {
|
// Keep obdinfo for backward compatibility with mc
|
||||||
// Keep obdinfo for backward compatibility with mc
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/obdinfo").
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/obdinfo").
|
HandlerFunc(adminMiddleware(adminAPI.HealthInfoHandler))
|
||||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
|
// -- Health API --
|
||||||
// -- Health API --
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/healthinfo").
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/healthinfo").
|
HandlerFunc(adminMiddleware(adminAPI.HealthInfoHandler))
|
||||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
|
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/bandwidth").
|
|
||||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.BandwidthMonitorHandler)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If none of the routes match add default error handler routes
|
// If none of the routes match add default error handler routes
|
||||||
|
|||||||
@@ -20,17 +20,21 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v3"
|
||||||
|
"github.com/minio/minio/internal/config"
|
||||||
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getLocalServerProperty - returns madmin.ServerProperties for only the
|
// getLocalServerProperty - returns madmin.ServerProperties for only the
|
||||||
// local endpoints from given list of endpoints
|
// local endpoints from given list of endpoints
|
||||||
func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request) madmin.ServerProperties {
|
func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Request) madmin.ServerProperties {
|
||||||
var localEndpoints Endpoints
|
|
||||||
addr := globalLocalNodeName
|
addr := globalLocalNodeName
|
||||||
if r != nil {
|
if r != nil {
|
||||||
addr = r.Host
|
addr = r.Host
|
||||||
@@ -48,7 +52,6 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
if endpoint.IsLocal {
|
if endpoint.IsLocal {
|
||||||
// Only proceed for local endpoints
|
// Only proceed for local endpoints
|
||||||
network[nodeName] = string(madmin.ItemOnline)
|
network[nodeName] = string(madmin.ItemOnline)
|
||||||
localEndpoints = append(localEndpoints, endpoint)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, present := network[nodeName]
|
_, present := network[nodeName]
|
||||||
@@ -67,8 +70,23 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
var memstats runtime.MemStats
|
var memstats runtime.MemStats
|
||||||
runtime.ReadMemStats(&memstats)
|
runtime.ReadMemStats(&memstats)
|
||||||
|
|
||||||
|
gcStats := debug.GCStats{
|
||||||
|
// If stats.PauseQuantiles is non-empty, ReadGCStats fills
|
||||||
|
// it with quantiles summarizing the distribution of pause time.
|
||||||
|
// For example, if len(stats.PauseQuantiles) is 5, it will be
|
||||||
|
// filled with the minimum, 25%, 50%, 75%, and maximum pause times.
|
||||||
|
PauseQuantiles: make([]time.Duration, 5),
|
||||||
|
}
|
||||||
|
debug.ReadGCStats(&gcStats)
|
||||||
|
// Truncate GC stats to max 5 entries.
|
||||||
|
if len(gcStats.PauseEnd) > 5 {
|
||||||
|
gcStats.PauseEnd = gcStats.PauseEnd[len(gcStats.PauseEnd)-5:]
|
||||||
|
}
|
||||||
|
if len(gcStats.Pause) > 5 {
|
||||||
|
gcStats.Pause = gcStats.Pause[len(gcStats.Pause)-5:]
|
||||||
|
}
|
||||||
|
|
||||||
props := madmin.ServerProperties{
|
props := madmin.ServerProperties{
|
||||||
State: string(madmin.ItemInitializing),
|
|
||||||
Endpoint: addr,
|
Endpoint: addr,
|
||||||
Uptime: UTCNow().Unix() - globalBootTime.Unix(),
|
Uptime: UTCNow().Unix() - globalBootTime.Unix(),
|
||||||
Version: Version,
|
Version: Version,
|
||||||
@@ -81,14 +99,54 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
|||||||
Frees: memstats.Frees,
|
Frees: memstats.Frees,
|
||||||
HeapAlloc: memstats.HeapAlloc,
|
HeapAlloc: memstats.HeapAlloc,
|
||||||
},
|
},
|
||||||
|
GoMaxProcs: runtime.GOMAXPROCS(0),
|
||||||
|
NumCPU: runtime.NumCPU(),
|
||||||
|
RuntimeVersion: runtime.Version(),
|
||||||
|
GCStats: &madmin.GCStats{
|
||||||
|
LastGC: gcStats.LastGC,
|
||||||
|
NumGC: gcStats.NumGC,
|
||||||
|
PauseTotal: gcStats.PauseTotal,
|
||||||
|
Pause: gcStats.Pause,
|
||||||
|
PauseEnd: gcStats.PauseEnd,
|
||||||
|
},
|
||||||
|
MinioEnvVars: make(map[string]string, 10),
|
||||||
|
}
|
||||||
|
|
||||||
|
sensitive := map[string]struct{}{
|
||||||
|
config.EnvAccessKey: {},
|
||||||
|
config.EnvSecretKey: {},
|
||||||
|
config.EnvRootUser: {},
|
||||||
|
config.EnvRootPassword: {},
|
||||||
|
config.EnvMinIOSubnetAPIKey: {},
|
||||||
|
kms.EnvKMSSecretKey: {},
|
||||||
|
}
|
||||||
|
for _, v := range os.Environ() {
|
||||||
|
if !strings.HasPrefix(v, "MINIO") && !strings.HasPrefix(v, "_MINIO") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
split := strings.SplitN(v, "=", 2)
|
||||||
|
key := split[0]
|
||||||
|
value := ""
|
||||||
|
if len(split) > 1 {
|
||||||
|
value = split[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not send sensitive creds.
|
||||||
|
if _, ok := sensitive[key]; ok || strings.Contains(strings.ToLower(key), "password") || strings.HasSuffix(strings.ToLower(key), "key") {
|
||||||
|
props.MinioEnvVars[key] = "*** EXISTS, REDACTED ***"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
props.MinioEnvVars[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
objLayer := newObjectLayerFn()
|
objLayer := newObjectLayerFn()
|
||||||
if objLayer != nil && !globalIsGateway {
|
if objLayer != nil {
|
||||||
// only need Disks information in server mode.
|
storageInfo := objLayer.LocalStorageInfo(GlobalContext)
|
||||||
storageInfo, _ := objLayer.LocalStorageInfo(GlobalContext)
|
|
||||||
props.State = string(madmin.ItemOnline)
|
props.State = string(madmin.ItemOnline)
|
||||||
props.Disks = storageInfo.Disks
|
props.Disks = storageInfo.Disks
|
||||||
|
} else {
|
||||||
|
props.State = string(madmin.ItemInitializing)
|
||||||
|
props.Disks = getOfflineDisks("", globalEndpoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
return props
|
return props
|
||||||
|
|||||||
+283
-109
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
//
|
//
|
||||||
// This file is part of MinIO Object Storage stack
|
// This file is part of MinIO Object Storage stack
|
||||||
//
|
//
|
||||||
@@ -28,8 +28,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||||
|
"github.com/minio/minio/internal/ioutil"
|
||||||
"google.golang.org/api/googleapi"
|
"google.golang.org/api/googleapi"
|
||||||
|
|
||||||
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/tags"
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
@@ -37,13 +39,15 @@ import (
|
|||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
"github.com/minio/minio/internal/config/dns"
|
"github.com/minio/minio/internal/config/dns"
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
|
||||||
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
objectlock "github.com/minio/minio/internal/bucket/object/lock"
|
||||||
"github.com/minio/minio/internal/bucket/versioning"
|
"github.com/minio/minio/internal/bucket/versioning"
|
||||||
|
levent "github.com/minio/minio/internal/config/lambda/event"
|
||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIError structure
|
// APIError structure
|
||||||
@@ -83,6 +87,7 @@ const (
|
|||||||
ErrInternalError
|
ErrInternalError
|
||||||
ErrInvalidAccessKeyID
|
ErrInvalidAccessKeyID
|
||||||
ErrAccessKeyDisabled
|
ErrAccessKeyDisabled
|
||||||
|
ErrInvalidArgument
|
||||||
ErrInvalidBucketName
|
ErrInvalidBucketName
|
||||||
ErrInvalidDigest
|
ErrInvalidDigest
|
||||||
ErrInvalidRange
|
ErrInvalidRange
|
||||||
@@ -131,7 +136,10 @@ const (
|
|||||||
ErrReplicationNeedsVersioningError
|
ErrReplicationNeedsVersioningError
|
||||||
ErrReplicationBucketNeedsVersioningError
|
ErrReplicationBucketNeedsVersioningError
|
||||||
ErrReplicationDenyEditError
|
ErrReplicationDenyEditError
|
||||||
|
ErrRemoteTargetDenyAddError
|
||||||
ErrReplicationNoExistingObjects
|
ErrReplicationNoExistingObjects
|
||||||
|
ErrReplicationValidationError
|
||||||
|
ErrReplicationPermissionCheckError
|
||||||
ErrObjectRestoreAlreadyInProgress
|
ErrObjectRestoreAlreadyInProgress
|
||||||
ErrNoSuchKey
|
ErrNoSuchKey
|
||||||
ErrNoSuchUpload
|
ErrNoSuchUpload
|
||||||
@@ -144,13 +152,14 @@ const (
|
|||||||
ErrMethodNotAllowed
|
ErrMethodNotAllowed
|
||||||
ErrInvalidPart
|
ErrInvalidPart
|
||||||
ErrInvalidPartOrder
|
ErrInvalidPartOrder
|
||||||
|
ErrMissingPart
|
||||||
ErrAuthorizationHeaderMalformed
|
ErrAuthorizationHeaderMalformed
|
||||||
ErrMalformedPOSTRequest
|
ErrMalformedPOSTRequest
|
||||||
ErrPOSTFileRequired
|
ErrPOSTFileRequired
|
||||||
ErrSignatureVersionNotSupported
|
ErrSignatureVersionNotSupported
|
||||||
ErrBucketNotEmpty
|
ErrBucketNotEmpty
|
||||||
ErrAllAccessDisabled
|
ErrAllAccessDisabled
|
||||||
ErrMalformedPolicy
|
ErrPolicyInvalidVersion
|
||||||
ErrMissingFields
|
ErrMissingFields
|
||||||
ErrMissingCredTag
|
ErrMissingCredTag
|
||||||
ErrCredMalformed
|
ErrCredMalformed
|
||||||
@@ -163,7 +172,6 @@ const (
|
|||||||
ErrMalformedDate
|
ErrMalformedDate
|
||||||
ErrMalformedPresignedDate
|
ErrMalformedPresignedDate
|
||||||
ErrMalformedCredentialDate
|
ErrMalformedCredentialDate
|
||||||
ErrMalformedCredentialRegion
|
|
||||||
ErrMalformedExpires
|
ErrMalformedExpires
|
||||||
ErrNegativeExpires
|
ErrNegativeExpires
|
||||||
ErrAuthHeaderEmpty
|
ErrAuthHeaderEmpty
|
||||||
@@ -176,11 +184,13 @@ const (
|
|||||||
ErrBucketAlreadyOwnedByYou
|
ErrBucketAlreadyOwnedByYou
|
||||||
ErrInvalidDuration
|
ErrInvalidDuration
|
||||||
ErrBucketAlreadyExists
|
ErrBucketAlreadyExists
|
||||||
ErrTooManyBuckets
|
|
||||||
ErrMetadataTooLarge
|
ErrMetadataTooLarge
|
||||||
ErrUnsupportedMetadata
|
ErrUnsupportedMetadata
|
||||||
|
ErrUnsupportedHostHeader
|
||||||
ErrMaximumExpires
|
ErrMaximumExpires
|
||||||
ErrSlowDown
|
ErrSlowDownRead
|
||||||
|
ErrSlowDownWrite
|
||||||
|
ErrMaxVersionsExceeded
|
||||||
ErrInvalidPrefixMarker
|
ErrInvalidPrefixMarker
|
||||||
ErrBadRequest
|
ErrBadRequest
|
||||||
ErrKeyTooLongError
|
ErrKeyTooLongError
|
||||||
@@ -195,6 +205,9 @@ const (
|
|||||||
ErrBucketTaggingNotFound
|
ErrBucketTaggingNotFound
|
||||||
ErrObjectLockInvalidHeaders
|
ErrObjectLockInvalidHeaders
|
||||||
ErrInvalidTagDirective
|
ErrInvalidTagDirective
|
||||||
|
ErrPolicyAlreadyAttached
|
||||||
|
ErrPolicyNotAttached
|
||||||
|
ErrExcessData
|
||||||
// Add new error codes here.
|
// Add new error codes here.
|
||||||
|
|
||||||
// SSE-S3/SSE-KMS related API errors
|
// SSE-S3/SSE-KMS related API errors
|
||||||
@@ -206,6 +219,8 @@ const (
|
|||||||
ErrSSEMultipartEncrypted
|
ErrSSEMultipartEncrypted
|
||||||
ErrSSEEncryptedObject
|
ErrSSEEncryptedObject
|
||||||
ErrInvalidEncryptionParameters
|
ErrInvalidEncryptionParameters
|
||||||
|
ErrInvalidEncryptionParametersSSEC
|
||||||
|
|
||||||
ErrInvalidSSECustomerAlgorithm
|
ErrInvalidSSECustomerAlgorithm
|
||||||
ErrInvalidSSECustomerKey
|
ErrInvalidSSECustomerKey
|
||||||
ErrMissingSSECustomerKey
|
ErrMissingSSECustomerKey
|
||||||
@@ -215,6 +230,7 @@ const (
|
|||||||
ErrIncompatibleEncryptionMethod
|
ErrIncompatibleEncryptionMethod
|
||||||
ErrKMSNotConfigured
|
ErrKMSNotConfigured
|
||||||
ErrKMSKeyNotFoundException
|
ErrKMSKeyNotFoundException
|
||||||
|
ErrKMSDefaultKeyAlreadyConfigured
|
||||||
|
|
||||||
ErrNoAccessKey
|
ErrNoAccessKey
|
||||||
ErrInvalidToken
|
ErrInvalidToken
|
||||||
@@ -238,18 +254,17 @@ const (
|
|||||||
// Add new extended error codes here.
|
// Add new extended error codes here.
|
||||||
|
|
||||||
// MinIO extended errors.
|
// MinIO extended errors.
|
||||||
ErrReadQuorum
|
|
||||||
ErrWriteQuorum
|
|
||||||
ErrStorageFull
|
ErrStorageFull
|
||||||
ErrRequestBodyParse
|
ErrRequestBodyParse
|
||||||
ErrObjectExistsAsDirectory
|
ErrObjectExistsAsDirectory
|
||||||
ErrInvalidObjectName
|
ErrInvalidObjectName
|
||||||
ErrInvalidObjectNamePrefixSlash
|
ErrInvalidObjectNamePrefixSlash
|
||||||
ErrInvalidResourceName
|
ErrInvalidResourceName
|
||||||
|
ErrInvalidLifecycleQueryParameter
|
||||||
ErrServerNotInitialized
|
ErrServerNotInitialized
|
||||||
ErrOperationTimedOut
|
ErrRequestTimedout
|
||||||
ErrClientDisconnected
|
ErrClientDisconnected
|
||||||
ErrOperationMaxedOut
|
ErrTooManyRequests
|
||||||
ErrInvalidRequest
|
ErrInvalidRequest
|
||||||
ErrTransitionStorageClassNotFoundError
|
ErrTransitionStorageClassNotFoundError
|
||||||
// MinIO storage class error codes
|
// MinIO storage class error codes
|
||||||
@@ -261,9 +276,13 @@ const (
|
|||||||
|
|
||||||
ErrMalformedJSON
|
ErrMalformedJSON
|
||||||
ErrAdminNoSuchUser
|
ErrAdminNoSuchUser
|
||||||
|
ErrAdminNoSuchUserLDAPWarn
|
||||||
ErrAdminNoSuchGroup
|
ErrAdminNoSuchGroup
|
||||||
ErrAdminGroupNotEmpty
|
ErrAdminGroupNotEmpty
|
||||||
|
ErrAdminGroupDisabled
|
||||||
|
ErrAdminNoSuchJob
|
||||||
ErrAdminNoSuchPolicy
|
ErrAdminNoSuchPolicy
|
||||||
|
ErrAdminPolicyChangeAlreadyApplied
|
||||||
ErrAdminInvalidArgument
|
ErrAdminInvalidArgument
|
||||||
ErrAdminInvalidAccessKey
|
ErrAdminInvalidAccessKey
|
||||||
ErrAdminInvalidSecretKey
|
ErrAdminInvalidSecretKey
|
||||||
@@ -273,6 +292,11 @@ const (
|
|||||||
ErrAdminNoSuchConfigTarget
|
ErrAdminNoSuchConfigTarget
|
||||||
ErrAdminConfigEnvOverridden
|
ErrAdminConfigEnvOverridden
|
||||||
ErrAdminConfigDuplicateKeys
|
ErrAdminConfigDuplicateKeys
|
||||||
|
ErrAdminConfigInvalidIDPType
|
||||||
|
ErrAdminConfigLDAPNonDefaultConfigName
|
||||||
|
ErrAdminConfigLDAPValidation
|
||||||
|
ErrAdminConfigIDPCfgNameAlreadyExists
|
||||||
|
ErrAdminConfigIDPCfgNameDoesNotExist
|
||||||
ErrAdminCredentialsMismatch
|
ErrAdminCredentialsMismatch
|
||||||
ErrInsecureClientRequest
|
ErrInsecureClientRequest
|
||||||
ErrObjectTampered
|
ErrObjectTampered
|
||||||
@@ -286,6 +310,11 @@ const (
|
|||||||
ErrSiteReplicationBucketMetaError
|
ErrSiteReplicationBucketMetaError
|
||||||
ErrSiteReplicationIAMError
|
ErrSiteReplicationIAMError
|
||||||
ErrSiteReplicationConfigMissing
|
ErrSiteReplicationConfigMissing
|
||||||
|
ErrSiteReplicationIAMConfigMismatch
|
||||||
|
|
||||||
|
// Pool rebalance errors
|
||||||
|
ErrAdminRebalanceAlreadyStarted
|
||||||
|
ErrAdminRebalanceNotStarted
|
||||||
|
|
||||||
// Bucket Quota error codes
|
// Bucket Quota error codes
|
||||||
ErrAdminBucketQuotaExceeded
|
ErrAdminBucketQuotaExceeded
|
||||||
@@ -396,6 +425,12 @@ const (
|
|||||||
ErrPostPolicyConditionInvalidFormat
|
ErrPostPolicyConditionInvalidFormat
|
||||||
|
|
||||||
ErrInvalidChecksum
|
ErrInvalidChecksum
|
||||||
|
|
||||||
|
// Lambda functions
|
||||||
|
ErrLambdaARNInvalid
|
||||||
|
ErrLambdaARNNotFound
|
||||||
|
|
||||||
|
apiErrCodeEnd // This is used only for the testing code
|
||||||
)
|
)
|
||||||
|
|
||||||
type errorCodeMap map[APIErrorCode]APIError
|
type errorCodeMap map[APIErrorCode]APIError
|
||||||
@@ -409,8 +444,7 @@ func (e errorCodeMap) ToAPIErrWithErr(errCode APIErrorCode, err error) APIError
|
|||||||
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
|
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
|
||||||
}
|
}
|
||||||
if globalSite.Region != "" {
|
if globalSite.Region != "" {
|
||||||
switch errCode {
|
if errCode == ErrAuthorizationHeaderMalformed {
|
||||||
case ErrAuthorizationHeaderMalformed:
|
|
||||||
apiErr.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region)
|
apiErr.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region)
|
||||||
return apiErr
|
return apiErr
|
||||||
}
|
}
|
||||||
@@ -505,6 +539,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Your proposed upload exceeds the maximum allowed object size.",
|
Description: "Your proposed upload exceeds the maximum allowed object size.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrExcessData: {
|
||||||
|
Code: "ExcessData",
|
||||||
|
Description: "More data provided than indicated content length",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrPolicyTooLarge: {
|
ErrPolicyTooLarge: {
|
||||||
Code: "PolicyTooLarge",
|
Code: "PolicyTooLarge",
|
||||||
Description: "Policy exceeds the maximum allowed document size.",
|
Description: "Policy exceeds the maximum allowed document size.",
|
||||||
@@ -530,6 +569,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Your account is disabled; please contact your administrator.",
|
Description: "Your account is disabled; please contact your administrator.",
|
||||||
HTTPStatusCode: http.StatusForbidden,
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
|
ErrInvalidArgument: {
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Description: "Invalid argument",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrInvalidBucketName: {
|
ErrInvalidBucketName: {
|
||||||
Code: "InvalidBucketName",
|
Code: "InvalidBucketName",
|
||||||
Description: "The specified bucket is not valid.",
|
Description: "The specified bucket is not valid.",
|
||||||
@@ -655,6 +699,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's entity tag.",
|
Description: "One or more of the specified parts could not be found. The part may not have been uploaded, or the specified entity tag may not match the part's entity tag.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrMissingPart: {
|
||||||
|
Code: "InvalidRequest",
|
||||||
|
Description: "You must specify at least one part",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrInvalidPartOrder: {
|
ErrInvalidPartOrder: {
|
||||||
Code: "InvalidPartOrder",
|
Code: "InvalidPartOrder",
|
||||||
Description: "The list of parts was not in ascending order. The parts list must be specified in order by part number.",
|
Description: "The list of parts was not in ascending order. The parts list must be specified in order by part number.",
|
||||||
@@ -685,11 +734,6 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.",
|
Description: "The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrTooManyBuckets: {
|
|
||||||
Code: "TooManyBuckets",
|
|
||||||
Description: "You have attempted to create more buckets than allowed",
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
},
|
|
||||||
ErrBucketNotEmpty: {
|
ErrBucketNotEmpty: {
|
||||||
Code: "BucketNotEmpty",
|
Code: "BucketNotEmpty",
|
||||||
Description: "The bucket you tried to delete is not empty",
|
Description: "The bucket you tried to delete is not empty",
|
||||||
@@ -705,9 +749,9 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "All access to this resource has been disabled.",
|
Description: "All access to this resource has been disabled.",
|
||||||
HTTPStatusCode: http.StatusForbidden,
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
ErrMalformedPolicy: {
|
ErrPolicyInvalidVersion: {
|
||||||
Code: "MalformedPolicy",
|
Code: "MalformedPolicy",
|
||||||
Description: "Policy has invalid resource.",
|
Description: "The policy must contain a valid version string",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrMissingFields: {
|
ErrMissingFields: {
|
||||||
@@ -805,11 +849,21 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Request is not valid yet",
|
Description: "Request is not valid yet",
|
||||||
HTTPStatusCode: http.StatusForbidden,
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
ErrSlowDown: {
|
ErrSlowDownRead: {
|
||||||
Code: "SlowDown",
|
Code: "SlowDownRead",
|
||||||
Description: "Resource requested is unreadable, please reduce your request rate",
|
Description: "Resource requested is unreadable, please reduce your request rate",
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
},
|
},
|
||||||
|
ErrSlowDownWrite: {
|
||||||
|
Code: "SlowDownWrite",
|
||||||
|
Description: "Resource requested is unwritable, please reduce your request rate",
|
||||||
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
|
},
|
||||||
|
ErrMaxVersionsExceeded: {
|
||||||
|
Code: "MaxVersionsExceeded",
|
||||||
|
Description: "You've exceeded the limit on the number of versions you can create on this object",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrInvalidPrefixMarker: {
|
ErrInvalidPrefixMarker: {
|
||||||
Code: "InvalidPrefixMarker",
|
Code: "InvalidPrefixMarker",
|
||||||
Description: "Invalid marker prefix combination",
|
Description: "Invalid marker prefix combination",
|
||||||
@@ -910,6 +964,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "No matching ExistingsObjects rule enabled",
|
Description: "No matching ExistingsObjects rule enabled",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrRemoteTargetDenyAddError: {
|
||||||
|
Code: "XMinioAdminRemoteTargetDenyAdd",
|
||||||
|
Description: "Cannot add remote target endpoint since this server is in a cluster replication setup",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrReplicationDenyEditError: {
|
ErrReplicationDenyEditError: {
|
||||||
Code: "XMinioReplicationDenyEdit",
|
Code: "XMinioReplicationDenyEdit",
|
||||||
Description: "Cannot alter local replication config since this server is in a cluster replication setup",
|
Description: "Cannot alter local replication config since this server is in a cluster replication setup",
|
||||||
@@ -965,6 +1024,16 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Versioning must be 'Enabled' on the bucket to add a replication target",
|
Description: "Versioning must be 'Enabled' on the bucket to add a replication target",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrReplicationValidationError: {
|
||||||
|
Code: "InvalidRequest",
|
||||||
|
Description: "Replication validation failed on target",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrReplicationPermissionCheckError: {
|
||||||
|
Code: "ReplicationPermissionCheck",
|
||||||
|
Description: "X-Minio-Source-Replication-Check cannot be specified in request. Request cannot be completed",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrNoSuchObjectLockConfiguration: {
|
ErrNoSuchObjectLockConfiguration: {
|
||||||
Code: "NoSuchObjectLockConfiguration",
|
Code: "NoSuchObjectLockConfiguration",
|
||||||
Description: "The specified object does not have a ObjectLock configuration",
|
Description: "The specified object does not have a ObjectLock configuration",
|
||||||
@@ -1078,8 +1147,13 @@ var errorCodes = errorCodeMap{
|
|||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrInvalidEncryptionMethod: {
|
ErrInvalidEncryptionMethod: {
|
||||||
Code: "InvalidRequest",
|
Code: "InvalidArgument",
|
||||||
Description: "The encryption method specified is not supported",
|
Description: "Server Side Encryption with AWS KMS managed key requires HTTP header x-amz-server-side-encryption : aws:kms",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrIncompatibleEncryptionMethod: {
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Description: "Server Side Encryption with Customer provided key is incompatible with the encryption method specified",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrInvalidEncryptionKeyID: {
|
ErrInvalidEncryptionKeyID: {
|
||||||
@@ -1107,6 +1181,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The encryption parameters are not applicable to this object.",
|
Description: "The encryption parameters are not applicable to this object.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrInvalidEncryptionParametersSSEC: {
|
||||||
|
Code: "InvalidRequest",
|
||||||
|
Description: "SSE-C encryption parameters are not supported on replicated bucket.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrInvalidSSECustomerAlgorithm: {
|
ErrInvalidSSECustomerAlgorithm: {
|
||||||
Code: "InvalidArgument",
|
Code: "InvalidArgument",
|
||||||
Description: "Requests specifying Server Side Encryption with Customer provided keys must provide a valid encryption algorithm.",
|
Description: "Requests specifying Server Side Encryption with Customer provided keys must provide a valid encryption algorithm.",
|
||||||
@@ -1137,11 +1216,6 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The provided encryption parameters did not match the ones used originally.",
|
Description: "The provided encryption parameters did not match the ones used originally.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrIncompatibleEncryptionMethod: {
|
|
||||||
Code: "InvalidArgument",
|
|
||||||
Description: "Server side encryption specified with both SSE-C and SSE-S3 headers",
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
},
|
|
||||||
ErrKMSNotConfigured: {
|
ErrKMSNotConfigured: {
|
||||||
Code: "NotImplemented",
|
Code: "NotImplemented",
|
||||||
Description: "Server side encryption specified but KMS is not configured",
|
Description: "Server side encryption specified but KMS is not configured",
|
||||||
@@ -1152,6 +1226,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Invalid keyId",
|
Description: "Invalid keyId",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrKMSDefaultKeyAlreadyConfigured: {
|
||||||
|
Code: "KMS.DefaultKeyAlreadyConfiguredException",
|
||||||
|
Description: "A default encryption already exists and cannot be changed on KMS",
|
||||||
|
HTTPStatusCode: http.StatusConflict,
|
||||||
|
},
|
||||||
ErrNoAccessKey: {
|
ErrNoAccessKey: {
|
||||||
Code: "AccessDenied",
|
Code: "AccessDenied",
|
||||||
Description: "No AWSAccessKey was presented",
|
Description: "No AWSAccessKey was presented",
|
||||||
@@ -1216,26 +1295,52 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "The JSON you provided was not well-formed or did not validate against our published format.",
|
Description: "The JSON you provided was not well-formed or did not validate against our published format.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrInvalidLifecycleQueryParameter: {
|
||||||
|
Code: "XMinioInvalidLifecycleParameter",
|
||||||
|
Description: "The boolean value provided for withUpdatedAt query parameter was invalid.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrAdminNoSuchUser: {
|
ErrAdminNoSuchUser: {
|
||||||
Code: "XMinioAdminNoSuchUser",
|
Code: "XMinioAdminNoSuchUser",
|
||||||
Description: "The specified user does not exist.",
|
Description: "The specified user does not exist.",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
|
ErrAdminNoSuchUserLDAPWarn: {
|
||||||
|
Code: "XMinioAdminNoSuchUser",
|
||||||
|
Description: "The specified user does not exist. If you meant a user in LDAP, use `mc idp ldap`",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
ErrAdminNoSuchGroup: {
|
ErrAdminNoSuchGroup: {
|
||||||
Code: "XMinioAdminNoSuchGroup",
|
Code: "XMinioAdminNoSuchGroup",
|
||||||
Description: "The specified group does not exist.",
|
Description: "The specified group does not exist.",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
|
ErrAdminNoSuchJob: {
|
||||||
|
Code: "XMinioAdminNoSuchJob",
|
||||||
|
Description: "The specified job does not exist.",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
ErrAdminGroupNotEmpty: {
|
ErrAdminGroupNotEmpty: {
|
||||||
Code: "XMinioAdminGroupNotEmpty",
|
Code: "XMinioAdminGroupNotEmpty",
|
||||||
Description: "The specified group is not empty - cannot remove it.",
|
Description: "The specified group is not empty - cannot remove it.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrAdminGroupDisabled: {
|
||||||
|
Code: "XMinioAdminGroupDisabled",
|
||||||
|
Description: "The specified group is disabled.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrAdminNoSuchPolicy: {
|
ErrAdminNoSuchPolicy: {
|
||||||
Code: "XMinioAdminNoSuchPolicy",
|
Code: "XMinioAdminNoSuchPolicy",
|
||||||
Description: "The canned policy does not exist.",
|
Description: "The canned policy does not exist.",
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
|
ErrAdminPolicyChangeAlreadyApplied: {
|
||||||
|
Code: "XMinioAdminPolicyChangeAlreadyApplied",
|
||||||
|
Description: "The specified policy change is already in effect.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
|
||||||
ErrAdminInvalidArgument: {
|
ErrAdminInvalidArgument: {
|
||||||
Code: "XMinioAdminInvalidArgument",
|
Code: "XMinioAdminInvalidArgument",
|
||||||
Description: "Invalid arguments specified.",
|
Description: "Invalid arguments specified.",
|
||||||
@@ -1282,6 +1387,31 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "JSON configuration provided has objects with duplicate keys",
|
Description: "JSON configuration provided has objects with duplicate keys",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrAdminConfigInvalidIDPType: {
|
||||||
|
Code: "XMinioAdminConfigInvalidIDPType",
|
||||||
|
Description: fmt.Sprintf("Invalid IDP configuration type - must be one of %v", madmin.ValidIDPConfigTypes),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrAdminConfigLDAPNonDefaultConfigName: {
|
||||||
|
Code: "XMinioAdminConfigLDAPNonDefaultConfigName",
|
||||||
|
Description: "Only a single LDAP configuration is supported - config name must be empty or `_`",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrAdminConfigLDAPValidation: {
|
||||||
|
Code: "XMinioAdminConfigLDAPValidation",
|
||||||
|
Description: "LDAP Configuration validation failed",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrAdminConfigIDPCfgNameAlreadyExists: {
|
||||||
|
Code: "XMinioAdminConfigIDPCfgNameAlreadyExists",
|
||||||
|
Description: "An IDP configuration with the given name already exists",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrAdminConfigIDPCfgNameDoesNotExist: {
|
||||||
|
Code: "XMinioAdminConfigIDPCfgNameDoesNotExist",
|
||||||
|
Description: "No such IDP configuration exists",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrAdminConfigNotificationTargetsFailed: {
|
ErrAdminConfigNotificationTargetsFailed: {
|
||||||
Code: "XMinioAdminNotificationTargetsTestFailed",
|
Code: "XMinioAdminNotificationTargetsTestFailed",
|
||||||
Description: "Configuration update failed due an unsuccessful attempt to connect to one or more notification servers",
|
Description: "Configuration update failed due an unsuccessful attempt to connect to one or more notification servers",
|
||||||
@@ -1312,7 +1442,7 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Cannot respond to plain-text request from TLS-encrypted server",
|
Description: "Cannot respond to plain-text request from TLS-encrypted server",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrOperationTimedOut: {
|
ErrRequestTimedout: {
|
||||||
Code: "RequestTimeout",
|
Code: "RequestTimeout",
|
||||||
Description: "A timeout occurred while trying to lock a resource, please reduce your request rate",
|
Description: "A timeout occurred while trying to lock a resource, please reduce your request rate",
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
@@ -1322,9 +1452,9 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Client disconnected before response was ready",
|
Description: "Client disconnected before response was ready",
|
||||||
HTTPStatusCode: 499, // No official code, use nginx value.
|
HTTPStatusCode: 499, // No official code, use nginx value.
|
||||||
},
|
},
|
||||||
ErrOperationMaxedOut: {
|
ErrTooManyRequests: {
|
||||||
Code: "SlowDown",
|
Code: "TooManyRequests",
|
||||||
Description: "A timeout exceeded while waiting to proceed with the request, please reduce your request rate",
|
Description: "Deadline exceeded while waiting in incoming queue, please reduce your request rate",
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
},
|
},
|
||||||
ErrUnsupportedMetadata: {
|
ErrUnsupportedMetadata: {
|
||||||
@@ -1332,6 +1462,11 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Your metadata headers are not supported.",
|
Description: "Your metadata headers are not supported.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrUnsupportedHostHeader: {
|
||||||
|
Code: "InvalidArgument",
|
||||||
|
Description: "Your Host header is malformed.",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
ErrObjectTampered: {
|
ErrObjectTampered: {
|
||||||
Code: "XMinioObjectTampered",
|
Code: "XMinioObjectTampered",
|
||||||
Description: errObjectTampered.Error(),
|
Description: errObjectTampered.Error(),
|
||||||
@@ -1346,7 +1481,7 @@ var errorCodes = errorCodeMap{
|
|||||||
ErrSiteReplicationPeerResp: {
|
ErrSiteReplicationPeerResp: {
|
||||||
Code: "XMinioSiteReplicationPeerResp",
|
Code: "XMinioSiteReplicationPeerResp",
|
||||||
Description: "Error received when contacting a peer site",
|
Description: "Error received when contacting a peer site",
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrSiteReplicationBackendIssue: {
|
ErrSiteReplicationBackendIssue: {
|
||||||
Code: "XMinioSiteReplicationBackendIssue",
|
Code: "XMinioSiteReplicationBackendIssue",
|
||||||
@@ -1378,6 +1513,21 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Site not found in site replication configuration",
|
Description: "Site not found in site replication configuration",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrSiteReplicationIAMConfigMismatch: {
|
||||||
|
Code: "XMinioSiteReplicationIAMConfigMismatch",
|
||||||
|
Description: "IAM configuration mismatch between sites",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrAdminRebalanceAlreadyStarted: {
|
||||||
|
Code: "XMinioAdminRebalanceAlreadyStarted",
|
||||||
|
Description: "Pool rebalance is already started",
|
||||||
|
HTTPStatusCode: http.StatusConflict,
|
||||||
|
},
|
||||||
|
ErrAdminRebalanceNotStarted: {
|
||||||
|
Code: "XMinioAdminRebalanceNotStarted",
|
||||||
|
Description: "Pool rebalance is not started",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
ErrMaximumExpires: {
|
ErrMaximumExpires: {
|
||||||
Code: "AuthorizationQueryParametersError",
|
Code: "AuthorizationQueryParametersError",
|
||||||
Description: "X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds",
|
Description: "X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds",
|
||||||
@@ -1454,7 +1604,7 @@ var errorCodes = errorCodeMap{
|
|||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
ErrBusy: {
|
ErrBusy: {
|
||||||
Code: "Busy",
|
Code: "ServerBusy",
|
||||||
Description: "The service is unavailable. Please retry.",
|
Description: "The service is unavailable. Please retry.",
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
},
|
},
|
||||||
@@ -1893,6 +2043,26 @@ var errorCodes = errorCodeMap{
|
|||||||
Description: "Invalid checksum provided.",
|
Description: "Invalid checksum provided.",
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
|
ErrLambdaARNInvalid: {
|
||||||
|
Code: "LambdaARNInvalid",
|
||||||
|
Description: "The specified lambda ARN is invalid",
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
ErrLambdaARNNotFound: {
|
||||||
|
Code: "LambdaARNNotFound",
|
||||||
|
Description: "The specified lambda ARN does not exist",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
ErrPolicyAlreadyAttached: {
|
||||||
|
Code: "XMinioPolicyAlreadyAttached",
|
||||||
|
Description: "The specified policy is already attached.",
|
||||||
|
HTTPStatusCode: http.StatusConflict,
|
||||||
|
},
|
||||||
|
ErrPolicyNotAttached: {
|
||||||
|
Code: "XMinioPolicyNotAttached",
|
||||||
|
Description: "The specified policy is not found.",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
// Add your error structure here.
|
// Add your error structure here.
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1905,26 +2075,33 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only return ErrClientDisconnected if the provided context is actually canceled.
|
// Only return ErrClientDisconnected if the provided context is actually canceled.
|
||||||
// This way downstream context.Canceled will still report ErrOperationTimedOut
|
// This way downstream context.Canceled will still report ErrRequestTimedout
|
||||||
if contextCanceled(ctx) {
|
if contextCanceled(ctx) && errors.Is(ctx.Err(), context.Canceled) {
|
||||||
if ctx.Err() == context.Canceled {
|
return ErrClientDisconnected
|
||||||
return ErrClientDisconnected
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unwrap the error first
|
||||||
|
err = unwrapAll(err)
|
||||||
|
|
||||||
switch err {
|
switch err {
|
||||||
case errInvalidArgument:
|
case errInvalidArgument:
|
||||||
apiErr = ErrAdminInvalidArgument
|
apiErr = ErrAdminInvalidArgument
|
||||||
|
case errNoSuchPolicy:
|
||||||
|
apiErr = ErrAdminNoSuchPolicy
|
||||||
case errNoSuchUser:
|
case errNoSuchUser:
|
||||||
apiErr = ErrAdminNoSuchUser
|
apiErr = ErrAdminNoSuchUser
|
||||||
|
case errNoSuchUserLDAPWarn:
|
||||||
|
apiErr = ErrAdminNoSuchUserLDAPWarn
|
||||||
case errNoSuchServiceAccount:
|
case errNoSuchServiceAccount:
|
||||||
apiErr = ErrAdminServiceAccountNotFound
|
apiErr = ErrAdminServiceAccountNotFound
|
||||||
case errNoSuchGroup:
|
case errNoSuchGroup:
|
||||||
apiErr = ErrAdminNoSuchGroup
|
apiErr = ErrAdminNoSuchGroup
|
||||||
case errGroupNotEmpty:
|
case errGroupNotEmpty:
|
||||||
apiErr = ErrAdminGroupNotEmpty
|
apiErr = ErrAdminGroupNotEmpty
|
||||||
case errNoSuchPolicy:
|
case errNoSuchJob:
|
||||||
apiErr = ErrAdminNoSuchPolicy
|
apiErr = ErrAdminNoSuchJob
|
||||||
|
case errNoPolicyToAttachOrDetach:
|
||||||
|
apiErr = ErrAdminPolicyChangeAlreadyApplied
|
||||||
case errSignatureMismatch:
|
case errSignatureMismatch:
|
||||||
apiErr = ErrSignatureDoesNotMatch
|
apiErr = ErrSignatureDoesNotMatch
|
||||||
case errInvalidRange:
|
case errInvalidRange:
|
||||||
@@ -1941,9 +2118,17 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrAdminInvalidSecretKey
|
apiErr = ErrAdminInvalidSecretKey
|
||||||
case errInvalidStorageClass:
|
case errInvalidStorageClass:
|
||||||
apiErr = ErrInvalidStorageClass
|
apiErr = ErrInvalidStorageClass
|
||||||
|
case errErasureReadQuorum:
|
||||||
|
apiErr = ErrSlowDownRead
|
||||||
|
case errErasureWriteQuorum:
|
||||||
|
apiErr = ErrSlowDownWrite
|
||||||
|
case errMaxVersionsExceeded:
|
||||||
|
apiErr = ErrMaxVersionsExceeded
|
||||||
// SSE errors
|
// SSE errors
|
||||||
case errInvalidEncryptionParameters:
|
case errInvalidEncryptionParameters:
|
||||||
apiErr = ErrInvalidEncryptionParameters
|
apiErr = ErrInvalidEncryptionParameters
|
||||||
|
case errInvalidEncryptionParametersSSEC:
|
||||||
|
apiErr = ErrInvalidEncryptionParametersSSEC
|
||||||
case crypto.ErrInvalidEncryptionMethod:
|
case crypto.ErrInvalidEncryptionMethod:
|
||||||
apiErr = ErrInvalidEncryptionMethod
|
apiErr = ErrInvalidEncryptionMethod
|
||||||
case crypto.ErrInvalidEncryptionKeyID:
|
case crypto.ErrInvalidEncryptionKeyID:
|
||||||
@@ -1970,11 +2155,12 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrKMSNotConfigured
|
apiErr = ErrKMSNotConfigured
|
||||||
case errKMSKeyNotFound:
|
case errKMSKeyNotFound:
|
||||||
apiErr = ErrKMSKeyNotFoundException
|
apiErr = ErrKMSKeyNotFoundException
|
||||||
|
case errKMSDefaultKeyAlreadyConfigured:
|
||||||
case context.Canceled, context.DeadlineExceeded:
|
apiErr = ErrKMSDefaultKeyAlreadyConfigured
|
||||||
apiErr = ErrOperationTimedOut
|
case context.Canceled:
|
||||||
case errDiskNotFound:
|
apiErr = ErrClientDisconnected
|
||||||
apiErr = ErrSlowDown
|
case context.DeadlineExceeded:
|
||||||
|
apiErr = ErrRequestTimedout
|
||||||
case objectlock.ErrInvalidRetentionDate:
|
case objectlock.ErrInvalidRetentionDate:
|
||||||
apiErr = ErrInvalidRetentionDate
|
apiErr = ErrInvalidRetentionDate
|
||||||
case objectlock.ErrPastObjectLockRetainDate:
|
case objectlock.ErrPastObjectLockRetainDate:
|
||||||
@@ -1985,11 +2171,14 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrObjectLockInvalidHeaders
|
apiErr = ErrObjectLockInvalidHeaders
|
||||||
case objectlock.ErrMalformedXML:
|
case objectlock.ErrMalformedXML:
|
||||||
apiErr = ErrMalformedXML
|
apiErr = ErrMalformedXML
|
||||||
|
case errInvalidMaxParts:
|
||||||
|
apiErr = ErrInvalidMaxParts
|
||||||
|
case ioutil.ErrOverread:
|
||||||
|
apiErr = ErrExcessData
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compression errors
|
// Compression errors
|
||||||
switch err {
|
if err == errInvalidDecompressedSize {
|
||||||
case errInvalidDecompressedSize:
|
|
||||||
apiErr = ErrInvalidDecompressedSize
|
apiErr = ErrInvalidDecompressedSize
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2000,7 +2189,7 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
|
|
||||||
// etcd specific errors, a key is always a bucket for us return
|
// etcd specific errors, a key is always a bucket for us return
|
||||||
// ErrNoSuchBucket in such a case.
|
// ErrNoSuchBucket in such a case.
|
||||||
if err == dns.ErrNoEntriesFound {
|
if errors.Is(err, dns.ErrNoEntriesFound) {
|
||||||
return ErrNoSuchBucket
|
return ErrNoSuchBucket
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2050,11 +2239,9 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
case InvalidPart:
|
case InvalidPart:
|
||||||
apiErr = ErrInvalidPart
|
apiErr = ErrInvalidPart
|
||||||
case InsufficientWriteQuorum:
|
case InsufficientWriteQuorum:
|
||||||
apiErr = ErrSlowDown
|
apiErr = ErrSlowDownWrite
|
||||||
case InsufficientReadQuorum:
|
case InsufficientReadQuorum:
|
||||||
apiErr = ErrSlowDown
|
apiErr = ErrSlowDownRead
|
||||||
case InvalidMarkerPrefixCombination:
|
|
||||||
apiErr = ErrNotImplemented
|
|
||||||
case InvalidUploadIDKeyCombination:
|
case InvalidUploadIDKeyCombination:
|
||||||
apiErr = ErrNotImplemented
|
apiErr = ErrNotImplemented
|
||||||
case MalformedUploadID:
|
case MalformedUploadID:
|
||||||
@@ -2067,10 +2254,10 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrContentSHA256Mismatch
|
apiErr = ErrContentSHA256Mismatch
|
||||||
case hash.ChecksumMismatch:
|
case hash.ChecksumMismatch:
|
||||||
apiErr = ErrContentChecksumMismatch
|
apiErr = ErrContentChecksumMismatch
|
||||||
case ObjectTooLarge:
|
case hash.SizeTooSmall:
|
||||||
apiErr = ErrEntityTooLarge
|
|
||||||
case ObjectTooSmall:
|
|
||||||
apiErr = ErrEntityTooSmall
|
apiErr = ErrEntityTooSmall
|
||||||
|
case hash.SizeTooLarge:
|
||||||
|
apiErr = ErrEntityTooLarge
|
||||||
case NotImplemented:
|
case NotImplemented:
|
||||||
apiErr = ErrNotImplemented
|
apiErr = ErrNotImplemented
|
||||||
case PartTooBig:
|
case PartTooBig:
|
||||||
@@ -2115,7 +2302,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrTransitionStorageClassNotFoundError
|
apiErr = ErrTransitionStorageClassNotFoundError
|
||||||
case InvalidObjectState:
|
case InvalidObjectState:
|
||||||
apiErr = ErrInvalidObjectState
|
apiErr = ErrInvalidObjectState
|
||||||
|
case PreConditionFailed:
|
||||||
|
apiErr = ErrPreconditionFailed
|
||||||
case BucketQuotaExceeded:
|
case BucketQuotaExceeded:
|
||||||
apiErr = ErrAdminBucketQuotaExceeded
|
apiErr = ErrAdminBucketQuotaExceeded
|
||||||
case *event.ErrInvalidEventName:
|
case *event.ErrInvalidEventName:
|
||||||
@@ -2124,6 +2312,10 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
apiErr = ErrARNNotification
|
apiErr = ErrARNNotification
|
||||||
case *event.ErrARNNotFound:
|
case *event.ErrARNNotFound:
|
||||||
apiErr = ErrARNNotification
|
apiErr = ErrARNNotification
|
||||||
|
case *levent.ErrInvalidARN:
|
||||||
|
apiErr = ErrLambdaARNInvalid
|
||||||
|
case *levent.ErrARNNotFound:
|
||||||
|
apiErr = ErrLambdaARNNotFound
|
||||||
case *event.ErrUnknownRegion:
|
case *event.ErrUnknownRegion:
|
||||||
apiErr = ErrRegionNotification
|
apiErr = ErrRegionNotification
|
||||||
case *event.ErrInvalidFilterName:
|
case *event.ErrInvalidFilterName:
|
||||||
@@ -2141,7 +2333,7 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
case *event.ErrUnsupportedConfiguration:
|
case *event.ErrUnsupportedConfiguration:
|
||||||
apiErr = ErrUnsupportedNotification
|
apiErr = ErrUnsupportedNotification
|
||||||
case OperationTimedOut:
|
case OperationTimedOut:
|
||||||
apiErr = ErrOperationTimedOut
|
apiErr = ErrRequestTimedout
|
||||||
case BackendDown:
|
case BackendDown:
|
||||||
apiErr = ErrBackendDown
|
apiErr = ErrBackendDown
|
||||||
case ObjectNameTooLong:
|
case ObjectNameTooLong:
|
||||||
@@ -2151,6 +2343,12 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
|||||||
case dns.ErrBucketConflict:
|
case dns.ErrBucketConflict:
|
||||||
apiErr = ErrBucketAlreadyExists
|
apiErr = ErrBucketAlreadyExists
|
||||||
default:
|
default:
|
||||||
|
if _, ok := err.(tags.Error); ok {
|
||||||
|
// tag errors are not exported, so we check their custom interface to avoid logging.
|
||||||
|
// The correct type is inserted by toAPIError.
|
||||||
|
apiErr = ErrInternalError
|
||||||
|
break
|
||||||
|
}
|
||||||
var ie, iw int
|
var ie, iw int
|
||||||
// This work-around is to handle the issue golang/go#30648
|
// This work-around is to handle the issue golang/go#30648
|
||||||
//nolint:gocritic
|
//nolint:gocritic
|
||||||
@@ -2187,39 +2385,29 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apiErr := errorCodes.ToAPIErr(toAPIErrorCode(ctx, err))
|
apiErr := errorCodes.ToAPIErr(toAPIErrorCode(ctx, err))
|
||||||
e, ok := err.(dns.ErrInvalidBucketName)
|
switch apiErr.Code {
|
||||||
if ok {
|
case "NotImplemented":
|
||||||
code := toAPIErrorCode(ctx, e)
|
desc := fmt.Sprintf("%s (%v)", apiErr.Description, err)
|
||||||
apiErr = errorCodes.ToAPIErrWithErr(code, e)
|
apiErr = APIError{
|
||||||
}
|
Code: apiErr.Code,
|
||||||
|
Description: desc,
|
||||||
if apiErr.Code == "NotImplemented" {
|
HTTPStatusCode: apiErr.HTTPStatusCode,
|
||||||
switch e := err.(type) {
|
|
||||||
case NotImplemented:
|
|
||||||
desc := e.Error()
|
|
||||||
if desc == "" {
|
|
||||||
desc = apiErr.Description
|
|
||||||
}
|
|
||||||
apiErr = APIError{
|
|
||||||
Code: apiErr.Code,
|
|
||||||
Description: desc,
|
|
||||||
HTTPStatusCode: apiErr.HTTPStatusCode,
|
|
||||||
}
|
|
||||||
return apiErr
|
|
||||||
}
|
}
|
||||||
}
|
case "XMinioBackendDown":
|
||||||
|
|
||||||
if apiErr.Code == "XMinioBackendDown" {
|
|
||||||
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, err)
|
apiErr.Description = fmt.Sprintf("%s (%v)", apiErr.Description, err)
|
||||||
return apiErr
|
case "InternalError":
|
||||||
}
|
|
||||||
|
|
||||||
if apiErr.Code == "InternalError" {
|
|
||||||
// If we see an internal error try to interpret
|
// If we see an internal error try to interpret
|
||||||
// any underlying errors if possible depending on
|
// any underlying errors if possible depending on
|
||||||
// their internal error types. This code is only
|
// their internal error types.
|
||||||
// useful with gateway implementations.
|
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
|
case kms.Error:
|
||||||
|
apiErr = APIError{
|
||||||
|
Description: e.Err.Error(),
|
||||||
|
Code: e.APICode,
|
||||||
|
HTTPStatusCode: e.HTTPStatusCode,
|
||||||
|
}
|
||||||
|
case batchReplicationJobError:
|
||||||
|
apiErr = APIError(e)
|
||||||
case InvalidArgument:
|
case InvalidArgument:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "InvalidArgument",
|
Code: "InvalidArgument",
|
||||||
@@ -2228,27 +2416,25 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
}
|
}
|
||||||
case *xml.SyntaxError:
|
case *xml.SyntaxError:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "MalformedXML",
|
Code: "MalformedXML",
|
||||||
Description: fmt.Sprintf("%s (%s)", errorCodes[ErrMalformedXML].Description,
|
Description: fmt.Sprintf("%s (%s)", errorCodes[ErrMalformedXML].Description, e),
|
||||||
e.Error()),
|
|
||||||
HTTPStatusCode: errorCodes[ErrMalformedXML].HTTPStatusCode,
|
HTTPStatusCode: errorCodes[ErrMalformedXML].HTTPStatusCode,
|
||||||
}
|
}
|
||||||
case url.EscapeError:
|
case url.EscapeError:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "XMinioInvalidObjectName",
|
Code: "XMinioInvalidObjectName",
|
||||||
Description: fmt.Sprintf("%s (%s)", errorCodes[ErrInvalidObjectName].Description,
|
Description: fmt.Sprintf("%s (%s)", errorCodes[ErrInvalidObjectName].Description, e),
|
||||||
e.Error()),
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
case versioning.Error:
|
case versioning.Error:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "IllegalVersioningConfigurationException",
|
Code: "IllegalVersioningConfigurationException",
|
||||||
Description: fmt.Sprintf("Versioning configuration specified in the request is invalid. (%s)", e.Error()),
|
Description: fmt.Sprintf("Versioning configuration specified in the request is invalid. (%s)", e),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
case lifecycle.Error:
|
case lifecycle.Error:
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "InvalidRequest",
|
Code: "InvalidArgument",
|
||||||
Description: e.Error(),
|
Description: e.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
}
|
}
|
||||||
@@ -2282,7 +2468,7 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
Description: e.Message,
|
Description: e.Message,
|
||||||
HTTPStatusCode: e.StatusCode,
|
HTTPStatusCode: e.StatusCode,
|
||||||
}
|
}
|
||||||
if globalIsGateway && strings.Contains(e.Message, "KMS is not configured") {
|
if strings.Contains(e.Message, "KMS is not configured") {
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "NotImplemented",
|
Code: "NotImplemented",
|
||||||
Description: e.Message,
|
Description: e.Message,
|
||||||
@@ -2306,22 +2492,10 @@ func toAPIError(ctx context.Context, err error) APIError {
|
|||||||
Description: e.Error(),
|
Description: e.Error(),
|
||||||
HTTPStatusCode: e.Response().StatusCode,
|
HTTPStatusCode: e.Response().StatusCode,
|
||||||
}
|
}
|
||||||
// Add more Gateway SDKs here if any in future.
|
// Add more other SDK related errors here if any in future.
|
||||||
default:
|
default:
|
||||||
//nolint:gocritic
|
//nolint:gocritic
|
||||||
if errors.Is(err, errMalformedEncoding) {
|
if errors.Is(err, errMalformedEncoding) || errors.Is(err, errChunkTooBig) || errors.Is(err, strconv.ErrRange) {
|
||||||
apiErr = APIError{
|
|
||||||
Code: "BadRequest",
|
|
||||||
Description: err.Error(),
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
}
|
|
||||||
} else if errors.Is(err, errChunkTooBig) {
|
|
||||||
apiErr = APIError{
|
|
||||||
Code: "BadRequest",
|
|
||||||
Description: err.Error(),
|
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
|
||||||
}
|
|
||||||
} else if errors.Is(err, strconv.ErrRange) {
|
|
||||||
apiErr = APIError{
|
apiErr = APIError{
|
||||||
Code: "BadRequest",
|
Code: "BadRequest",
|
||||||
Description: err.Error(),
|
Description: err.Error(),
|
||||||
|
|||||||
+18
-10
@@ -20,8 +20,6 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
@@ -42,9 +40,8 @@ var toAPIErrorTests = []struct {
|
|||||||
{err: ObjectNameInvalid{}, errCode: ErrInvalidObjectName},
|
{err: ObjectNameInvalid{}, errCode: ErrInvalidObjectName},
|
||||||
{err: InvalidUploadID{}, errCode: ErrNoSuchUpload},
|
{err: InvalidUploadID{}, errCode: ErrNoSuchUpload},
|
||||||
{err: InvalidPart{}, errCode: ErrInvalidPart},
|
{err: InvalidPart{}, errCode: ErrInvalidPart},
|
||||||
{err: InsufficientReadQuorum{}, errCode: ErrSlowDown},
|
{err: InsufficientReadQuorum{}, errCode: ErrSlowDownRead},
|
||||||
{err: InsufficientWriteQuorum{}, errCode: ErrSlowDown},
|
{err: InsufficientWriteQuorum{}, errCode: ErrSlowDownWrite},
|
||||||
{err: InvalidMarkerPrefixCombination{}, errCode: ErrNotImplemented},
|
|
||||||
{err: InvalidUploadIDKeyCombination{}, errCode: ErrNotImplemented},
|
{err: InvalidUploadIDKeyCombination{}, errCode: ErrNotImplemented},
|
||||||
{err: MalformedUploadID{}, errCode: ErrNoSuchUpload},
|
{err: MalformedUploadID{}, errCode: ErrNoSuchUpload},
|
||||||
{err: PartTooSmall{}, errCode: ErrEntityTooSmall},
|
{err: PartTooSmall{}, errCode: ErrEntityTooSmall},
|
||||||
@@ -67,11 +64,6 @@ var toAPIErrorTests = []struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIErrCode(t *testing.T) {
|
func TestAPIErrCode(t *testing.T) {
|
||||||
disk := filepath.Join(globalTestTmpDir, "minio-"+nextSuffix())
|
|
||||||
defer os.RemoveAll(disk)
|
|
||||||
|
|
||||||
initFSObjects(disk, t)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
for i, testCase := range toAPIErrorTests {
|
for i, testCase := range toAPIErrorTests {
|
||||||
errCode := toAPIErrorCode(ctx, testCase.err)
|
errCode := toAPIErrorCode(ctx, testCase.err)
|
||||||
@@ -80,3 +72,19 @@ func TestAPIErrCode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if an API error is properly defined
|
||||||
|
func TestAPIErrCodeDefinition(t *testing.T) {
|
||||||
|
for errAPI := ErrNone + 1; errAPI < apiErrCodeEnd; errAPI++ {
|
||||||
|
errCode, ok := errorCodes[errAPI]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal(errAPI, "error code is not defined in the API error code table")
|
||||||
|
}
|
||||||
|
if errCode.Code == "" {
|
||||||
|
t.Fatal(errAPI, "error code has an empty XML code")
|
||||||
|
}
|
||||||
|
if errCode.HTTPStatusCode == 0 {
|
||||||
|
t.Fatal(errAPI, "error code has a zero HTTP status code")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+34
-18
@@ -20,13 +20,14 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
@@ -64,15 +65,31 @@ func setCommonHeaders(w http.ResponseWriter) {
|
|||||||
|
|
||||||
// Encodes the response headers into XML format.
|
// Encodes the response headers into XML format.
|
||||||
func encodeResponse(response interface{}) []byte {
|
func encodeResponse(response interface{}) []byte {
|
||||||
var bytesBuffer bytes.Buffer
|
var buf bytes.Buffer
|
||||||
bytesBuffer.WriteString(xxml.Header)
|
buf.WriteString(xml.Header)
|
||||||
buf, err := xxml.Marshal(response)
|
if err := xml.NewEncoder(&buf).Encode(response); err != nil {
|
||||||
if err != nil {
|
|
||||||
logger.LogIf(GlobalContext, err)
|
logger.LogIf(GlobalContext, err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
bytesBuffer.Write(buf)
|
return buf.Bytes()
|
||||||
return bytesBuffer.Bytes()
|
}
|
||||||
|
|
||||||
|
// Use this encodeResponseList() to support control characters
|
||||||
|
// this function must be used by only ListObjects() for objects
|
||||||
|
// with control characters, this is a specialized extension
|
||||||
|
// to support AWS S3 compatible behavior.
|
||||||
|
//
|
||||||
|
// Do not use this function for anything other than ListObjects()
|
||||||
|
// variants, please open a github discussion if you wish to use
|
||||||
|
// this in other places.
|
||||||
|
func encodeResponseList(response interface{}) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString(xxml.Header)
|
||||||
|
if err := xxml.NewEncoder(&buf).Encode(response); err != nil {
|
||||||
|
logger.LogIf(GlobalContext, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encodes the response headers into JSON format.
|
// Encodes the response headers into JSON format.
|
||||||
@@ -116,16 +133,15 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
w.Header().Set(xhttp.Expires, objInfo.Expires.UTC().Format(http.TimeFormat))
|
w.Header().Set(xhttp.Expires, objInfo.Expires.UTC().Format(http.TimeFormat))
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalCacheConfig.Enabled {
|
|
||||||
w.Header().Set(xhttp.XCache, objInfo.CacheStatus.String())
|
|
||||||
w.Header().Set(xhttp.XCacheLookup, objInfo.CacheLookupStatus.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set tag count if object has tags
|
// Set tag count if object has tags
|
||||||
if len(objInfo.UserTags) > 0 {
|
if len(objInfo.UserTags) > 0 {
|
||||||
tags, _ := url.ParseQuery(objInfo.UserTags)
|
tags, _ := tags.ParseObjectTags(objInfo.UserTags)
|
||||||
if len(tags) > 0 {
|
if tags.Count() > 0 {
|
||||||
w.Header()[xhttp.AmzTagCount] = []string{strconv.Itoa(len(tags))}
|
w.Header()[xhttp.AmzTagCount] = []string{strconv.Itoa(tags.Count())}
|
||||||
|
if opts.Tagging {
|
||||||
|
// This is MinIO only extension to return back tags along with the count.
|
||||||
|
w.Header()[xhttp.AmzObjectTagging] = []string{objInfo.UserTags}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +152,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||||
// Do not need to send any internal metadata
|
// Do not need to send any internal metadata
|
||||||
// values to client.
|
// values to client.
|
||||||
continue
|
continue
|
||||||
@@ -149,7 +165,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
|
|
||||||
var isSet bool
|
var isSet bool
|
||||||
for _, userMetadataPrefix := range userMetadataKeyPrefixes {
|
for _, userMetadataPrefix := range userMetadataKeyPrefixes {
|
||||||
if !strings.HasPrefix(strings.ToLower(k), strings.ToLower(userMetadataPrefix)) {
|
if !stringsHasPrefixFold(k, userMetadataPrefix) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
w.Header()[strings.ToLower(k)] = []string{v}
|
w.Header()[strings.ToLower(k)] = []string{v}
|
||||||
@@ -186,7 +202,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the relevant version ID as part of the response header.
|
// Set the relevant version ID as part of the response header.
|
||||||
if objInfo.VersionID != "" {
|
if objInfo.VersionID != "" && objInfo.VersionID != nullVersionID {
|
||||||
w.Header()[xhttp.AmzVersionID] = []string{objInfo.VersionID}
|
w.Header()[xhttp.AmzVersionID] = []string{objInfo.VersionID}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ func getListObjectsV1Args(values url.Values) (prefix, marker, delimiter string,
|
|||||||
maxkeys = maxObjectList
|
maxkeys = maxObjectList
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix = trimLeadingSlash(values.Get("prefix"))
|
prefix = values.Get("prefix")
|
||||||
marker = trimLeadingSlash(values.Get("marker"))
|
marker = values.Get("marker")
|
||||||
delimiter = values.Get("delimiter")
|
delimiter = values.Get("delimiter")
|
||||||
encodingType = values.Get("encoding-type")
|
encodingType = values.Get("encoding-type")
|
||||||
return
|
return
|
||||||
@@ -57,8 +57,8 @@ func getListBucketObjectVersionsArgs(values url.Values) (prefix, marker, delimit
|
|||||||
maxkeys = maxObjectList
|
maxkeys = maxObjectList
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix = trimLeadingSlash(values.Get("prefix"))
|
prefix = values.Get("prefix")
|
||||||
marker = trimLeadingSlash(values.Get("key-marker"))
|
marker = values.Get("key-marker")
|
||||||
delimiter = values.Get("delimiter")
|
delimiter = values.Get("delimiter")
|
||||||
encodingType = values.Get("encoding-type")
|
encodingType = values.Get("encoding-type")
|
||||||
versionIDMarker = values.Get("version-id-marker")
|
versionIDMarker = values.Get("version-id-marker")
|
||||||
@@ -87,8 +87,8 @@ func getListObjectsV2Args(values url.Values) (prefix, token, startAfter, delimit
|
|||||||
maxkeys = maxObjectList
|
maxkeys = maxObjectList
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix = trimLeadingSlash(values.Get("prefix"))
|
prefix = values.Get("prefix")
|
||||||
startAfter = trimLeadingSlash(values.Get("start-after"))
|
startAfter = values.Get("start-after")
|
||||||
delimiter = values.Get("delimiter")
|
delimiter = values.Get("delimiter")
|
||||||
fetchOwner = values.Get("fetch-owner") == "true"
|
fetchOwner = values.Get("fetch-owner") == "true"
|
||||||
encodingType = values.Get("encoding-type")
|
encodingType = values.Get("encoding-type")
|
||||||
@@ -118,8 +118,8 @@ func getBucketMultipartResources(values url.Values) (prefix, keyMarker, uploadID
|
|||||||
maxUploads = maxUploadsList
|
maxUploads = maxUploadsList
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix = trimLeadingSlash(values.Get("prefix"))
|
prefix = values.Get("prefix")
|
||||||
keyMarker = trimLeadingSlash(values.Get("key-marker"))
|
keyMarker = values.Get("key-marker")
|
||||||
uploadIDMarker = values.Get("upload-id-marker")
|
uploadIDMarker = values.Get("upload-id-marker")
|
||||||
delimiter = values.Get("delimiter")
|
delimiter = values.Get("delimiter")
|
||||||
encodingType = values.Get("encoding-type")
|
encodingType = values.Get("encoding-type")
|
||||||
|
|||||||
+139
-77
@@ -29,20 +29,21 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/minio/internal/amztime"
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
"github.com/minio/minio/internal/handlers"
|
"github.com/minio/minio/internal/handlers"
|
||||||
"github.com/minio/minio/internal/hash"
|
"github.com/minio/minio/internal/hash"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/pkg/v2/policy"
|
||||||
|
xxml "github.com/minio/xxml"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// RFC3339 a subset of the ISO8601 timestamp format. e.g 2014-04-29T18:30:38Z
|
maxObjectList = 1000 // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse.
|
||||||
iso8601TimeFormat = "2006-01-02T15:04:05.000Z" // Reply date format with nanosecond precision.
|
maxDeleteList = 1000 // Limit number of objects deleted in a delete call.
|
||||||
maxObjectList = 1000 // Limit number of objects in a listObjectsResponse/listObjectsVersionsResponse.
|
maxUploadsList = 10000 // Limit number of uploads in a listUploadsResponse.
|
||||||
maxDeleteList = 1000 // Limit number of objects deleted in a delete call.
|
maxPartsList = 10000 // Limit number of parts in a listPartsResponse.
|
||||||
maxUploadsList = 10000 // Limit number of uploads in a listUploadsResponse.
|
|
||||||
maxPartsList = 10000 // Limit number of parts in a listPartsResponse.
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LocationResponse - format for location response.
|
// LocationResponse - format for location response.
|
||||||
@@ -84,14 +85,13 @@ type ListVersionsResponse struct {
|
|||||||
VersionIDMarker string `xml:"VersionIdMarker"`
|
VersionIDMarker string `xml:"VersionIdMarker"`
|
||||||
|
|
||||||
MaxKeys int
|
MaxKeys int
|
||||||
Delimiter string
|
Delimiter string `xml:"Delimiter,omitempty"`
|
||||||
// A flag that indicates whether or not ListObjects returned all of the results
|
// A flag that indicates whether or not ListObjects returned all of the results
|
||||||
// that satisfied the search criteria.
|
// that satisfied the search criteria.
|
||||||
IsTruncated bool
|
IsTruncated bool
|
||||||
|
|
||||||
CommonPrefixes []CommonPrefix
|
CommonPrefixes []CommonPrefix
|
||||||
DeleteMarkers []DeleteMarkerVersion `xml:"DeleteMarker,omitempty"`
|
Versions []ObjectVersion
|
||||||
Versions []ObjectVersion `xml:"Version,omitempty"`
|
|
||||||
|
|
||||||
// Encoding type used to encode object keys in the response.
|
// Encoding type used to encode object keys in the response.
|
||||||
EncodingType string `xml:"EncodingType,omitempty"`
|
EncodingType string `xml:"EncodingType,omitempty"`
|
||||||
@@ -115,7 +115,7 @@ type ListObjectsResponse struct {
|
|||||||
NextMarker string `xml:"NextMarker,omitempty"`
|
NextMarker string `xml:"NextMarker,omitempty"`
|
||||||
|
|
||||||
MaxKeys int
|
MaxKeys int
|
||||||
Delimiter string
|
Delimiter string `xml:"Delimiter,omitempty"`
|
||||||
// A flag that indicates whether or not ListObjects returned all of the results
|
// A flag that indicates whether or not ListObjects returned all of the results
|
||||||
// that satisfied the search criteria.
|
// that satisfied the search criteria.
|
||||||
IsTruncated bool
|
IsTruncated bool
|
||||||
@@ -146,7 +146,7 @@ type ListObjectsV2Response struct {
|
|||||||
|
|
||||||
KeyCount int
|
KeyCount int
|
||||||
MaxKeys int
|
MaxKeys int
|
||||||
Delimiter string
|
Delimiter string `xml:"Delimiter,omitempty"`
|
||||||
// A flag that indicates whether or not ListObjects returned all of the results
|
// A flag that indicates whether or not ListObjects returned all of the results
|
||||||
// that satisfied the search criteria.
|
// that satisfied the search criteria.
|
||||||
IsTruncated bool
|
IsTruncated bool
|
||||||
@@ -166,10 +166,10 @@ type Part struct {
|
|||||||
Size int64
|
Size int64
|
||||||
|
|
||||||
// Checksum values
|
// Checksum values
|
||||||
ChecksumCRC32 string
|
ChecksumCRC32 string `xml:"ChecksumCRC32,omitempty"`
|
||||||
ChecksumCRC32C string
|
ChecksumCRC32C string `xml:"ChecksumCRC32C,omitempty"`
|
||||||
ChecksumSHA1 string
|
ChecksumSHA1 string `xml:"ChecksumSHA1,omitempty"`
|
||||||
ChecksumSHA256 string
|
ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPartsResponse - format for list parts response.
|
// ListPartsResponse - format for list parts response.
|
||||||
@@ -205,7 +205,7 @@ type ListMultipartUploadsResponse struct {
|
|||||||
UploadIDMarker string `xml:"UploadIdMarker"`
|
UploadIDMarker string `xml:"UploadIdMarker"`
|
||||||
NextKeyMarker string
|
NextKeyMarker string
|
||||||
NextUploadIDMarker string `xml:"NextUploadIdMarker"`
|
NextUploadIDMarker string `xml:"NextUploadIdMarker"`
|
||||||
Delimiter string
|
Delimiter string `xml:"Delimiter,omitempty"`
|
||||||
Prefix string
|
Prefix string
|
||||||
EncodingType string `xml:"EncodingType,omitempty"`
|
EncodingType string `xml:"EncodingType,omitempty"`
|
||||||
MaxUploads int
|
MaxUploads int
|
||||||
@@ -256,6 +256,19 @@ type ObjectVersion struct {
|
|||||||
Object
|
Object
|
||||||
IsLatest bool
|
IsLatest bool
|
||||||
VersionID string `xml:"VersionId"`
|
VersionID string `xml:"VersionId"`
|
||||||
|
|
||||||
|
isDeleteMarker bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalXML - marshal ObjectVersion
|
||||||
|
func (o ObjectVersion) MarshalXML(e *xxml.Encoder, start xxml.StartElement) error {
|
||||||
|
if o.isDeleteMarker {
|
||||||
|
start.Name.Local = "DeleteMarker"
|
||||||
|
} else {
|
||||||
|
start.Name.Local = "Version"
|
||||||
|
}
|
||||||
|
type objectVersionWrapper ObjectVersion
|
||||||
|
return e.EncodeElement(objectVersionWrapper(o), start)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteMarkerVersion container for delete marker metadata
|
// DeleteMarkerVersion container for delete marker metadata
|
||||||
@@ -302,12 +315,12 @@ func (s *Metadata) Set(k, v string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type xmlKeyEntry struct {
|
type xmlKeyEntry struct {
|
||||||
XMLName xml.Name
|
XMLName xxml.Name
|
||||||
Value string `xml:",chardata"`
|
Value string `xml:",chardata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalXML - StringMap marshals into XML.
|
// MarshalXML - StringMap marshals into XML.
|
||||||
func (s *Metadata) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
func (s *Metadata) MarshalXML(e *xxml.Encoder, start xxml.StartElement) error {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -322,7 +335,7 @@ func (s *Metadata) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|||||||
|
|
||||||
for _, item := range s.Items {
|
for _, item := range s.Items {
|
||||||
if err := e.Encode(xmlKeyEntry{
|
if err := e.Encode(xmlKeyEntry{
|
||||||
XMLName: xml.Name{Local: item.Key},
|
XMLName: xxml.Name{Local: item.Key},
|
||||||
Value: item.Value,
|
Value: item.Value,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -332,6 +345,13 @@ func (s *Metadata) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|||||||
return e.EncodeToken(start.End())
|
return e.EncodeToken(start.End())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ObjectInternalInfo contains some internal information about a given
|
||||||
|
// object, it will printed in listing calls with enabled metadata.
|
||||||
|
type ObjectInternalInfo struct {
|
||||||
|
K int // Data blocks
|
||||||
|
M int // Parity blocks
|
||||||
|
}
|
||||||
|
|
||||||
// Object container for object metadata
|
// Object container for object metadata
|
||||||
type Object struct {
|
type Object struct {
|
||||||
Key string
|
Key string
|
||||||
@@ -340,13 +360,16 @@ type Object struct {
|
|||||||
Size int64
|
Size int64
|
||||||
|
|
||||||
// Owner of the object.
|
// Owner of the object.
|
||||||
Owner Owner
|
Owner *Owner `xml:"Owner,omitempty"`
|
||||||
|
|
||||||
// The class of storage used to store the object.
|
// The class of storage used to store the object.
|
||||||
StorageClass string
|
StorageClass string
|
||||||
|
|
||||||
// UserMetadata user-defined metadata
|
// UserMetadata user-defined metadata
|
||||||
UserMetadata *Metadata `xml:"UserMetadata,omitempty"`
|
UserMetadata *Metadata `xml:"UserMetadata,omitempty"`
|
||||||
|
UserTags string `xml:"UserTags,omitempty"`
|
||||||
|
|
||||||
|
Internal *ObjectInternalInfo `xml:"Internal,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyObjectResponse container returns ETag and LastModified of the successfully copied object
|
// CopyObjectResponse container returns ETag and LastModified of the successfully copied object
|
||||||
@@ -390,10 +413,10 @@ type CompleteMultipartUploadResponse struct {
|
|||||||
Key string
|
Key string
|
||||||
ETag string
|
ETag string
|
||||||
|
|
||||||
ChecksumCRC32 string
|
ChecksumCRC32 string `xml:"ChecksumCRC32,omitempty"`
|
||||||
ChecksumCRC32C string
|
ChecksumCRC32C string `xml:"ChecksumCRC32C,omitempty"`
|
||||||
ChecksumSHA1 string
|
ChecksumSHA1 string `xml:"ChecksumSHA1,omitempty"`
|
||||||
ChecksumSHA256 string
|
ChecksumSHA256 string `xml:"ChecksumSHA256,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteError structure.
|
// DeleteError structure.
|
||||||
@@ -469,7 +492,7 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
|
|||||||
for _, bucket := range buckets {
|
for _, bucket := range buckets {
|
||||||
listbuckets = append(listbuckets, Bucket{
|
listbuckets = append(listbuckets, Bucket{
|
||||||
Name: bucket.Name,
|
Name: bucket.Name,
|
||||||
CreationDate: bucket.Created.UTC().Format(iso8601TimeFormat),
|
CreationDate: amztime.ISO8601Format(bucket.Created.UTC()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,38 +503,30 @@ func generateListBucketsResponse(buckets []BucketInfo) ListBucketsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
// generates an ListBucketVersions response for the said bucket with other enumerated options.
|
||||||
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo) ListVersionsResponse {
|
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo, metadata metaCheckFn) ListVersionsResponse {
|
||||||
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
versions := make([]ObjectVersion, 0, len(resp.Objects))
|
||||||
deleteMarkers := make([]DeleteMarkerVersion, 0, len(resp.Objects))
|
|
||||||
|
|
||||||
owner := Owner{
|
owner := &Owner{
|
||||||
ID: globalMinioDefaultOwnerID,
|
ID: globalMinioDefaultOwnerID,
|
||||||
DisplayName: "minio",
|
DisplayName: "minio",
|
||||||
}
|
}
|
||||||
data := ListVersionsResponse{}
|
data := ListVersionsResponse{}
|
||||||
|
var lastObjMetaName string
|
||||||
|
var tagErr, metaErr APIErrorCode = -1, -1
|
||||||
|
|
||||||
for _, object := range resp.Objects {
|
for _, object := range resp.Objects {
|
||||||
if object.Name == "" {
|
if object.Name == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Cache checks for the same object
|
||||||
if object.DeleteMarker {
|
if metadata != nil && lastObjMetaName != object.Name {
|
||||||
deleteMarker := DeleteMarkerVersion{
|
tagErr = metadata(object.Name, policy.GetObjectTaggingAction)
|
||||||
Key: s3EncodeName(object.Name, encodingType),
|
metaErr = metadata(object.Name, policy.GetObjectAction)
|
||||||
LastModified: object.ModTime.UTC().Format(iso8601TimeFormat),
|
lastObjMetaName = object.Name
|
||||||
Owner: owner,
|
|
||||||
VersionID: object.VersionID,
|
|
||||||
}
|
|
||||||
if deleteMarker.VersionID == "" {
|
|
||||||
deleteMarker.VersionID = nullVersionID
|
|
||||||
}
|
|
||||||
deleteMarker.IsLatest = object.IsLatest
|
|
||||||
deleteMarkers = append(deleteMarkers, deleteMarker)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
content := ObjectVersion{}
|
content := ObjectVersion{}
|
||||||
content.Key = s3EncodeName(object.Name, encodingType)
|
content.Key = s3EncodeName(object.Name, encodingType)
|
||||||
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
|
content.LastModified = amztime.ISO8601Format(object.ModTime.UTC())
|
||||||
if object.ETag != "" {
|
if object.ETag != "" {
|
||||||
content.ETag = "\"" + object.ETag + "\""
|
content.ETag = "\"" + object.ETag + "\""
|
||||||
}
|
}
|
||||||
@@ -521,18 +536,48 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
} else {
|
} else {
|
||||||
content.StorageClass = globalMinioDefaultStorageClass
|
content.StorageClass = globalMinioDefaultStorageClass
|
||||||
}
|
}
|
||||||
|
if tagErr == ErrNone {
|
||||||
|
content.UserTags = object.UserTags
|
||||||
|
}
|
||||||
|
if metaErr == ErrNone {
|
||||||
|
content.UserMetadata = &Metadata{}
|
||||||
|
switch kind, _ := crypto.IsEncrypted(object.UserDefined); kind {
|
||||||
|
case crypto.S3:
|
||||||
|
content.UserMetadata.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
|
||||||
|
case crypto.S3KMS:
|
||||||
|
content.UserMetadata.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS)
|
||||||
|
case crypto.SSEC:
|
||||||
|
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
||||||
|
}
|
||||||
|
for k, v := range cleanMinioInternalMetadataKeys(object.UserDefined) {
|
||||||
|
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||||
|
// Do not need to send any internal metadata
|
||||||
|
// values to client.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
|
||||||
|
if equals(k, xhttp.AmzMetaUnencryptedContentLength, xhttp.AmzMetaUnencryptedContentMD5) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
content.UserMetadata.Set(k, v)
|
||||||
|
}
|
||||||
|
content.Internal = &ObjectInternalInfo{
|
||||||
|
K: object.DataBlocks,
|
||||||
|
M: object.ParityBlocks,
|
||||||
|
}
|
||||||
|
}
|
||||||
content.Owner = owner
|
content.Owner = owner
|
||||||
content.VersionID = object.VersionID
|
content.VersionID = object.VersionID
|
||||||
if content.VersionID == "" {
|
if content.VersionID == "" {
|
||||||
content.VersionID = nullVersionID
|
content.VersionID = nullVersionID
|
||||||
}
|
}
|
||||||
content.IsLatest = object.IsLatest
|
content.IsLatest = object.IsLatest
|
||||||
|
content.isDeleteMarker = object.DeleteMarker
|
||||||
versions = append(versions, content)
|
versions = append(versions, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Name = bucket
|
data.Name = bucket
|
||||||
data.Versions = versions
|
data.Versions = versions
|
||||||
data.DeleteMarkers = deleteMarkers
|
|
||||||
data.EncodingType = encodingType
|
data.EncodingType = encodingType
|
||||||
data.Prefix = s3EncodeName(prefix, encodingType)
|
data.Prefix = s3EncodeName(prefix, encodingType)
|
||||||
data.KeyMarker = s3EncodeName(marker, encodingType)
|
data.KeyMarker = s3EncodeName(marker, encodingType)
|
||||||
@@ -557,7 +602,7 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
|
|||||||
// generates an ListObjectsV1 response for the said bucket with other enumerated options.
|
// generates an ListObjectsV1 response for the said bucket with other enumerated options.
|
||||||
func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
|
func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
|
||||||
contents := make([]Object, 0, len(resp.Objects))
|
contents := make([]Object, 0, len(resp.Objects))
|
||||||
owner := Owner{
|
owner := &Owner{
|
||||||
ID: globalMinioDefaultOwnerID,
|
ID: globalMinioDefaultOwnerID,
|
||||||
DisplayName: "minio",
|
DisplayName: "minio",
|
||||||
}
|
}
|
||||||
@@ -569,7 +614,7 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingTy
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
content.Key = s3EncodeName(object.Name, encodingType)
|
content.Key = s3EncodeName(object.Name, encodingType)
|
||||||
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
|
content.LastModified = amztime.ISO8601Format(object.ModTime.UTC())
|
||||||
if object.ETag != "" {
|
if object.ETag != "" {
|
||||||
content.ETag = "\"" + object.ETag + "\""
|
content.ETag = "\"" + object.ETag + "\""
|
||||||
}
|
}
|
||||||
@@ -604,12 +649,16 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingTy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generates an ListObjectsV2 response for the said bucket with other enumerated options.
|
// generates an ListObjectsV2 response for the said bucket with other enumerated options.
|
||||||
func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata bool) ListObjectsV2Response {
|
func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata metaCheckFn) ListObjectsV2Response {
|
||||||
contents := make([]Object, 0, len(objects))
|
contents := make([]Object, 0, len(objects))
|
||||||
owner := Owner{
|
var owner *Owner
|
||||||
ID: globalMinioDefaultOwnerID,
|
if fetchOwner {
|
||||||
DisplayName: "minio",
|
owner = &Owner{
|
||||||
|
ID: globalMinioDefaultOwnerID,
|
||||||
|
DisplayName: "minio",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data := ListObjectsV2Response{}
|
data := ListObjectsV2Response{}
|
||||||
|
|
||||||
for _, object := range objects {
|
for _, object := range objects {
|
||||||
@@ -618,7 +667,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
content.Key = s3EncodeName(object.Name, encodingType)
|
content.Key = s3EncodeName(object.Name, encodingType)
|
||||||
content.LastModified = object.ModTime.UTC().Format(iso8601TimeFormat)
|
content.LastModified = amztime.ISO8601Format(object.ModTime.UTC())
|
||||||
if object.ETag != "" {
|
if object.ETag != "" {
|
||||||
content.ETag = "\"" + object.ETag + "\""
|
content.ETag = "\"" + object.ETag + "\""
|
||||||
}
|
}
|
||||||
@@ -629,27 +678,36 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
|||||||
content.StorageClass = globalMinioDefaultStorageClass
|
content.StorageClass = globalMinioDefaultStorageClass
|
||||||
}
|
}
|
||||||
content.Owner = owner
|
content.Owner = owner
|
||||||
if metadata {
|
if metadata != nil {
|
||||||
content.UserMetadata = &Metadata{}
|
if metadata(object.Name, policy.GetObjectTaggingAction) == ErrNone {
|
||||||
switch kind, _ := crypto.IsEncrypted(object.UserDefined); kind {
|
content.UserTags = object.UserTags
|
||||||
case crypto.S3:
|
|
||||||
content.UserMetadata.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
|
|
||||||
case crypto.S3KMS:
|
|
||||||
content.UserMetadata.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS)
|
|
||||||
case crypto.SSEC:
|
|
||||||
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
|
||||||
}
|
}
|
||||||
for k, v := range CleanMinioInternalMetadataKeys(object.UserDefined) {
|
if metadata(object.Name, policy.GetObjectAction) == ErrNone {
|
||||||
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
content.UserMetadata = &Metadata{}
|
||||||
// Do not need to send any internal metadata
|
switch kind, _ := crypto.IsEncrypted(object.UserDefined); kind {
|
||||||
// values to client.
|
case crypto.S3:
|
||||||
continue
|
content.UserMetadata.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
|
||||||
|
case crypto.S3KMS:
|
||||||
|
content.UserMetadata.Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS)
|
||||||
|
case crypto.SSEC:
|
||||||
|
content.UserMetadata.Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, xhttp.AmzEncryptionAES)
|
||||||
}
|
}
|
||||||
// https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
|
for k, v := range cleanMinioInternalMetadataKeys(object.UserDefined) {
|
||||||
if equals(k, xhttp.AmzMetaUnencryptedContentLength, xhttp.AmzMetaUnencryptedContentMD5) {
|
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||||
continue
|
// Do not need to send any internal metadata
|
||||||
|
// values to client.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// https://github.com/google/security-research/security/advisories/GHSA-76wf-9vgp-pj7w
|
||||||
|
if equals(k, xhttp.AmzMetaUnencryptedContentLength, xhttp.AmzMetaUnencryptedContentMD5) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
content.UserMetadata.Set(k, v)
|
||||||
|
}
|
||||||
|
content.Internal = &ObjectInternalInfo{
|
||||||
|
K: object.DataBlocks,
|
||||||
|
M: object.ParityBlocks,
|
||||||
}
|
}
|
||||||
content.UserMetadata.Set(k, v)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contents = append(contents, content)
|
contents = append(contents, content)
|
||||||
@@ -677,11 +735,13 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type metaCheckFn = func(name string, action policy.Action) (s3Err APIErrorCode)
|
||||||
|
|
||||||
// generates CopyObjectResponse from etag and lastModified time.
|
// generates CopyObjectResponse from etag and lastModified time.
|
||||||
func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectResponse {
|
func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectResponse {
|
||||||
return CopyObjectResponse{
|
return CopyObjectResponse{
|
||||||
ETag: "\"" + etag + "\"",
|
ETag: "\"" + etag + "\"",
|
||||||
LastModified: lastModified.UTC().Format(iso8601TimeFormat),
|
LastModified: amztime.ISO8601Format(lastModified.UTC()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -689,7 +749,7 @@ func generateCopyObjectResponse(etag string, lastModified time.Time) CopyObjectR
|
|||||||
func generateCopyObjectPartResponse(etag string, lastModified time.Time) CopyObjectPartResponse {
|
func generateCopyObjectPartResponse(etag string, lastModified time.Time) CopyObjectPartResponse {
|
||||||
return CopyObjectPartResponse{
|
return CopyObjectPartResponse{
|
||||||
ETag: "\"" + etag + "\"",
|
ETag: "\"" + etag + "\"",
|
||||||
LastModified: lastModified.UTC().Format(iso8601TimeFormat),
|
LastModified: amztime.ISO8601Format(lastModified.UTC()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -704,7 +764,7 @@ func generateInitiateMultipartUploadResponse(bucket, key, uploadID string) Initi
|
|||||||
|
|
||||||
// generates CompleteMultipartUploadResponse for given bucket, key, location and ETag.
|
// generates CompleteMultipartUploadResponse for given bucket, key, location and ETag.
|
||||||
func generateCompleteMultpartUploadResponse(bucket, key, location string, oi ObjectInfo) CompleteMultipartUploadResponse {
|
func generateCompleteMultpartUploadResponse(bucket, key, location string, oi ObjectInfo) CompleteMultipartUploadResponse {
|
||||||
cs := oi.decryptChecksums()
|
cs := oi.decryptChecksums(0)
|
||||||
c := CompleteMultipartUploadResponse{
|
c := CompleteMultipartUploadResponse{
|
||||||
Location: location,
|
Location: location,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
@@ -749,7 +809,7 @@ func generateListPartsResponse(partsInfo ListPartsInfo, encodingType string) Lis
|
|||||||
newPart.PartNumber = part.PartNumber
|
newPart.PartNumber = part.PartNumber
|
||||||
newPart.ETag = "\"" + part.ETag + "\""
|
newPart.ETag = "\"" + part.ETag + "\""
|
||||||
newPart.Size = part.Size
|
newPart.Size = part.Size
|
||||||
newPart.LastModified = part.LastModified.UTC().Format(iso8601TimeFormat)
|
newPart.LastModified = amztime.ISO8601Format(part.LastModified.UTC())
|
||||||
newPart.ChecksumCRC32 = part.ChecksumCRC32
|
newPart.ChecksumCRC32 = part.ChecksumCRC32
|
||||||
newPart.ChecksumCRC32C = part.ChecksumCRC32C
|
newPart.ChecksumCRC32C = part.ChecksumCRC32C
|
||||||
newPart.ChecksumSHA1 = part.ChecksumSHA1
|
newPart.ChecksumSHA1 = part.ChecksumSHA1
|
||||||
@@ -783,7 +843,7 @@ func generateListMultipartUploadsResponse(bucket string, multipartsInfo ListMult
|
|||||||
newUpload := Upload{}
|
newUpload := Upload{}
|
||||||
newUpload.UploadID = upload.UploadID
|
newUpload.UploadID = upload.UploadID
|
||||||
newUpload.Key = s3EncodeName(upload.Object, encodingType)
|
newUpload.Key = s3EncodeName(upload.Object, encodingType)
|
||||||
newUpload.Initiated = upload.Initiated.UTC().Format(iso8601TimeFormat)
|
newUpload.Initiated = amztime.ISO8601Format(upload.Initiated.UTC())
|
||||||
listMultipartUploadsResponse.Uploads[index] = newUpload
|
listMultipartUploadsResponse.Uploads[index] = newUpload
|
||||||
}
|
}
|
||||||
return listMultipartUploadsResponse
|
return listMultipartUploadsResponse
|
||||||
@@ -879,12 +939,14 @@ func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError
|
|||||||
|
|
||||||
// Generate error response.
|
// Generate error response.
|
||||||
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path,
|
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path,
|
||||||
w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
|
w.Header().Get(xhttp.AmzRequestID), w.Header().Get(xhttp.AmzRequestHostID))
|
||||||
encodedErrorResponse := encodeResponse(errorResponse)
|
encodedErrorResponse := encodeResponse(errorResponse)
|
||||||
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML)
|
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeErrorResponseHeadersOnly(w http.ResponseWriter, err APIError) {
|
func writeErrorResponseHeadersOnly(w http.ResponseWriter, err APIError) {
|
||||||
|
w.Header().Set(xMinIOErrCodeHeader, err.Code)
|
||||||
|
w.Header().Set(xMinIOErrDescHeader, "\""+err.Description+"\"")
|
||||||
writeResponse(w, err.HTTPStatusCode, nil, mimeNone)
|
writeResponse(w, err.HTTPStatusCode, nil, mimeNone)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -897,7 +959,7 @@ func writeErrorResponseString(ctx context.Context, w http.ResponseWriter, err AP
|
|||||||
// useful for admin APIs.
|
// useful for admin APIs.
|
||||||
func writeErrorResponseJSON(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
|
func writeErrorResponseJSON(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
|
||||||
// Generate error response.
|
// Generate error response.
|
||||||
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path, w.Header().Get(xhttp.AmzRequestID), globalDeploymentID)
|
errorResponse := getAPIErrorResponse(ctx, err, reqURL.Path, w.Header().Get(xhttp.AmzRequestID), w.Header().Get(xhttp.AmzRequestHostID))
|
||||||
encodedErrorResponse := encodeResponseJSON(errorResponse)
|
encodedErrorResponse := encodeResponseJSON(errorResponse)
|
||||||
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
|
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
|
||||||
}
|
}
|
||||||
@@ -916,7 +978,7 @@ func writeCustomErrorResponseJSON(ctx context.Context, w http.ResponseWriter, er
|
|||||||
BucketName: reqInfo.BucketName,
|
BucketName: reqInfo.BucketName,
|
||||||
Key: reqInfo.ObjectName,
|
Key: reqInfo.ObjectName,
|
||||||
RequestID: w.Header().Get(xhttp.AmzRequestID),
|
RequestID: w.Header().Get(xhttp.AmzRequestID),
|
||||||
HostID: globalDeploymentID,
|
HostID: globalDeploymentID(),
|
||||||
}
|
}
|
||||||
encodedErrorResponse := encodeResponseJSON(errorResponse)
|
encodedErrorResponse := encodeResponseJSON(errorResponse)
|
||||||
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
|
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
|
||||||
|
|||||||
+24
-25
@@ -22,12 +22,12 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/klauspost/compress/gzhttp"
|
"github.com/klauspost/compress/gzhttp"
|
||||||
"github.com/minio/console/restapi"
|
"github.com/minio/console/restapi"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/wildcard"
|
"github.com/minio/mux"
|
||||||
|
"github.com/minio/pkg/v2/wildcard"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,18 +61,6 @@ func newObjectLayerFn() ObjectLayer {
|
|||||||
return globalObjectAPI
|
return globalObjectAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCachedObjectLayerFn() CacheObjectLayer {
|
|
||||||
globalObjLayerMutex.RLock()
|
|
||||||
defer globalObjLayerMutex.RUnlock()
|
|
||||||
return globalCacheObjectAPI
|
|
||||||
}
|
|
||||||
|
|
||||||
func setCacheObjectLayer(c CacheObjectLayer) {
|
|
||||||
globalObjLayerMutex.Lock()
|
|
||||||
globalCacheObjectAPI = c
|
|
||||||
globalObjLayerMutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func setObjectLayer(o ObjectLayer) {
|
func setObjectLayer(o ObjectLayer) {
|
||||||
globalObjLayerMutex.Lock()
|
globalObjLayerMutex.Lock()
|
||||||
globalObjectAPI = o
|
globalObjectAPI = o
|
||||||
@@ -82,7 +70,6 @@ func setObjectLayer(o ObjectLayer) {
|
|||||||
// objectAPIHandler implements and provides http handlers for S3 API.
|
// objectAPIHandler implements and provides http handlers for S3 API.
|
||||||
type objectAPIHandlers struct {
|
type objectAPIHandlers struct {
|
||||||
ObjectAPI func() ObjectLayer
|
ObjectAPI func() ObjectLayer
|
||||||
CacheAPI func() CacheObjectLayer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getHost tries its best to return the request host.
|
// getHost tries its best to return the request host.
|
||||||
@@ -189,7 +176,6 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
// Initialize API.
|
// Initialize API.
|
||||||
api := objectAPIHandlers{
|
api := objectAPIHandlers{
|
||||||
ObjectAPI: newObjectLayerFn,
|
ObjectAPI: newObjectLayerFn,
|
||||||
CacheAPI: newCachedObjectLayerFn,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// API Router
|
// API Router
|
||||||
@@ -245,10 +231,10 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
router.Methods(http.MethodPut).Path("/{object:.+}").
|
router.Methods(http.MethodPut).Path("/{object:.+}").
|
||||||
HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").
|
HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").
|
||||||
HandlerFunc(collectAPIStats("copyobjectpart", maxClients(gz(httpTraceAll(api.CopyObjectPartHandler))))).
|
HandlerFunc(collectAPIStats("copyobjectpart", maxClients(gz(httpTraceAll(api.CopyObjectPartHandler))))).
|
||||||
Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
Queries("partNumber", "{partNumber:.*}", "uploadId", "{uploadId:.*}")
|
||||||
// PutObjectPart
|
// PutObjectPart
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("putobjectpart", maxClients(gz(httpTraceHdrs(api.PutObjectPartHandler))))).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
collectAPIStats("putobjectpart", maxClients(gz(httpTraceHdrs(api.PutObjectPartHandler))))).Queries("partNumber", "{partNumber:.*}", "uploadId", "{uploadId:.*}")
|
||||||
// ListObjectParts
|
// ListObjectParts
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("listobjectparts", maxClients(gz(httpTraceAll(api.ListObjectPartsHandler))))).Queries("uploadId", "{uploadId:.*}")
|
collectAPIStats("listobjectparts", maxClients(gz(httpTraceAll(api.ListObjectPartsHandler))))).Queries("uploadId", "{uploadId:.*}")
|
||||||
@@ -285,7 +271,10 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
// GetObjectLegalHold
|
// GetObjectLegalHold
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("getobjectlegalhold", maxClients(gz(httpTraceAll(api.GetObjectLegalHoldHandler))))).Queries("legal-hold", "")
|
collectAPIStats("getobjectlegalhold", maxClients(gz(httpTraceAll(api.GetObjectLegalHoldHandler))))).Queries("legal-hold", "")
|
||||||
// GetObject - note gzip compression is *not* added due to Range requests.
|
// GetObject with lambda ARNs
|
||||||
|
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||||
|
collectAPIStats("getobject", maxClients(gz(httpTraceHdrs(api.GetObjectLambdaHandler))))).Queries("lambdaArn", "{lambdaArn:.*}")
|
||||||
|
// GetObject
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("getobject", maxClients(gz(httpTraceHdrs(api.GetObjectHandler)))))
|
collectAPIStats("getobject", maxClients(gz(httpTraceHdrs(api.GetObjectHandler)))))
|
||||||
// CopyObject
|
// CopyObject
|
||||||
@@ -389,6 +378,9 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("listobjectsv2", maxClients(gz(httpTraceAll(api.ListObjectsV2Handler))))).Queries("list-type", "2")
|
collectAPIStats("listobjectsv2", maxClients(gz(httpTraceAll(api.ListObjectsV2Handler))))).Queries("list-type", "2")
|
||||||
// ListObjectVersions
|
// ListObjectVersions
|
||||||
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
|
collectAPIStats("listobjectversions", maxClients(gz(httpTraceAll(api.ListObjectVersionsMHandler))))).Queries("versions", "", "metadata", "true")
|
||||||
|
// ListObjectVersions
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("listobjectversions", maxClients(gz(httpTraceAll(api.ListObjectVersionsHandler))))).Queries("versions", "")
|
collectAPIStats("listobjectversions", maxClients(gz(httpTraceAll(api.ListObjectVersionsHandler))))).Queries("versions", "")
|
||||||
// GetBucketPolicyStatus
|
// GetBucketPolicyStatus
|
||||||
@@ -431,8 +423,9 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
router.Methods(http.MethodHead).HandlerFunc(
|
router.Methods(http.MethodHead).HandlerFunc(
|
||||||
collectAPIStats("headbucket", maxClients(gz(httpTraceAll(api.HeadBucketHandler)))))
|
collectAPIStats("headbucket", maxClients(gz(httpTraceAll(api.HeadBucketHandler)))))
|
||||||
// PostPolicy
|
// PostPolicy
|
||||||
router.Methods(http.MethodPost).HeadersRegexp(xhttp.ContentType, "multipart/form-data*").HandlerFunc(
|
router.Methods(http.MethodPost).MatcherFunc(func(r *http.Request, _ *mux.RouteMatch) bool {
|
||||||
collectAPIStats("postpolicybucket", maxClients(gz(httpTraceHdrs(api.PostPolicyBucketHandler)))))
|
return isRequestPostPolicySignatureV4(r)
|
||||||
|
}).HandlerFunc(collectAPIStats("postpolicybucket", maxClients(gz(httpTraceHdrs(api.PostPolicyBucketHandler)))))
|
||||||
// DeleteMultipleObjects
|
// DeleteMultipleObjects
|
||||||
router.Methods(http.MethodPost).HandlerFunc(
|
router.Methods(http.MethodPost).HandlerFunc(
|
||||||
collectAPIStats("deletemultipleobjects", maxClients(gz(httpTraceAll(api.DeleteMultipleObjectsHandler))))).Queries("delete", "")
|
collectAPIStats("deletemultipleobjects", maxClients(gz(httpTraceAll(api.DeleteMultipleObjectsHandler))))).Queries("delete", "")
|
||||||
@@ -454,10 +447,16 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
|
|
||||||
// MinIO extension API for replication.
|
// MinIO extension API for replication.
|
||||||
//
|
//
|
||||||
// GetBucketReplicationMetrics
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
|
collectAPIStats("getbucketreplicationmetrics", maxClients(gz(httpTraceAll(api.GetBucketReplicationMetricsV2Handler))))).Queries("replication-metrics", "2")
|
||||||
|
// deprecated handler
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketreplicationmetrics", maxClients(gz(httpTraceAll(api.GetBucketReplicationMetricsHandler))))).Queries("replication-metrics", "")
|
collectAPIStats("getbucketreplicationmetrics", maxClients(gz(httpTraceAll(api.GetBucketReplicationMetricsHandler))))).Queries("replication-metrics", "")
|
||||||
|
|
||||||
|
// ValidateBucketReplicationCreds
|
||||||
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
|
collectAPIStats("checkbucketreplicationconfiguration", maxClients(gz(httpTraceAll(api.ValidateBucketReplicationCredsHandler))))).Queries("replication-check", "")
|
||||||
|
|
||||||
// Register rejected bucket APIs
|
// Register rejected bucket APIs
|
||||||
for _, r := range rejectedBucketAPIs {
|
for _, r := range rejectedBucketAPIs {
|
||||||
router.Methods(r.methods...).
|
router.Methods(r.methods...).
|
||||||
@@ -513,8 +512,7 @@ func corsHandler(handler http.Handler) http.Handler {
|
|||||||
"x-amz*",
|
"x-amz*",
|
||||||
"*",
|
"*",
|
||||||
}
|
}
|
||||||
|
opts := cors.Options{
|
||||||
return cors.New(cors.Options{
|
|
||||||
AllowOriginFunc: func(origin string) bool {
|
AllowOriginFunc: func(origin string) bool {
|
||||||
for _, allowedOrigin := range globalAPIConfig.getCorsAllowOrigins() {
|
for _, allowedOrigin := range globalAPIConfig.getCorsAllowOrigins() {
|
||||||
if wildcard.MatchSimple(allowedOrigin, origin) {
|
if wildcard.MatchSimple(allowedOrigin, origin) {
|
||||||
@@ -535,5 +533,6 @@ func corsHandler(handler http.Handler) http.Handler {
|
|||||||
AllowedHeaders: commonS3Headers,
|
AllowedHeaders: commonS3Headers,
|
||||||
ExposedHeaders: commonS3Headers,
|
ExposedHeaders: commonS3Headers,
|
||||||
AllowCredentials: true,
|
AllowCredentials: true,
|
||||||
}).Handler(handler)
|
}
|
||||||
|
return cors.New(opts).Handler(handler)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-8
@@ -94,14 +94,8 @@ func s3URLEncode(s string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// s3EncodeName encodes string in response when encodingType is specified in AWS S3 requests.
|
// s3EncodeName encodes string in response when encodingType is specified in AWS S3 requests.
|
||||||
func s3EncodeName(name string, encodingType string) (result string) {
|
func s3EncodeName(name, encodingType string) string {
|
||||||
// Quick path to exit
|
if strings.ToLower(encodingType) == "url" {
|
||||||
if encodingType == "" {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
encodingType = strings.ToLower(encodingType)
|
|
||||||
switch encodingType {
|
|
||||||
case "url":
|
|
||||||
return s3URLEncode(name)
|
return s3URLEncode(name)
|
||||||
}
|
}
|
||||||
return name
|
return name
|
||||||
|
|||||||
+312
-287
File diff suppressed because one or more lines are too long
+131
-67
@@ -25,6 +25,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -39,8 +40,8 @@ import (
|
|||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
xjwt "github.com/minio/minio/internal/jwt"
|
xjwt "github.com/minio/minio/internal/jwt"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/bucket/policy"
|
"github.com/minio/minio/internal/mcontext"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Verify if request has JWT.
|
// Verify if request has JWT.
|
||||||
@@ -73,8 +74,11 @@ func isRequestPresignedSignatureV2(r *http.Request) bool {
|
|||||||
|
|
||||||
// Verify if request has AWS Post policy Signature Version '4'.
|
// Verify if request has AWS Post policy Signature Version '4'.
|
||||||
func isRequestPostPolicySignatureV4(r *http.Request) bool {
|
func isRequestPostPolicySignatureV4(r *http.Request) bool {
|
||||||
return strings.Contains(r.Header.Get(xhttp.ContentType), "multipart/form-data") &&
|
mediaType, _, err := mime.ParseMediaType(r.Header.Get(xhttp.ContentType))
|
||||||
r.Method == http.MethodPost
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return mediaType == "multipart/form-data" && r.Method == http.MethodPost
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify if the request has AWS Streaming Signature Version '4'. This is only valid for 'PUT' operation.
|
// Verify if the request has AWS Streaming Signature Version '4'. This is only valid for 'PUT' operation.
|
||||||
@@ -83,6 +87,18 @@ func isRequestSignStreamingV4(r *http.Request) bool {
|
|||||||
r.Method == http.MethodPut
|
r.Method == http.MethodPut
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify if the request has AWS Streaming Signature Version '4'. This is only valid for 'PUT' operation.
|
||||||
|
func isRequestSignStreamingTrailerV4(r *http.Request) bool {
|
||||||
|
return r.Header.Get(xhttp.AmzContentSha256) == streamingContentSHA256Trailer &&
|
||||||
|
r.Method == http.MethodPut
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify if the request has AWS Streaming Signature Version '4', with unsigned content and trailer.
|
||||||
|
func isRequestUnsignedTrailerV4(r *http.Request) bool {
|
||||||
|
return r.Header.Get(xhttp.AmzContentSha256) == unsignedPayloadTrailer &&
|
||||||
|
r.Method == http.MethodPut && strings.Contains(r.Header.Get(xhttp.ContentEncoding), streamingContentEncoding)
|
||||||
|
}
|
||||||
|
|
||||||
// Authorization type.
|
// Authorization type.
|
||||||
//
|
//
|
||||||
//go:generate stringer -type=authType -trimprefix=authType $GOFILE
|
//go:generate stringer -type=authType -trimprefix=authType $GOFILE
|
||||||
@@ -100,10 +116,12 @@ const (
|
|||||||
authTypeSignedV2
|
authTypeSignedV2
|
||||||
authTypeJWT
|
authTypeJWT
|
||||||
authTypeSTS
|
authTypeSTS
|
||||||
|
authTypeStreamingSignedTrailer
|
||||||
|
authTypeStreamingUnsignedTrailer
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get request authentication type.
|
// Get request authentication type.
|
||||||
func getRequestAuthType(r *http.Request) authType {
|
func getRequestAuthType(r *http.Request) (at authType) {
|
||||||
if r.URL != nil {
|
if r.URL != nil {
|
||||||
var err error
|
var err error
|
||||||
r.Form, err = url.ParseQuery(r.URL.RawQuery)
|
r.Form, err = url.ParseQuery(r.URL.RawQuery)
|
||||||
@@ -118,6 +136,10 @@ func getRequestAuthType(r *http.Request) authType {
|
|||||||
return authTypePresignedV2
|
return authTypePresignedV2
|
||||||
} else if isRequestSignStreamingV4(r) {
|
} else if isRequestSignStreamingV4(r) {
|
||||||
return authTypeStreamingSigned
|
return authTypeStreamingSigned
|
||||||
|
} else if isRequestSignStreamingTrailerV4(r) {
|
||||||
|
return authTypeStreamingSignedTrailer
|
||||||
|
} else if isRequestUnsignedTrailerV4(r) {
|
||||||
|
return authTypeStreamingUnsignedTrailer
|
||||||
} else if isRequestSignatureV4(r) {
|
} else if isRequestSignatureV4(r) {
|
||||||
return authTypeSigned
|
return authTypeSigned
|
||||||
} else if isRequestPresignedSignatureV4(r) {
|
} else if isRequestPresignedSignatureV4(r) {
|
||||||
@@ -134,7 +156,7 @@ func getRequestAuthType(r *http.Request) authType {
|
|||||||
return authTypeUnknown
|
return authTypeUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateAdminSignature(ctx context.Context, r *http.Request, region string) (auth.Credentials, map[string]interface{}, bool, APIErrorCode) {
|
func validateAdminSignature(ctx context.Context, r *http.Request, region string) (auth.Credentials, bool, APIErrorCode) {
|
||||||
var cred auth.Credentials
|
var cred auth.Credentials
|
||||||
var owner bool
|
var owner bool
|
||||||
s3Err := ErrAccessDenied
|
s3Err := ErrAccessDenied
|
||||||
@@ -143,37 +165,38 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
|
|||||||
// We only support admin credentials to access admin APIs.
|
// We only support admin credentials to access admin APIs.
|
||||||
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return cred, nil, owner, s3Err
|
return cred, owner, s3Err
|
||||||
}
|
}
|
||||||
|
|
||||||
// we only support V4 (no presign) with auth body
|
// we only support V4 (no presign) with auth body
|
||||||
s3Err = isReqAuthenticated(ctx, r, region, serviceS3)
|
s3Err = isReqAuthenticated(ctx, r, region, serviceS3)
|
||||||
}
|
}
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
reqInfo := (&logger.ReqInfo{}).AppendTags("requestHeaders", dumpRequest(r))
|
return cred, owner, s3Err
|
||||||
ctx := logger.SetReqInfo(ctx, reqInfo)
|
|
||||||
logger.LogIf(ctx, errors.New(getAPIError(s3Err).Description), logger.Application)
|
|
||||||
return cred, nil, owner, s3Err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cred, cred.Claims, owner, ErrNone
|
logger.GetReqInfo(ctx).Cred = cred
|
||||||
|
logger.GetReqInfo(ctx).Owner = owner
|
||||||
|
logger.GetReqInfo(ctx).Region = globalSite.Region
|
||||||
|
|
||||||
|
return cred, owner, ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkAdminRequestAuth checks for authentication and authorization for the incoming
|
// checkAdminRequestAuth checks for authentication and authorization for the incoming
|
||||||
// request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests
|
// request. It only accepts V2 and V4 requests. Presigned, JWT and anonymous requests
|
||||||
// are automatically rejected.
|
// are automatically rejected.
|
||||||
func checkAdminRequestAuth(ctx context.Context, r *http.Request, action iampolicy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
|
func checkAdminRequestAuth(ctx context.Context, r *http.Request, action policy.AdminAction, region string) (auth.Credentials, APIErrorCode) {
|
||||||
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, region)
|
cred, owner, s3Err := validateAdminSignature(ctx, r, region)
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
return cred, s3Err
|
return cred, s3Err
|
||||||
}
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.Action(action),
|
Action: policy.Action(action),
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: claims,
|
Claims: cred.Claims,
|
||||||
}) {
|
}) {
|
||||||
// Request is allowed return the appropriate access key.
|
// Request is allowed return the appropriate access key.
|
||||||
return cred, ErrNone
|
return cred, ErrNone
|
||||||
@@ -205,7 +228,7 @@ func getClaimsFromTokenWithSecret(token, secret string) (map[string]interface{},
|
|||||||
// that clients cannot decode the token using the temp
|
// that clients cannot decode the token using the temp
|
||||||
// secret keys and generate an entirely new claim by essentially
|
// secret keys and generate an entirely new claim by essentially
|
||||||
// hijacking the policies. We need to make sure that this is
|
// hijacking the policies. We need to make sure that this is
|
||||||
// based an admin credential such that token cannot be decoded
|
// based on admin credential such that token cannot be decoded
|
||||||
// on the client side and is treated like an opaque value.
|
// on the client side and is treated like an opaque value.
|
||||||
claims, err := auth.ExtractClaims(token, secret)
|
claims, err := auth.ExtractClaims(token, secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -224,7 +247,7 @@ func getClaimsFromTokenWithSecret(token, secret string) (map[string]interface{},
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if a session policy is set. If so, decode it here.
|
// Check if a session policy is set. If so, decode it here.
|
||||||
sp, spok := claims.Lookup(iampolicy.SessionPolicyName)
|
sp, spok := claims.Lookup(policy.SessionPolicyName)
|
||||||
if spok {
|
if spok {
|
||||||
// Looks like subpolicy is set and is a string, if set then its
|
// Looks like subpolicy is set and is a string, if set then its
|
||||||
// base64 encoded, decode it. Decoding fails reject such
|
// base64 encoded, decode it. Decoding fails reject such
|
||||||
@@ -255,7 +278,7 @@ func checkClaimsFromToken(r *http.Request, cred auth.Credentials) (map[string]in
|
|||||||
return nil, ErrNoAccessKey
|
return nil, ErrNoAccessKey
|
||||||
}
|
}
|
||||||
|
|
||||||
if token == "" && cred.IsTemp() {
|
if token == "" && cred.IsTemp() && !cred.IsServiceAccount() {
|
||||||
// Temporary credentials should always have x-amz-security-token
|
// Temporary credentials should always have x-amz-security-token
|
||||||
return nil, ErrInvalidToken
|
return nil, ErrInvalidToken
|
||||||
}
|
}
|
||||||
@@ -265,7 +288,7 @@ func checkClaimsFromToken(r *http.Request, cred auth.Credentials) (map[string]in
|
|||||||
return nil, ErrInvalidToken
|
return nil, ErrInvalidToken
|
||||||
}
|
}
|
||||||
|
|
||||||
if cred.IsTemp() && subtle.ConstantTimeCompare([]byte(token), []byte(cred.SessionToken)) != 1 {
|
if !cred.IsServiceAccount() && cred.IsTemp() && subtle.ConstantTimeCompare([]byte(token), []byte(cred.SessionToken)) != 1 {
|
||||||
// validate token for temporary credentials only.
|
// validate token for temporary credentials only.
|
||||||
return nil, ErrInvalidToken
|
return nil, ErrInvalidToken
|
||||||
}
|
}
|
||||||
@@ -302,6 +325,17 @@ func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Ac
|
|||||||
return s3Err
|
return s3Err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkRequestAuthTypeWithVID is similar to checkRequestAuthType
|
||||||
|
// passes versionID additionally.
|
||||||
|
func checkRequestAuthTypeWithVID(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName, versionID string) (s3Err APIErrorCode) {
|
||||||
|
logger.GetReqInfo(ctx).BucketName = bucketName
|
||||||
|
logger.GetReqInfo(ctx).ObjectName = objectName
|
||||||
|
logger.GetReqInfo(ctx).VersionID = versionID
|
||||||
|
|
||||||
|
_, _, s3Err = checkRequestAuthTypeCredential(ctx, r, action)
|
||||||
|
return s3Err
|
||||||
|
}
|
||||||
|
|
||||||
func authenticateRequest(ctx context.Context, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
func authenticateRequest(ctx context.Context, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
||||||
if logger.GetReqInfo(ctx) == nil {
|
if logger.GetReqInfo(ctx) == nil {
|
||||||
logger.LogIf(ctx, errors.New("unexpected context.Context does not have a logger.ReqInfo"), logger.Minio)
|
logger.LogIf(ctx, errors.New("unexpected context.Context does not have a logger.ReqInfo"), logger.Minio)
|
||||||
@@ -335,6 +369,7 @@ func authenticateRequest(ctx context.Context, r *http.Request, action policy.Act
|
|||||||
|
|
||||||
logger.GetReqInfo(ctx).Cred = cred
|
logger.GetReqInfo(ctx).Cred = cred
|
||||||
logger.GetReqInfo(ctx).Owner = owner
|
logger.GetReqInfo(ctx).Owner = owner
|
||||||
|
logger.GetReqInfo(ctx).Region = globalSite.Region
|
||||||
|
|
||||||
// region is valid only for CreateBucketAction.
|
// region is valid only for CreateBucketAction.
|
||||||
var region string
|
var region string
|
||||||
@@ -373,14 +408,16 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
region := reqInfo.Region
|
region := reqInfo.Region
|
||||||
bucket := reqInfo.BucketName
|
bucket := reqInfo.BucketName
|
||||||
object := reqInfo.ObjectName
|
object := reqInfo.ObjectName
|
||||||
|
versionID := reqInfo.VersionID
|
||||||
|
|
||||||
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
||||||
// Anonymous checks are not meant for ListAllBuckets action
|
// Anonymous checks are not meant for ListAllBuckets action
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
Action: action,
|
Action: action,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, region, "", nil),
|
ConditionValues: getConditionValues(r, region, auth.AnonymousCredentials),
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
}) {
|
}) {
|
||||||
@@ -391,11 +428,12 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
if action == policy.ListBucketVersionsAction {
|
if action == policy.ListBucketVersionsAction {
|
||||||
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
|
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
|
||||||
// verify as a fallback.
|
// verify as a fallback.
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
Action: policy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, region, "", nil),
|
ConditionValues: getConditionValues(r, region, auth.AnonymousCredentials),
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
}) {
|
}) {
|
||||||
@@ -406,13 +444,27 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
|
|
||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
if action == policy.DeleteObjectAction && versionID != "" {
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if !globalIAMSys.IsAllowed(policy.Args{
|
||||||
|
AccountName: cred.AccessKey,
|
||||||
|
Groups: cred.Groups,
|
||||||
|
Action: policy.Action(policy.DeleteObjectVersionAction),
|
||||||
|
BucketName: bucket,
|
||||||
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
|
ObjectName: object,
|
||||||
|
IsOwner: owner,
|
||||||
|
Claims: cred.Claims,
|
||||||
|
DenyOnly: true,
|
||||||
|
}) { // Request is not allowed if Deny action on DeleteObjectVersionAction
|
||||||
|
return ErrAccessDenied
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.Action(action),
|
Action: action,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -424,12 +476,12 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
|||||||
if action == policy.ListBucketVersionsAction {
|
if action == policy.ListBucketVersionsAction {
|
||||||
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
|
// In AWS S3 s3:ListBucket permission is same as s3:ListBucketVersions permission
|
||||||
// verify as a fallback.
|
// verify as a fallback.
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.ListBucketAction,
|
Action: policy.ListBucketAction,
|
||||||
BucketName: bucket,
|
BucketName: bucket,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
ObjectName: object,
|
ObjectName: object,
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
@@ -515,7 +567,7 @@ func isReqAuthenticated(ctx context.Context, r *http.Request, region string, sty
|
|||||||
|
|
||||||
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
|
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
|
||||||
// The verification happens implicit during reading.
|
// The verification happens implicit during reading.
|
||||||
reader, err := hash.NewReader(r.Body, -1, clientETag.String(), hex.EncodeToString(contentSHA256), -1)
|
reader, err := hash.NewReader(ctx, r.Body, -1, clientETag.String(), hex.EncodeToString(contentSHA256), -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toAPIErrorCode(ctx, err)
|
return toAPIErrorCode(ctx, err)
|
||||||
}
|
}
|
||||||
@@ -525,13 +577,15 @@ func isReqAuthenticated(ctx context.Context, r *http.Request, region string, sty
|
|||||||
|
|
||||||
// List of all support S3 auth types.
|
// List of all support S3 auth types.
|
||||||
var supportedS3AuthTypes = map[authType]struct{}{
|
var supportedS3AuthTypes = map[authType]struct{}{
|
||||||
authTypeAnonymous: {},
|
authTypeAnonymous: {},
|
||||||
authTypePresigned: {},
|
authTypePresigned: {},
|
||||||
authTypePresignedV2: {},
|
authTypePresignedV2: {},
|
||||||
authTypeSigned: {},
|
authTypeSigned: {},
|
||||||
authTypeSignedV2: {},
|
authTypeSignedV2: {},
|
||||||
authTypePostPolicy: {},
|
authTypePostPolicy: {},
|
||||||
authTypeStreamingSigned: {},
|
authTypeStreamingSigned: {},
|
||||||
|
authTypeStreamingSignedTrailer: {},
|
||||||
|
authTypeStreamingUnsignedTrailer: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate if the authType is valid and supported.
|
// Validate if the authType is valid and supported.
|
||||||
@@ -540,25 +594,27 @@ func isSupportedS3AuthType(aType authType) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// setAuthHandler to validate authorization header for the incoming request.
|
// setAuthMiddleware to validate authorization header for the incoming request.
|
||||||
func setAuthHandler(h http.Handler) http.Handler {
|
func setAuthMiddleware(h http.Handler) http.Handler {
|
||||||
// handler for validating incoming authorization headers.
|
// handler for validating incoming authorization headers.
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
tc, ok := r.Context().Value(contextTraceReqKey).(*traceCtxt)
|
tc, ok := r.Context().Value(mcontext.ContextTraceKey).(*mcontext.TraceCtxt)
|
||||||
|
|
||||||
aType := getRequestAuthType(r)
|
aType := getRequestAuthType(r)
|
||||||
if aType == authTypeSigned || aType == authTypeSignedV2 || aType == authTypeStreamingSigned {
|
switch aType {
|
||||||
|
case authTypeSigned, authTypeSignedV2, authTypeStreamingSigned, authTypeStreamingSignedTrailer:
|
||||||
// Verify if date headers are set, if not reject the request
|
// Verify if date headers are set, if not reject the request
|
||||||
amzDate, errCode := parseAmzDateHeader(r)
|
amzDate, errCode := parseAmzDateHeader(r)
|
||||||
if errCode != ErrNone {
|
if errCode != ErrNone {
|
||||||
if ok {
|
if ok {
|
||||||
tc.funcName = "handler.Auth"
|
tc.FuncName = "handler.Auth"
|
||||||
tc.responseRecorder.LogErrBody = true
|
tc.ResponseRecorder.LogErrBody = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// All our internal APIs are sensitive towards Date
|
// All our internal APIs are sensitive towards Date
|
||||||
// header, for all requests where Date header is not
|
// header, for all requests where Date header is not
|
||||||
// present we will reject such clients.
|
// present we will reject such clients.
|
||||||
|
defer logger.AuditLog(r.Context(), w, r, mustGetClaimsFromToken(r))
|
||||||
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(errCode), r.URL)
|
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(errCode), r.URL)
|
||||||
atomic.AddUint64(&globalHTTPStats.rejectedRequestsTime, 1)
|
atomic.AddUint64(&globalHTTPStats.rejectedRequestsTime, 1)
|
||||||
return
|
return
|
||||||
@@ -568,25 +624,33 @@ func setAuthHandler(h http.Handler) http.Handler {
|
|||||||
curTime := UTCNow()
|
curTime := UTCNow()
|
||||||
if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime {
|
if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime {
|
||||||
if ok {
|
if ok {
|
||||||
tc.funcName = "handler.Auth"
|
tc.FuncName = "handler.Auth"
|
||||||
tc.responseRecorder.LogErrBody = true
|
tc.ResponseRecorder.LogErrBody = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer logger.AuditLog(r.Context(), w, r, mustGetClaimsFromToken(r))
|
||||||
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrRequestTimeTooSkewed), r.URL)
|
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrRequestTimeTooSkewed), r.URL)
|
||||||
atomic.AddUint64(&globalHTTPStats.rejectedRequestsTime, 1)
|
atomic.AddUint64(&globalHTTPStats.rejectedRequestsTime, 1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if isSupportedS3AuthType(aType) || aType == authTypeJWT || aType == authTypeSTS {
|
|
||||||
h.ServeHTTP(w, r)
|
h.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
|
case authTypeJWT, authTypeSTS:
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
if isSupportedS3AuthType(aType) {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
tc.funcName = "handler.Auth"
|
tc.FuncName = "handler.Auth"
|
||||||
tc.responseRecorder.LogErrBody = true
|
tc.ResponseRecorder.LogErrBody = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer logger.AuditLog(r.Context(), w, r, mustGetClaimsFromToken(r))
|
||||||
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrSignatureVersionNotSupported), r.URL)
|
writeErrorResponse(r.Context(), w, errorCodes.ToAPIErr(ErrSignatureVersionNotSupported), r.URL)
|
||||||
atomic.AddUint64(&globalHTTPStats.rejectedRequestsAuth, 1)
|
atomic.AddUint64(&globalHTTPStats.rejectedRequestsAuth, 1)
|
||||||
})
|
})
|
||||||
@@ -624,17 +688,17 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
conditions := getConditionValues(r, "", cred.AccessKey, cred.Claims)
|
conditions := getConditionValues(r, "", cred)
|
||||||
conditions["object-lock-mode"] = []string{string(retMode)}
|
conditions["object-lock-mode"] = []string{string(retMode)}
|
||||||
conditions["object-lock-retain-until-date"] = []string{retDate.Format(time.RFC3339)}
|
conditions["object-lock-retain-until-date"] = []string{retDate.UTC().Format(time.RFC3339)}
|
||||||
if retDays > 0 {
|
if retDays > 0 {
|
||||||
conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
|
conditions["object-lock-remaining-retention-days"] = []string{strconv.Itoa(retDays)}
|
||||||
}
|
}
|
||||||
if retMode == objectlock.RetGovernance && byPassSet {
|
if retMode == objectlock.RetGovernance && byPassSet {
|
||||||
byPassSet = globalIAMSys.IsAllowed(iampolicy.Args{
|
byPassSet = globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.BypassGovernanceRetentionAction,
|
Action: policy.BypassGovernanceRetentionAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
ConditionValues: conditions,
|
ConditionValues: conditions,
|
||||||
@@ -642,10 +706,10 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: iampolicy.PutObjectRetentionAction,
|
Action: policy.PutObjectRetentionAction,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: conditions,
|
ConditionValues: conditions,
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
@@ -663,7 +727,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
|
|||||||
// isPutActionAllowed - check if PUT operation is allowed on the resource, this
|
// isPutActionAllowed - check if PUT operation is allowed on the resource, this
|
||||||
// call verifies bucket policies and IAM policies, supports multi user
|
// call verifies bucket policies and IAM policies, supports multi user
|
||||||
// checks etc.
|
// checks etc.
|
||||||
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action iampolicy.Action) (s3Err APIErrorCode) {
|
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
||||||
var cred auth.Credentials
|
var cred auth.Credentials
|
||||||
var owner bool
|
var owner bool
|
||||||
region := globalSite.Region
|
region := globalSite.Region
|
||||||
@@ -672,7 +736,7 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
|
|||||||
return ErrSignatureVersionNotSupported
|
return ErrSignatureVersionNotSupported
|
||||||
case authTypeSignedV2, authTypePresignedV2:
|
case authTypeSignedV2, authTypePresignedV2:
|
||||||
cred, owner, s3Err = getReqAccessKeyV2(r)
|
cred, owner, s3Err = getReqAccessKeyV2(r)
|
||||||
case authTypeStreamingSigned, authTypePresigned, authTypeSigned:
|
case authTypeStreamingSigned, authTypePresigned, authTypeSigned, authTypeStreamingSignedTrailer, authTypeStreamingUnsignedTrailer:
|
||||||
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
||||||
}
|
}
|
||||||
if s3Err != ErrNone {
|
if s3Err != ErrNone {
|
||||||
@@ -686,19 +750,19 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
|
|||||||
// Do not check for PutObjectRetentionAction permission,
|
// Do not check for PutObjectRetentionAction permission,
|
||||||
// if mode and retain until date are not set.
|
// if mode and retain until date are not set.
|
||||||
// Can happen when bucket has default lock config set
|
// Can happen when bucket has default lock config set
|
||||||
if action == iampolicy.PutObjectRetentionAction &&
|
if action == policy.PutObjectRetentionAction &&
|
||||||
r.Header.Get(xhttp.AmzObjectLockMode) == "" &&
|
r.Header.Get(xhttp.AmzObjectLockMode) == "" &&
|
||||||
r.Header.Get(xhttp.AmzObjectLockRetainUntilDate) == "" {
|
r.Header.Get(xhttp.AmzObjectLockRetainUntilDate) == "" {
|
||||||
return ErrNone
|
return ErrNone
|
||||||
}
|
}
|
||||||
|
|
||||||
if cred.AccessKey == "" {
|
if cred.AccessKey == "" {
|
||||||
if globalPolicySys.IsAllowed(policy.Args{
|
if globalPolicySys.IsAllowed(policy.BucketPolicyArgs{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: policy.Action(action),
|
Action: action,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: getConditionValues(r, "", "", nil),
|
ConditionValues: getConditionValues(r, "", auth.AnonymousCredentials),
|
||||||
IsOwner: false,
|
IsOwner: false,
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
}) {
|
}) {
|
||||||
@@ -707,12 +771,12 @@ func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectN
|
|||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(policy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
Groups: cred.Groups,
|
Groups: cred.Groups,
|
||||||
Action: action,
|
Action: action,
|
||||||
BucketName: bucketName,
|
BucketName: bucketName,
|
||||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
ConditionValues: getConditionValues(r, "", cred),
|
||||||
ObjectName: objectName,
|
ObjectName: objectName,
|
||||||
IsOwner: owner,
|
IsOwner: owner,
|
||||||
Claims: cred.Claims,
|
Claims: cred.Claims,
|
||||||
|
|||||||
+18
-16
@@ -28,7 +28,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
"github.com/minio/pkg/v2/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nullReader struct{}
|
type nullReader struct{}
|
||||||
@@ -359,7 +359,10 @@ func mustNewSignedBadMD5Request(method string, urlStr string, contentLength int6
|
|||||||
|
|
||||||
// Tests is requested authenticated function, tests replies for s3 errors.
|
// Tests is requested authenticated function, tests replies for s3 errors.
|
||||||
func TestIsReqAuthenticated(t *testing.T) {
|
func TestIsReqAuthenticated(t *testing.T) {
|
||||||
objLayer, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(GlobalContext)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -368,15 +371,10 @@ func TestIsReqAuthenticated(t *testing.T) {
|
|||||||
t.Fatalf("unable initialize config file, %s", err)
|
t.Fatalf("unable initialize config file, %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
initConfigSubsystem(ctx, objLayer)
|
initConfigSubsystem(ctx, objLayer)
|
||||||
|
|
||||||
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
|
||||||
|
|
||||||
creds, err := auth.CreateCredentials("myuser", "mypassword")
|
creds, err := auth.CreateCredentials("myuser", "mypassword")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable create credential, %s", err)
|
t.Fatalf("unable create credential, %s", err)
|
||||||
@@ -384,6 +382,8 @@ func TestIsReqAuthenticated(t *testing.T) {
|
|||||||
|
|
||||||
globalActiveCred = creds
|
globalActiveCred = creds
|
||||||
|
|
||||||
|
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
||||||
|
|
||||||
// List of test cases for validating http request authentication.
|
// List of test cases for validating http request authentication.
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
req *http.Request
|
req *http.Request
|
||||||
@@ -413,7 +413,10 @@ func TestIsReqAuthenticated(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckAdminRequestAuthType(t *testing.T) {
|
func TestCheckAdminRequestAuthType(t *testing.T) {
|
||||||
objLayer, fsDir, err := prepareFS()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -439,9 +442,8 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
|
|||||||
{Request: mustNewPresignedV2Request(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
{Request: mustNewPresignedV2Request(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||||
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, iampolicy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode {
|
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, policy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode {
|
||||||
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -451,7 +453,7 @@ func TestValidateAdminSignature(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
objLayer, fsDir, err := prepareFS()
|
objLayer, fsDir, err := prepareFS(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -461,18 +463,18 @@ func TestValidateAdminSignature(t *testing.T) {
|
|||||||
t.Fatalf("unable initialize config file, %s", err)
|
t.Fatalf("unable initialize config file, %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
initAllSubsystems()
|
initAllSubsystems(ctx)
|
||||||
|
|
||||||
initConfigSubsystem(ctx, objLayer)
|
initConfigSubsystem(ctx, objLayer)
|
||||||
|
|
||||||
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
|
||||||
|
|
||||||
creds, err := auth.CreateCredentials("admin", "mypassword")
|
creds, err := auth.CreateCredentials("admin", "mypassword")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable create credential, %s", err)
|
t.Fatalf("unable create credential, %s", err)
|
||||||
}
|
}
|
||||||
globalActiveCred = creds
|
globalActiveCred = creds
|
||||||
|
|
||||||
|
globalIAMSys.Init(ctx, objLayer, globalEtcdClient, 2*time.Second)
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
AccessKey string
|
AccessKey string
|
||||||
SecretKey string
|
SecretKey string
|
||||||
@@ -491,7 +493,7 @@ func TestValidateAdminSignature(t *testing.T) {
|
|||||||
if err := signRequestV4(req, testCase.AccessKey, testCase.SecretKey); err != nil {
|
if err := signRequestV4(req, testCase.AccessKey, testCase.SecretKey); err != nil {
|
||||||
t.Fatalf("Unable to inititalized new signed http request %s", err)
|
t.Fatalf("Unable to inititalized new signed http request %s", err)
|
||||||
}
|
}
|
||||||
_, _, _, s3Error := validateAdminSignature(ctx, req, globalMinioDefaultRegion)
|
_, _, s3Error := validateAdminSignature(ctx, req, globalMinioDefaultRegion)
|
||||||
if s3Error != testCase.ErrCode {
|
if s3Error != testCase.ErrCode {
|
||||||
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i+1, testCase.ErrCode, s3Error)
|
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i+1, testCase.ErrCode, s3Error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ func _() {
|
|||||||
_ = x[authTypeSignedV2-7]
|
_ = x[authTypeSignedV2-7]
|
||||||
_ = x[authTypeJWT-8]
|
_ = x[authTypeJWT-8]
|
||||||
_ = x[authTypeSTS-9]
|
_ = x[authTypeSTS-9]
|
||||||
|
_ = x[authTypeStreamingSignedTrailer-10]
|
||||||
|
_ = x[authTypeStreamingUnsignedTrailer-11]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _authType_name = "UnknownAnonymousPresignedPresignedV2PostPolicyStreamingSignedSignedSignedV2JWTSTS"
|
const _authType_name = "UnknownAnonymousPresignedPresignedV2PostPolicyStreamingSignedSignedSignedV2JWTSTSStreamingSignedTrailerStreamingUnsignedTrailer"
|
||||||
|
|
||||||
var _authType_index = [...]uint8{0, 7, 16, 25, 36, 46, 61, 67, 75, 78, 81}
|
var _authType_index = [...]uint8{0, 7, 16, 25, 36, 46, 61, 67, 75, 78, 81, 103, 127}
|
||||||
|
|
||||||
func (i authType) String() string {
|
func (i authType) String() string {
|
||||||
if i < 0 || i >= authType(len(_authType_index)-1) {
|
if i < 0 || i >= authType(len(_authType_index)-1) {
|
||||||
|
|||||||
+58
-12
@@ -19,10 +19,14 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio/internal/pubsub"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/pkg/v2/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
// healTask represents what to heal along with options
|
// healTask represents what to heal along with options
|
||||||
@@ -54,16 +58,46 @@ type healRoutine struct {
|
|||||||
func activeListeners() int {
|
func activeListeners() int {
|
||||||
// Bucket notification and http trace are not costly, it is okay to ignore them
|
// Bucket notification and http trace are not costly, it is okay to ignore them
|
||||||
// while counting the number of concurrent connections
|
// while counting the number of concurrent connections
|
||||||
return int(globalHTTPListen.NumSubscribers(pubsub.MaskAll)) + int(globalTrace.NumSubscribers(pubsub.MaskAll))
|
return int(globalHTTPListen.Subscribers()) + int(globalTrace.Subscribers())
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForLowIO(maxIO int, maxWait time.Duration, currentIO func() int) {
|
||||||
|
// No need to wait run at full speed.
|
||||||
|
if maxIO <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const waitTick = 100 * time.Millisecond
|
||||||
|
|
||||||
|
tmpMaxWait := maxWait
|
||||||
|
|
||||||
|
for currentIO() >= maxIO {
|
||||||
|
if tmpMaxWait > 0 {
|
||||||
|
if tmpMaxWait < waitTick {
|
||||||
|
time.Sleep(tmpMaxWait)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(waitTick)
|
||||||
|
tmpMaxWait -= waitTick
|
||||||
|
}
|
||||||
|
if tmpMaxWait <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func currentHTTPIO() int {
|
||||||
|
httpServer := newHTTPServerFn()
|
||||||
|
if httpServer == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpServer.GetRequestCount() - activeListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForLowHTTPReq() {
|
func waitForLowHTTPReq() {
|
||||||
var currentIO func() int
|
maxIO, maxWait, _ := globalHealConfig.Clone()
|
||||||
if httpServer := newHTTPServerFn(); httpServer != nil {
|
waitForLowIO(maxIO, maxWait, currentHTTPIO)
|
||||||
currentIO = httpServer.GetRequestCount
|
|
||||||
}
|
|
||||||
|
|
||||||
globalHealConfig.Wait(currentIO, activeListeners)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
|
func initBackgroundHealing(ctx context.Context, objAPI ObjectLayer) {
|
||||||
@@ -89,8 +123,7 @@ func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
var err error
|
var err error
|
||||||
switch task.bucket {
|
switch task.bucket {
|
||||||
case nopHeal:
|
case nopHeal:
|
||||||
task.respCh <- healResult{err: errSkipFile}
|
err = errSkipFile
|
||||||
continue
|
|
||||||
case SlashSeparator:
|
case SlashSeparator:
|
||||||
res, err = healDiskFormat(ctx, objAPI, task.opts)
|
res, err = healDiskFormat(ctx, objAPI, task.opts)
|
||||||
default:
|
default:
|
||||||
@@ -101,7 +134,10 @@ func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task.respCh <- healResult{result: res, err: err}
|
if task.respCh != nil {
|
||||||
|
task.respCh <- healResult{result: res, err: err}
|
||||||
|
}
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -110,9 +146,19 @@ func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
|
|
||||||
func newHealRoutine() *healRoutine {
|
func newHealRoutine() *healRoutine {
|
||||||
workers := runtime.GOMAXPROCS(0) / 2
|
workers := runtime.GOMAXPROCS(0) / 2
|
||||||
|
|
||||||
|
if envHealWorkers := env.Get("_MINIO_HEAL_WORKERS", ""); envHealWorkers != "" {
|
||||||
|
if numHealers, err := strconv.Atoi(envHealWorkers); err != nil {
|
||||||
|
logger.LogIf(context.Background(), fmt.Errorf("invalid _MINIO_HEAL_WORKERS value: %w", err))
|
||||||
|
} else {
|
||||||
|
workers = numHealers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if workers == 0 {
|
if workers == 0 {
|
||||||
workers = 4
|
workers = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
return &healRoutine{
|
return &healRoutine{
|
||||||
tasks: make(chan healTask),
|
tasks: make(chan healTask),
|
||||||
workers: workers,
|
workers: workers,
|
||||||
|
|||||||
@@ -26,12 +26,15 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go/v3"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/pkg/v2/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -43,7 +46,8 @@ const (
|
|||||||
|
|
||||||
// healingTracker is used to persist healing information during a heal.
|
// healingTracker is used to persist healing information during a heal.
|
||||||
type healingTracker struct {
|
type healingTracker struct {
|
||||||
disk StorageAPI `msg:"-"`
|
disk StorageAPI `msg:"-"`
|
||||||
|
mu *sync.RWMutex `msg:"-"`
|
||||||
|
|
||||||
ID string
|
ID string
|
||||||
PoolIndex int
|
PoolIndex int
|
||||||
@@ -79,6 +83,10 @@ type healingTracker struct {
|
|||||||
|
|
||||||
// Filled during heal.
|
// Filled during heal.
|
||||||
HealedBuckets []string
|
HealedBuckets []string
|
||||||
|
|
||||||
|
// ID of the current healing operation
|
||||||
|
HealID string
|
||||||
|
|
||||||
// Add future tracking capabilities
|
// Add future tracking capabilities
|
||||||
// Be sure that they are included in toHealingDisk
|
// Be sure that they are included in toHealingDisk
|
||||||
}
|
}
|
||||||
@@ -108,21 +116,76 @@ func loadHealingTracker(ctx context.Context, disk StorageAPI) (*healingTracker,
|
|||||||
}
|
}
|
||||||
h.disk = disk
|
h.disk = disk
|
||||||
h.ID = diskID
|
h.ID = diskID
|
||||||
|
h.mu = &sync.RWMutex{}
|
||||||
return &h, nil
|
return &h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newHealingTracker will create a new healing tracker for the disk.
|
// newHealingTracker will create a new healing tracker for the disk.
|
||||||
func newHealingTracker(disk StorageAPI) *healingTracker {
|
func newHealingTracker() *healingTracker {
|
||||||
diskID, _ := disk.GetDiskID()
|
return &healingTracker{
|
||||||
h := healingTracker{
|
mu: &sync.RWMutex{},
|
||||||
disk: disk,
|
|
||||||
ID: diskID,
|
|
||||||
Path: disk.String(),
|
|
||||||
Endpoint: disk.Endpoint().String(),
|
|
||||||
Started: time.Now().UTC(),
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initHealingTracker(disk StorageAPI, healID string) *healingTracker {
|
||||||
|
h := newHealingTracker()
|
||||||
|
diskID, _ := disk.GetDiskID()
|
||||||
|
h.disk = disk
|
||||||
|
h.ID = diskID
|
||||||
|
h.HealID = healID
|
||||||
|
h.Path = disk.String()
|
||||||
|
h.Endpoint = disk.Endpoint().String()
|
||||||
|
h.Started = time.Now().UTC()
|
||||||
h.PoolIndex, h.SetIndex, h.DiskIndex = disk.GetDiskLoc()
|
h.PoolIndex, h.SetIndex, h.DiskIndex = disk.GetDiskLoc()
|
||||||
return &h
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h healingTracker) getLastUpdate() time.Time {
|
||||||
|
h.mu.RLock()
|
||||||
|
defer h.mu.RUnlock()
|
||||||
|
|
||||||
|
return h.LastUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h healingTracker) getBucket() string {
|
||||||
|
h.mu.RLock()
|
||||||
|
defer h.mu.RUnlock()
|
||||||
|
|
||||||
|
return h.Bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *healingTracker) setBucket(bucket string) {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
|
h.Bucket = bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h healingTracker) getObject() string {
|
||||||
|
h.mu.RLock()
|
||||||
|
defer h.mu.RUnlock()
|
||||||
|
|
||||||
|
return h.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *healingTracker) setObject(object string) {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
|
h.Object = object
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *healingTracker) updateProgress(success bool, bytes uint64) {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
|
if success {
|
||||||
|
h.ItemsHealed++
|
||||||
|
h.BytesDone += bytes
|
||||||
|
} else {
|
||||||
|
h.ItemsFailed++
|
||||||
|
h.BytesFailed += bytes
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update will update the tracker on the disk.
|
// update will update the tracker on the disk.
|
||||||
@@ -131,15 +194,18 @@ func (h *healingTracker) update(ctx context.Context) error {
|
|||||||
if h.disk.Healing() == nil {
|
if h.disk.Healing() == nil {
|
||||||
return fmt.Errorf("healingTracker: drive %q is not marked as healing", h.ID)
|
return fmt.Errorf("healingTracker: drive %q is not marked as healing", h.ID)
|
||||||
}
|
}
|
||||||
|
h.mu.Lock()
|
||||||
if h.ID == "" || h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 {
|
if h.ID == "" || h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 {
|
||||||
h.ID, _ = h.disk.GetDiskID()
|
h.ID, _ = h.disk.GetDiskID()
|
||||||
h.PoolIndex, h.SetIndex, h.DiskIndex = h.disk.GetDiskLoc()
|
h.PoolIndex, h.SetIndex, h.DiskIndex = h.disk.GetDiskLoc()
|
||||||
}
|
}
|
||||||
|
h.mu.Unlock()
|
||||||
return h.save(ctx)
|
return h.save(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// save will unconditionally save the tracker and will be created if not existing.
|
// save will unconditionally save the tracker and will be created if not existing.
|
||||||
func (h *healingTracker) save(ctx context.Context) error {
|
func (h *healingTracker) save(ctx context.Context) error {
|
||||||
|
h.mu.Lock()
|
||||||
if h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 {
|
if h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 {
|
||||||
// Attempt to get location.
|
// Attempt to get location.
|
||||||
if api := newObjectLayerFn(); api != nil {
|
if api := newObjectLayerFn(); api != nil {
|
||||||
@@ -150,6 +216,7 @@ func (h *healingTracker) save(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
h.LastUpdate = time.Now().UTC()
|
h.LastUpdate = time.Now().UTC()
|
||||||
htrackerBytes, err := h.MarshalMsg(nil)
|
htrackerBytes, err := h.MarshalMsg(nil)
|
||||||
|
h.mu.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -171,6 +238,8 @@ func (h *healingTracker) delete(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *healingTracker) isHealed(bucket string) bool {
|
func (h *healingTracker) isHealed(bucket string) bool {
|
||||||
|
h.mu.RLock()
|
||||||
|
defer h.mu.RUnlock()
|
||||||
for _, v := range h.HealedBuckets {
|
for _, v := range h.HealedBuckets {
|
||||||
if v == bucket {
|
if v == bucket {
|
||||||
return true
|
return true
|
||||||
@@ -181,6 +250,9 @@ func (h *healingTracker) isHealed(bucket string) bool {
|
|||||||
|
|
||||||
// resume will reset progress to the numbers at the start of the bucket.
|
// resume will reset progress to the numbers at the start of the bucket.
|
||||||
func (h *healingTracker) resume() {
|
func (h *healingTracker) resume() {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
h.ItemsHealed = h.ResumeItemsHealed
|
h.ItemsHealed = h.ResumeItemsHealed
|
||||||
h.ItemsFailed = h.ResumeItemsFailed
|
h.ItemsFailed = h.ResumeItemsFailed
|
||||||
h.BytesDone = h.ResumeBytesDone
|
h.BytesDone = h.ResumeBytesDone
|
||||||
@@ -190,6 +262,9 @@ func (h *healingTracker) resume() {
|
|||||||
// bucketDone should be called when a bucket is done healing.
|
// bucketDone should be called when a bucket is done healing.
|
||||||
// Adds the bucket to the list of healed buckets and updates resume numbers.
|
// Adds the bucket to the list of healed buckets and updates resume numbers.
|
||||||
func (h *healingTracker) bucketDone(bucket string) {
|
func (h *healingTracker) bucketDone(bucket string) {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
h.ResumeItemsHealed = h.ItemsHealed
|
h.ResumeItemsHealed = h.ItemsHealed
|
||||||
h.ResumeItemsFailed = h.ItemsFailed
|
h.ResumeItemsFailed = h.ItemsFailed
|
||||||
h.ResumeBytesDone = h.BytesDone
|
h.ResumeBytesDone = h.BytesDone
|
||||||
@@ -206,6 +281,9 @@ func (h *healingTracker) bucketDone(bucket string) {
|
|||||||
// setQueuedBuckets will add buckets, but exclude any that is already in h.HealedBuckets.
|
// setQueuedBuckets will add buckets, but exclude any that is already in h.HealedBuckets.
|
||||||
// Order is preserved.
|
// Order is preserved.
|
||||||
func (h *healingTracker) setQueuedBuckets(buckets []BucketInfo) {
|
func (h *healingTracker) setQueuedBuckets(buckets []BucketInfo) {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
s := set.CreateStringSet(h.HealedBuckets...)
|
s := set.CreateStringSet(h.HealedBuckets...)
|
||||||
h.QueuedBuckets = make([]string, 0, len(buckets))
|
h.QueuedBuckets = make([]string, 0, len(buckets))
|
||||||
for _, b := range buckets {
|
for _, b := range buckets {
|
||||||
@@ -216,17 +294,25 @@ func (h *healingTracker) setQueuedBuckets(buckets []BucketInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *healingTracker) printTo(writer io.Writer) {
|
func (h *healingTracker) printTo(writer io.Writer) {
|
||||||
|
h.mu.RLock()
|
||||||
|
defer h.mu.RUnlock()
|
||||||
|
|
||||||
b, err := json.MarshalIndent(h, "", " ")
|
b, err := json.MarshalIndent(h, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writer.Write([]byte(err.Error()))
|
writer.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
writer.Write(b)
|
writer.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// toHealingDisk converts the information to madmin.HealingDisk
|
// toHealingDisk converts the information to madmin.HealingDisk
|
||||||
func (h *healingTracker) toHealingDisk() madmin.HealingDisk {
|
func (h *healingTracker) toHealingDisk() madmin.HealingDisk {
|
||||||
|
h.mu.RLock()
|
||||||
|
defer h.mu.RUnlock()
|
||||||
|
|
||||||
return madmin.HealingDisk{
|
return madmin.HealingDisk{
|
||||||
ID: h.ID,
|
ID: h.ID,
|
||||||
|
HealID: h.HealID,
|
||||||
Endpoint: h.Endpoint,
|
Endpoint: h.Endpoint,
|
||||||
PoolIndex: h.PoolIndex,
|
PoolIndex: h.PoolIndex,
|
||||||
SetIndex: h.SetIndex,
|
SetIndex: h.SetIndex,
|
||||||
@@ -261,10 +347,15 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
|
|
||||||
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
||||||
|
|
||||||
go monitorLocalDisksAndHeal(ctx, z)
|
if env.Get("_MINIO_AUTO_DRIVE_HEALING", config.EnableOn) == config.EnableOn || env.Get("_MINIO_AUTO_DISK_HEALING", config.EnableOn) == config.EnableOn {
|
||||||
|
go monitorLocalDisksAndHeal(ctx, z)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
||||||
|
globalLocalDrivesMu.RLock()
|
||||||
|
globalLocalDrives := globalLocalDrives
|
||||||
|
globalLocalDrivesMu.RUnlock()
|
||||||
for _, disk := range globalLocalDrives {
|
for _, disk := range globalLocalDrives {
|
||||||
_, err := disk.GetDiskID()
|
_, err := disk.GetDiskID()
|
||||||
if errors.Is(err, errUnformattedDisk) {
|
if errors.Is(err, errUnformattedDisk) {
|
||||||
@@ -286,16 +377,14 @@ func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
|||||||
var newDiskHealingTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
var newDiskHealingTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
||||||
|
|
||||||
func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint) error {
|
func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint) error {
|
||||||
logger.Info(fmt.Sprintf("Proceeding to heal '%s' - 'mc admin heal alias/ --verbose' to check the status.", endpoint))
|
|
||||||
|
|
||||||
disk, format, err := connectEndpoint(endpoint)
|
disk, format, err := connectEndpoint(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error: %w, %s", err, endpoint)
|
return fmt.Errorf("Error: %w, %s", err, endpoint)
|
||||||
}
|
}
|
||||||
|
defer disk.Close()
|
||||||
poolIdx := globalEndpoints.GetLocalPoolIdx(disk.Endpoint())
|
poolIdx := globalEndpoints.GetLocalPoolIdx(disk.Endpoint())
|
||||||
if poolIdx < 0 {
|
if poolIdx < 0 {
|
||||||
return fmt.Errorf("unexpected pool index (%d) found in %s", poolIdx, disk.Endpoint())
|
return fmt.Errorf("unexpected pool index (%d) found for %s", poolIdx, disk.Endpoint())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the set index where the current endpoint belongs
|
// Calculate the set index where the current endpoint belongs
|
||||||
@@ -306,20 +395,36 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if setIdx < 0 {
|
if setIdx < 0 {
|
||||||
return fmt.Errorf("unexpected set index (%d) found in %s", setIdx, disk.Endpoint())
|
return fmt.Errorf("unexpected set index (%d) found for %s", setIdx, disk.Endpoint())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent parallel erasure set healing
|
// Prevent parallel erasure set healing
|
||||||
locker := z.NewNSLock(minioMetaBucket, fmt.Sprintf("new-drive-healing/%s/%d/%d", endpoint, poolIdx, setIdx))
|
locker := z.NewNSLock(minioMetaBucket, fmt.Sprintf("new-drive-healing/%d/%d", poolIdx, setIdx))
|
||||||
lkctx, err := locker.GetLock(ctx, newDiskHealingTimeout)
|
lkctx, err := locker.GetLock(ctx, newDiskHealingTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("Healing of drive '%v' on %s pool, belonging to %s erasure set already in progress: %w",
|
||||||
|
disk, humanize.Ordinal(poolIdx+1), humanize.Ordinal(setIdx+1), err)
|
||||||
}
|
}
|
||||||
ctx = lkctx.Context()
|
ctx = lkctx.Context()
|
||||||
defer locker.Unlock(lkctx.Cancel)
|
defer locker.Unlock(lkctx)
|
||||||
|
|
||||||
|
// Load healing tracker in this disk
|
||||||
|
tracker, err := loadHealingTracker(ctx, disk)
|
||||||
|
if err != nil {
|
||||||
|
// A healing tracker may be deleted if another disk in the
|
||||||
|
// same erasure set with same healing-id successfully finished
|
||||||
|
// healing.
|
||||||
|
if errors.Is(err, errFileNotFound) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to load healing tracker on '%s': %w, re-initializing..", disk, err))
|
||||||
|
tracker = initHealingTracker(disk, mustGetUUID())
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info(fmt.Sprintf("Healing drive '%s' - 'mc admin heal alias/ --verbose' to check the current status.", endpoint))
|
||||||
|
|
||||||
buckets, _ := z.ListBuckets(ctx, BucketOptions{})
|
buckets, _ := z.ListBuckets(ctx, BucketOptions{})
|
||||||
// Buckets data are dispersed in multiple zones/sets, make
|
// Buckets data are dispersed in multiple pools/sets, make
|
||||||
// sure to heal all bucket metadata configuration.
|
// sure to heal all bucket metadata configuration.
|
||||||
buckets = append(buckets, BucketInfo{
|
buckets = append(buckets, BucketInfo{
|
||||||
Name: pathJoin(minioMetaBucket, minioConfigPrefix),
|
Name: pathJoin(minioMetaBucket, minioConfigPrefix),
|
||||||
@@ -337,16 +442,7 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
|||||||
})
|
})
|
||||||
|
|
||||||
if serverDebugLog {
|
if serverDebugLog {
|
||||||
logger.Info("Healing drive '%v' on %s pool", disk, humanize.Ordinal(poolIdx+1))
|
logger.Info("Healing drive '%v' on %s pool, belonging to %s erasure set", disk, humanize.Ordinal(poolIdx+1), humanize.Ordinal(setIdx+1))
|
||||||
}
|
|
||||||
|
|
||||||
// Load healing tracker in this disk
|
|
||||||
tracker, err := loadHealingTracker(ctx, disk)
|
|
||||||
if err != nil {
|
|
||||||
// So someone changed the drives underneath, healing tracker missing.
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Healing tracker missing on '%s', drive was swapped again on %s pool: %w",
|
|
||||||
disk, humanize.Ordinal(poolIdx+1), err))
|
|
||||||
tracker = newHealingTracker(disk)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load bucket totals
|
// Load bucket totals
|
||||||
@@ -369,9 +465,13 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
|||||||
}
|
}
|
||||||
|
|
||||||
if tracker.ItemsFailed > 0 {
|
if tracker.ItemsFailed > 0 {
|
||||||
logger.Info("Healing drive '%s' failed (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
|
logger.Info("Healing of drive '%s' failed (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
|
||||||
} else {
|
} else {
|
||||||
logger.Info("Healing drive '%s' complete (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
|
logger.Info("Healing of drive '%s' complete (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tracker.QueuedBuckets) > 0 {
|
||||||
|
return fmt.Errorf("not all buckets were healed: %v", tracker.QueuedBuckets)
|
||||||
}
|
}
|
||||||
|
|
||||||
if serverDebugLog {
|
if serverDebugLog {
|
||||||
@@ -379,7 +479,29 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
|||||||
logger.Info("\n")
|
logger.Info("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogIf(ctx, tracker.delete(ctx))
|
if tracker.HealID == "" { // HealID was empty only before Feb 2023
|
||||||
|
logger.LogIf(ctx, tracker.delete(ctx))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove .healing.bin from all disks with similar heal-id
|
||||||
|
disks, err := z.GetDisks(poolIdx, setIdx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, disk := range disks {
|
||||||
|
t, err := loadHealingTracker(ctx, disk)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, errFileNotFound) {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if t.HealID == tracker.HealID {
|
||||||
|
t.delete(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -415,10 +537,13 @@ func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools) {
|
|||||||
|
|
||||||
for _, disk := range healDisks {
|
for _, disk := range healDisks {
|
||||||
go func(disk Endpoint) {
|
go func(disk Endpoint) {
|
||||||
globalBackgroundHealState.markDiskForHealing(disk)
|
globalBackgroundHealState.setDiskHealingStatus(disk, true)
|
||||||
err := healFreshDisk(ctx, z, disk)
|
if err := healFreshDisk(ctx, z, disk); err != nil {
|
||||||
if err != nil {
|
globalBackgroundHealState.setDiskHealingStatus(disk, false)
|
||||||
printEndpointError(disk, err, false)
|
timedout := OperationTimedOut{}
|
||||||
|
if !errors.Is(err, context.Canceled) && !errors.As(err, &timedout) {
|
||||||
|
printEndpointError(disk, err, false)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Only upon success pop the healed disk.
|
// Only upon success pop the healed disk.
|
||||||
|
|||||||
@@ -182,6 +182,12 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "HealID":
|
||||||
|
z.HealID, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HealID")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err = dc.Skip()
|
err = dc.Skip()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -195,9 +201,9 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 22
|
// map header, size 23
|
||||||
// write "ID"
|
// write "ID"
|
||||||
err = en.Append(0xde, 0x0, 0x16, 0xa2, 0x49, 0x44)
|
err = en.Append(0xde, 0x0, 0x17, 0xa2, 0x49, 0x44)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -430,15 +436,25 @@ func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// write "HealID"
|
||||||
|
err = en.Append(0xa6, 0x48, 0x65, 0x61, 0x6c, 0x49, 0x44)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.HealID)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HealID")
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 22
|
// map header, size 23
|
||||||
// string "ID"
|
// string "ID"
|
||||||
o = append(o, 0xde, 0x0, 0x16, 0xa2, 0x49, 0x44)
|
o = append(o, 0xde, 0x0, 0x17, 0xa2, 0x49, 0x44)
|
||||||
o = msgp.AppendString(o, z.ID)
|
o = msgp.AppendString(o, z.ID)
|
||||||
// string "PoolIndex"
|
// string "PoolIndex"
|
||||||
o = append(o, 0xa9, 0x50, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78)
|
o = append(o, 0xa9, 0x50, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78)
|
||||||
@@ -509,6 +525,9 @@ func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
for za0002 := range z.HealedBuckets {
|
for za0002 := range z.HealedBuckets {
|
||||||
o = msgp.AppendString(o, z.HealedBuckets[za0002])
|
o = msgp.AppendString(o, z.HealedBuckets[za0002])
|
||||||
}
|
}
|
||||||
|
// string "HealID"
|
||||||
|
o = append(o, 0xa6, 0x48, 0x65, 0x61, 0x6c, 0x49, 0x44)
|
||||||
|
o = msgp.AppendString(o, z.HealID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -688,6 +707,12 @@ func (z *healingTracker) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "HealID":
|
||||||
|
z.HealID, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "HealID")
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
bts, err = msgp.Skip(bts)
|
bts, err = msgp.Skip(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -710,5 +735,6 @@ func (z *healingTracker) Msgsize() (s int) {
|
|||||||
for za0002 := range z.HealedBuckets {
|
for za0002 := range z.HealedBuckets {
|
||||||
s += msgp.StringPrefixSize + len(z.HealedBuckets[za0002])
|
s += msgp.StringPrefixSize + len(z.HealedBuckets[za0002])
|
||||||
}
|
}
|
||||||
|
s += 7 + msgp.StringPrefixSize + len(z.HealID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,800 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *BatchJobRequest) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "ID":
|
||||||
|
z.ID, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "User":
|
||||||
|
z.User, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "User")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Started":
|
||||||
|
z.Started, err = dc.ReadTime()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Started")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Location":
|
||||||
|
z.Location, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Location")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Replicate":
|
||||||
|
if dc.IsNil() {
|
||||||
|
err = dc.ReadNil()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Replicate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Replicate = nil
|
||||||
|
} else {
|
||||||
|
if z.Replicate == nil {
|
||||||
|
z.Replicate = new(BatchJobReplicateV1)
|
||||||
|
}
|
||||||
|
err = z.Replicate.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Replicate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "KeyRotate":
|
||||||
|
if dc.IsNil() {
|
||||||
|
err = dc.ReadNil()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "KeyRotate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.KeyRotate = nil
|
||||||
|
} else {
|
||||||
|
if z.KeyRotate == nil {
|
||||||
|
z.KeyRotate = new(BatchJobKeyRotateV1)
|
||||||
|
}
|
||||||
|
err = z.KeyRotate.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "KeyRotate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z *BatchJobRequest) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 6
|
||||||
|
// write "ID"
|
||||||
|
err = en.Append(0x86, 0xa2, 0x49, 0x44)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.ID)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "User"
|
||||||
|
err = en.Append(0xa4, 0x55, 0x73, 0x65, 0x72)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.User)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "User")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Started"
|
||||||
|
err = en.Append(0xa7, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteTime(z.Started)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Started")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Location"
|
||||||
|
err = en.Append(0xa8, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Location)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Location")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Replicate"
|
||||||
|
err = en.Append(0xa9, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.Replicate == nil {
|
||||||
|
err = en.WriteNil()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = z.Replicate.EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Replicate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// write "KeyRotate"
|
||||||
|
err = en.Append(0xa9, 0x4b, 0x65, 0x79, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if z.KeyRotate == nil {
|
||||||
|
err = en.WriteNil()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = z.KeyRotate.EncodeMsg(en)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "KeyRotate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z *BatchJobRequest) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 6
|
||||||
|
// string "ID"
|
||||||
|
o = append(o, 0x86, 0xa2, 0x49, 0x44)
|
||||||
|
o = msgp.AppendString(o, z.ID)
|
||||||
|
// string "User"
|
||||||
|
o = append(o, 0xa4, 0x55, 0x73, 0x65, 0x72)
|
||||||
|
o = msgp.AppendString(o, z.User)
|
||||||
|
// string "Started"
|
||||||
|
o = append(o, 0xa7, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64)
|
||||||
|
o = msgp.AppendTime(o, z.Started)
|
||||||
|
// string "Location"
|
||||||
|
o = append(o, 0xa8, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e)
|
||||||
|
o = msgp.AppendString(o, z.Location)
|
||||||
|
// string "Replicate"
|
||||||
|
o = append(o, 0xa9, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65)
|
||||||
|
if z.Replicate == nil {
|
||||||
|
o = msgp.AppendNil(o)
|
||||||
|
} else {
|
||||||
|
o, err = z.Replicate.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Replicate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// string "KeyRotate"
|
||||||
|
o = append(o, 0xa9, 0x4b, 0x65, 0x79, 0x52, 0x6f, 0x74, 0x61, 0x74, 0x65)
|
||||||
|
if z.KeyRotate == nil {
|
||||||
|
o = msgp.AppendNil(o)
|
||||||
|
} else {
|
||||||
|
o, err = z.KeyRotate.MarshalMsg(o)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "KeyRotate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *BatchJobRequest) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "ID":
|
||||||
|
z.ID, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "User":
|
||||||
|
z.User, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "User")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Started":
|
||||||
|
z.Started, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Started")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Location":
|
||||||
|
z.Location, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Location")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Replicate":
|
||||||
|
if msgp.IsNil(bts) {
|
||||||
|
bts, err = msgp.ReadNilBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.Replicate = nil
|
||||||
|
} else {
|
||||||
|
if z.Replicate == nil {
|
||||||
|
z.Replicate = new(BatchJobReplicateV1)
|
||||||
|
}
|
||||||
|
bts, err = z.Replicate.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Replicate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "KeyRotate":
|
||||||
|
if msgp.IsNil(bts) {
|
||||||
|
bts, err = msgp.ReadNilBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
z.KeyRotate = nil
|
||||||
|
} else {
|
||||||
|
if z.KeyRotate == nil {
|
||||||
|
z.KeyRotate = new(BatchJobKeyRotateV1)
|
||||||
|
}
|
||||||
|
bts, err = z.KeyRotate.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "KeyRotate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z *BatchJobRequest) Msgsize() (s int) {
|
||||||
|
s = 1 + 3 + msgp.StringPrefixSize + len(z.ID) + 5 + msgp.StringPrefixSize + len(z.User) + 8 + msgp.TimeSize + 9 + msgp.StringPrefixSize + len(z.Location) + 10
|
||||||
|
if z.Replicate == nil {
|
||||||
|
s += msgp.NilSize
|
||||||
|
} else {
|
||||||
|
s += z.Replicate.Msgsize()
|
||||||
|
}
|
||||||
|
s += 10
|
||||||
|
if z.KeyRotate == nil {
|
||||||
|
s += msgp.NilSize
|
||||||
|
} else {
|
||||||
|
s += z.KeyRotate.Msgsize()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *batchJobInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "v":
|
||||||
|
z.Version, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Version")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "jid":
|
||||||
|
z.JobID, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "JobID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "jt":
|
||||||
|
z.JobType, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "JobType")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "st":
|
||||||
|
z.StartTime, err = dc.ReadTime()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "StartTime")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "lu":
|
||||||
|
z.LastUpdate, err = dc.ReadTime()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "LastUpdate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ra":
|
||||||
|
z.RetryAttempts, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RetryAttempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "cmp":
|
||||||
|
z.Complete, err = dc.ReadBool()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Complete")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "fld":
|
||||||
|
z.Failed, err = dc.ReadBool()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "lbkt":
|
||||||
|
z.Bucket, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "lobj":
|
||||||
|
z.Object, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Object")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ob":
|
||||||
|
z.Objects, err = dc.ReadInt64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Objects")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "dm":
|
||||||
|
z.DeleteMarkers, err = dc.ReadInt64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "DeleteMarkers")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "obf":
|
||||||
|
z.ObjectsFailed, err = dc.ReadInt64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjectsFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "dmf":
|
||||||
|
z.DeleteMarkersFailed, err = dc.ReadInt64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "DeleteMarkersFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "bt":
|
||||||
|
z.BytesTransferred, err = dc.ReadInt64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesTransferred")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "bf":
|
||||||
|
z.BytesFailed, err = dc.ReadInt64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z *batchJobInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 16
|
||||||
|
// write "v"
|
||||||
|
err = en.Append(0xde, 0x0, 0x10, 0xa1, 0x76)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(z.Version)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Version")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "jid"
|
||||||
|
err = en.Append(0xa3, 0x6a, 0x69, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.JobID)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "JobID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "jt"
|
||||||
|
err = en.Append(0xa2, 0x6a, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.JobType)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "JobType")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "st"
|
||||||
|
err = en.Append(0xa2, 0x73, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteTime(z.StartTime)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "StartTime")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "lu"
|
||||||
|
err = en.Append(0xa2, 0x6c, 0x75)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteTime(z.LastUpdate)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "LastUpdate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "ra"
|
||||||
|
err = en.Append(0xa2, 0x72, 0x61)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(z.RetryAttempts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RetryAttempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "cmp"
|
||||||
|
err = en.Append(0xa3, 0x63, 0x6d, 0x70)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteBool(z.Complete)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Complete")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "fld"
|
||||||
|
err = en.Append(0xa3, 0x66, 0x6c, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteBool(z.Failed)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "lbkt"
|
||||||
|
err = en.Append(0xa4, 0x6c, 0x62, 0x6b, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Bucket)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "lobj"
|
||||||
|
err = en.Append(0xa4, 0x6c, 0x6f, 0x62, 0x6a)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Object)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Object")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "ob"
|
||||||
|
err = en.Append(0xa2, 0x6f, 0x62)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt64(z.Objects)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Objects")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "dm"
|
||||||
|
err = en.Append(0xa2, 0x64, 0x6d)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt64(z.DeleteMarkers)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "DeleteMarkers")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "obf"
|
||||||
|
err = en.Append(0xa3, 0x6f, 0x62, 0x66)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt64(z.ObjectsFailed)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjectsFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "dmf"
|
||||||
|
err = en.Append(0xa3, 0x64, 0x6d, 0x66)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt64(z.DeleteMarkersFailed)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "DeleteMarkersFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "bt"
|
||||||
|
err = en.Append(0xa2, 0x62, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt64(z.BytesTransferred)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesTransferred")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "bf"
|
||||||
|
err = en.Append(0xa2, 0x62, 0x66)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt64(z.BytesFailed)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z *batchJobInfo) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 16
|
||||||
|
// string "v"
|
||||||
|
o = append(o, 0xde, 0x0, 0x10, 0xa1, 0x76)
|
||||||
|
o = msgp.AppendInt(o, z.Version)
|
||||||
|
// string "jid"
|
||||||
|
o = append(o, 0xa3, 0x6a, 0x69, 0x64)
|
||||||
|
o = msgp.AppendString(o, z.JobID)
|
||||||
|
// string "jt"
|
||||||
|
o = append(o, 0xa2, 0x6a, 0x74)
|
||||||
|
o = msgp.AppendString(o, z.JobType)
|
||||||
|
// string "st"
|
||||||
|
o = append(o, 0xa2, 0x73, 0x74)
|
||||||
|
o = msgp.AppendTime(o, z.StartTime)
|
||||||
|
// string "lu"
|
||||||
|
o = append(o, 0xa2, 0x6c, 0x75)
|
||||||
|
o = msgp.AppendTime(o, z.LastUpdate)
|
||||||
|
// string "ra"
|
||||||
|
o = append(o, 0xa2, 0x72, 0x61)
|
||||||
|
o = msgp.AppendInt(o, z.RetryAttempts)
|
||||||
|
// string "cmp"
|
||||||
|
o = append(o, 0xa3, 0x63, 0x6d, 0x70)
|
||||||
|
o = msgp.AppendBool(o, z.Complete)
|
||||||
|
// string "fld"
|
||||||
|
o = append(o, 0xa3, 0x66, 0x6c, 0x64)
|
||||||
|
o = msgp.AppendBool(o, z.Failed)
|
||||||
|
// string "lbkt"
|
||||||
|
o = append(o, 0xa4, 0x6c, 0x62, 0x6b, 0x74)
|
||||||
|
o = msgp.AppendString(o, z.Bucket)
|
||||||
|
// string "lobj"
|
||||||
|
o = append(o, 0xa4, 0x6c, 0x6f, 0x62, 0x6a)
|
||||||
|
o = msgp.AppendString(o, z.Object)
|
||||||
|
// string "ob"
|
||||||
|
o = append(o, 0xa2, 0x6f, 0x62)
|
||||||
|
o = msgp.AppendInt64(o, z.Objects)
|
||||||
|
// string "dm"
|
||||||
|
o = append(o, 0xa2, 0x64, 0x6d)
|
||||||
|
o = msgp.AppendInt64(o, z.DeleteMarkers)
|
||||||
|
// string "obf"
|
||||||
|
o = append(o, 0xa3, 0x6f, 0x62, 0x66)
|
||||||
|
o = msgp.AppendInt64(o, z.ObjectsFailed)
|
||||||
|
// string "dmf"
|
||||||
|
o = append(o, 0xa3, 0x64, 0x6d, 0x66)
|
||||||
|
o = msgp.AppendInt64(o, z.DeleteMarkersFailed)
|
||||||
|
// string "bt"
|
||||||
|
o = append(o, 0xa2, 0x62, 0x74)
|
||||||
|
o = msgp.AppendInt64(o, z.BytesTransferred)
|
||||||
|
// string "bf"
|
||||||
|
o = append(o, 0xa2, 0x62, 0x66)
|
||||||
|
o = msgp.AppendInt64(o, z.BytesFailed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *batchJobInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "v":
|
||||||
|
z.Version, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Version")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "jid":
|
||||||
|
z.JobID, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "JobID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "jt":
|
||||||
|
z.JobType, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "JobType")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "st":
|
||||||
|
z.StartTime, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "StartTime")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "lu":
|
||||||
|
z.LastUpdate, bts, err = msgp.ReadTimeBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "LastUpdate")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ra":
|
||||||
|
z.RetryAttempts, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "RetryAttempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "cmp":
|
||||||
|
z.Complete, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Complete")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "fld":
|
||||||
|
z.Failed, bts, err = msgp.ReadBoolBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "lbkt":
|
||||||
|
z.Bucket, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Bucket")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "lobj":
|
||||||
|
z.Object, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Object")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ob":
|
||||||
|
z.Objects, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Objects")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "dm":
|
||||||
|
z.DeleteMarkers, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "DeleteMarkers")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "obf":
|
||||||
|
z.ObjectsFailed, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ObjectsFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "dmf":
|
||||||
|
z.DeleteMarkersFailed, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "DeleteMarkersFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "bt":
|
||||||
|
z.BytesTransferred, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesTransferred")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "bf":
|
||||||
|
z.BytesFailed, bts, err = msgp.ReadInt64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "BytesFailed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z *batchJobInfo) Msgsize() (s int) {
|
||||||
|
s = 3 + 2 + msgp.IntSize + 4 + msgp.StringPrefixSize + len(z.JobID) + 3 + msgp.StringPrefixSize + len(z.JobType) + 3 + msgp.TimeSize + 3 + msgp.TimeSize + 3 + msgp.IntSize + 4 + msgp.BoolSize + 4 + msgp.BoolSize + 5 + msgp.StringPrefixSize + len(z.Bucket) + 5 + msgp.StringPrefixSize + len(z.Object) + 3 + msgp.Int64Size + 3 + msgp.Int64Size + 4 + msgp.Int64Size + 4 + msgp.Int64Size + 3 + msgp.Int64Size + 3 + msgp.Int64Size
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobRequest(t *testing.T) {
|
||||||
|
v := BatchJobRequest{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobRequest(b *testing.B) {
|
||||||
|
v := BatchJobRequest{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobRequest(b *testing.B) {
|
||||||
|
v := BatchJobRequest{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobRequest(b *testing.B) {
|
||||||
|
v := BatchJobRequest{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobRequest(t *testing.T) {
|
||||||
|
v := BatchJobRequest{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobRequest Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobRequest{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobRequest(b *testing.B) {
|
||||||
|
v := BatchJobRequest{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobRequest(b *testing.B) {
|
||||||
|
v := BatchJobRequest{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalbatchJobInfo(t *testing.T) {
|
||||||
|
v := batchJobInfo{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgbatchJobInfo(b *testing.B) {
|
||||||
|
v := batchJobInfo{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgbatchJobInfo(b *testing.B) {
|
||||||
|
v := batchJobInfo{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalbatchJobInfo(b *testing.B) {
|
||||||
|
v := batchJobInfo{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodebatchJobInfo(t *testing.T) {
|
||||||
|
v := batchJobInfo{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodebatchJobInfo Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := batchJobInfo{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodebatchJobInfo(b *testing.B) {
|
||||||
|
v := batchJobInfo{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodebatchJobInfo(b *testing.B) {
|
||||||
|
v := batchJobInfo{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/pkg/v2/wildcard"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate msgp -file $GOFILE
|
||||||
|
|
||||||
|
// BatchJobKV is a key-value data type which supports wildcard matching
|
||||||
|
type BatchJobKV struct {
|
||||||
|
Key string `yaml:"key" json:"key"`
|
||||||
|
Value string `yaml:"value" json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate returns an error if key is empty
|
||||||
|
func (kv BatchJobKV) Validate() error {
|
||||||
|
if kv.Key == "" {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty indicates if kv is not set
|
||||||
|
func (kv BatchJobKV) Empty() bool {
|
||||||
|
return kv.Key == "" && kv.Value == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match matches input kv with kv, value will be wildcard matched depending on the user input
|
||||||
|
func (kv BatchJobKV) Match(ikv BatchJobKV) bool {
|
||||||
|
if kv.Empty() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.EqualFold(kv.Key, ikv.Key) {
|
||||||
|
return wildcard.Match(kv.Value, ikv.Value)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobNotification stores notification endpoint and token information.
|
||||||
|
// Used by batch jobs to notify of their status.
|
||||||
|
type BatchJobNotification struct {
|
||||||
|
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
||||||
|
Token string `yaml:"token" json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobRetry stores retry configuration used in the event of failures.
|
||||||
|
type BatchJobRetry struct {
|
||||||
|
Attempts int `yaml:"attempts" json:"attempts"` // number of retry attempts
|
||||||
|
Delay time.Duration `yaml:"delay" json:"delay"` // delay between each retries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates input replicate retries.
|
||||||
|
func (r BatchJobRetry) Validate() error {
|
||||||
|
if r.Attempts < 0 {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Delay < 0 {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,391 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *BatchJobKV) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "Key":
|
||||||
|
z.Key, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Value":
|
||||||
|
z.Value, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z BatchJobKV) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 2
|
||||||
|
// write "Key"
|
||||||
|
err = en.Append(0x82, 0xa3, 0x4b, 0x65, 0x79)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Key)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Value"
|
||||||
|
err = en.Append(0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Value)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z BatchJobKV) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 2
|
||||||
|
// string "Key"
|
||||||
|
o = append(o, 0x82, 0xa3, 0x4b, 0x65, 0x79)
|
||||||
|
o = msgp.AppendString(o, z.Key)
|
||||||
|
// string "Value"
|
||||||
|
o = append(o, 0xa5, 0x56, 0x61, 0x6c, 0x75, 0x65)
|
||||||
|
o = msgp.AppendString(o, z.Value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *BatchJobKV) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "Key":
|
||||||
|
z.Key, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Value":
|
||||||
|
z.Value, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z BatchJobKV) Msgsize() (s int) {
|
||||||
|
s = 1 + 4 + msgp.StringPrefixSize + len(z.Key) + 6 + msgp.StringPrefixSize + len(z.Value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *BatchJobNotification) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "Endpoint":
|
||||||
|
z.Endpoint, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Endpoint")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Token":
|
||||||
|
z.Token, err = dc.ReadString()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z BatchJobNotification) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 2
|
||||||
|
// write "Endpoint"
|
||||||
|
err = en.Append(0x82, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Endpoint")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Token"
|
||||||
|
err = en.Append(0xa5, 0x54, 0x6f, 0x6b, 0x65, 0x6e)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteString(z.Token)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z BatchJobNotification) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 2
|
||||||
|
// string "Endpoint"
|
||||||
|
o = append(o, 0x82, 0xa8, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74)
|
||||||
|
o = msgp.AppendString(o, z.Endpoint)
|
||||||
|
// string "Token"
|
||||||
|
o = append(o, 0xa5, 0x54, 0x6f, 0x6b, 0x65, 0x6e)
|
||||||
|
o = msgp.AppendString(o, z.Token)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *BatchJobNotification) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "Endpoint":
|
||||||
|
z.Endpoint, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Endpoint")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Token":
|
||||||
|
z.Token, bts, err = msgp.ReadStringBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z BatchJobNotification) Msgsize() (s int) {
|
||||||
|
s = 1 + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 6 + msgp.StringPrefixSize + len(z.Token)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMsg implements msgp.Decodable
|
||||||
|
func (z *BatchJobRetry) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, err = dc.ReadMapHeader()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, err = dc.ReadMapKeyPtr()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "Attempts":
|
||||||
|
z.Attempts, err = dc.ReadInt()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Attempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Delay":
|
||||||
|
z.Delay, err = dc.ReadDuration()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Delay")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = dc.Skip()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMsg implements msgp.Encodable
|
||||||
|
func (z BatchJobRetry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
|
// map header, size 2
|
||||||
|
// write "Attempts"
|
||||||
|
err = en.Append(0x82, 0xa8, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteInt(z.Attempts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Attempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "Delay"
|
||||||
|
err = en.Append(0xa5, 0x44, 0x65, 0x6c, 0x61, 0x79)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteDuration(z.Delay)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Delay")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMsg implements msgp.Marshaler
|
||||||
|
func (z BatchJobRetry) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
|
o = msgp.Require(b, z.Msgsize())
|
||||||
|
// map header, size 2
|
||||||
|
// string "Attempts"
|
||||||
|
o = append(o, 0x82, 0xa8, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73)
|
||||||
|
o = msgp.AppendInt(o, z.Attempts)
|
||||||
|
// string "Delay"
|
||||||
|
o = append(o, 0xa5, 0x44, 0x65, 0x6c, 0x61, 0x79)
|
||||||
|
o = msgp.AppendDuration(o, z.Delay)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMsg implements msgp.Unmarshaler
|
||||||
|
func (z *BatchJobRetry) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||||
|
var field []byte
|
||||||
|
_ = field
|
||||||
|
var zb0001 uint32
|
||||||
|
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for zb0001 > 0 {
|
||||||
|
zb0001--
|
||||||
|
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch msgp.UnsafeString(field) {
|
||||||
|
case "Attempts":
|
||||||
|
z.Attempts, bts, err = msgp.ReadIntBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Attempts")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "Delay":
|
||||||
|
z.Delay, bts, err = msgp.ReadDurationBytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "Delay")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
bts, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o = bts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
|
func (z BatchJobRetry) Msgsize() (s int) {
|
||||||
|
s = 1 + 9 + msgp.IntSize + 6 + msgp.DurationSize
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobKV(t *testing.T) {
|
||||||
|
v := BatchJobKV{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobKV(b *testing.B) {
|
||||||
|
v := BatchJobKV{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobKV(b *testing.B) {
|
||||||
|
v := BatchJobKV{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobKV(b *testing.B) {
|
||||||
|
v := BatchJobKV{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobKV(t *testing.T) {
|
||||||
|
v := BatchJobKV{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobKV Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobKV{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobKV(b *testing.B) {
|
||||||
|
v := BatchJobKV{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobKV(b *testing.B) {
|
||||||
|
v := BatchJobKV{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobNotification(t *testing.T) {
|
||||||
|
v := BatchJobNotification{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobNotification(b *testing.B) {
|
||||||
|
v := BatchJobNotification{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobNotification(b *testing.B) {
|
||||||
|
v := BatchJobNotification{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobNotification(b *testing.B) {
|
||||||
|
v := BatchJobNotification{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobNotification(t *testing.T) {
|
||||||
|
v := BatchJobNotification{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobNotification Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobNotification{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobNotification(b *testing.B) {
|
||||||
|
v := BatchJobNotification{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobNotification(b *testing.B) {
|
||||||
|
v := BatchJobNotification{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobRetry(t *testing.T) {
|
||||||
|
v := BatchJobRetry{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobRetry(b *testing.B) {
|
||||||
|
v := BatchJobRetry{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobRetry(b *testing.B) {
|
||||||
|
v := BatchJobRetry{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobRetry(b *testing.B) {
|
||||||
|
v := BatchJobRetry{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobRetry(t *testing.T) {
|
||||||
|
v := BatchJobRetry{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobRetry Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobRetry{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobRetry(b *testing.B) {
|
||||||
|
v := BatchJobRetry{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobRetry(b *testing.B) {
|
||||||
|
v := BatchJobRetry{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
miniogo "github.com/minio/minio-go/v7"
|
||||||
|
|
||||||
|
"github.com/minio/minio/internal/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate msgp -file $GOFILE
|
||||||
|
|
||||||
|
// replicate:
|
||||||
|
// # source of the objects to be replicated
|
||||||
|
// source:
|
||||||
|
// type: "minio"
|
||||||
|
// bucket: "testbucket"
|
||||||
|
// prefix: "spark/"
|
||||||
|
//
|
||||||
|
// # optional flags based filtering criteria
|
||||||
|
// # for source objects
|
||||||
|
// flags:
|
||||||
|
// filter:
|
||||||
|
// newerThan: "7d"
|
||||||
|
// olderThan: "7d"
|
||||||
|
// createdAfter: "date"
|
||||||
|
// createdBefore: "date"
|
||||||
|
// tags:
|
||||||
|
// - key: "name"
|
||||||
|
// value: "value*"
|
||||||
|
// metadata:
|
||||||
|
// - key: "content-type"
|
||||||
|
// value: "image/*"
|
||||||
|
// notify:
|
||||||
|
// endpoint: "https://splunk-hec.dev.com"
|
||||||
|
// token: "Splunk ..." # e.g. "Bearer token"
|
||||||
|
//
|
||||||
|
// # target where the objects must be replicated
|
||||||
|
// target:
|
||||||
|
// type: "minio"
|
||||||
|
// bucket: "testbucket1"
|
||||||
|
// endpoint: "https://play.min.io"
|
||||||
|
// path: "on"
|
||||||
|
// credentials:
|
||||||
|
// accessKey: "minioadmin"
|
||||||
|
// secretKey: "minioadmin"
|
||||||
|
// sessionToken: ""
|
||||||
|
|
||||||
|
// BatchReplicateFilter holds all the filters currently supported for batch replication
|
||||||
|
type BatchReplicateFilter struct {
|
||||||
|
NewerThan time.Duration `yaml:"newerThan,omitempty" json:"newerThan"`
|
||||||
|
OlderThan time.Duration `yaml:"olderThan,omitempty" json:"olderThan"`
|
||||||
|
CreatedAfter time.Time `yaml:"createdAfter,omitempty" json:"createdAfter"`
|
||||||
|
CreatedBefore time.Time `yaml:"createdBefore,omitempty" json:"createdBefore"`
|
||||||
|
Tags []BatchJobKV `yaml:"tags,omitempty" json:"tags"`
|
||||||
|
Metadata []BatchJobKV `yaml:"metadata,omitempty" json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobReplicateFlags various configurations for replication job definition currently includes
|
||||||
|
// - filter
|
||||||
|
// - notify
|
||||||
|
// - retry
|
||||||
|
type BatchJobReplicateFlags struct {
|
||||||
|
Filter BatchReplicateFilter `yaml:"filter" json:"filter"`
|
||||||
|
Notify BatchJobNotification `yaml:"notify" json:"notify"`
|
||||||
|
Retry BatchJobRetry `yaml:"retry" json:"retry"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobReplicateResourceType defines the type of batch jobs
|
||||||
|
type BatchJobReplicateResourceType string
|
||||||
|
|
||||||
|
// Validate validates if the replicate resource type is recognized and supported
|
||||||
|
func (t BatchJobReplicateResourceType) Validate() error {
|
||||||
|
switch t {
|
||||||
|
case BatchJobReplicateResourceMinIO:
|
||||||
|
case BatchJobReplicateResourceS3:
|
||||||
|
default:
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t BatchJobReplicateResourceType) isMinio() bool {
|
||||||
|
return t == BatchJobReplicateResourceMinIO
|
||||||
|
}
|
||||||
|
|
||||||
|
// Different types of batch jobs..
|
||||||
|
const (
|
||||||
|
BatchJobReplicateResourceMinIO BatchJobReplicateResourceType = "minio"
|
||||||
|
BatchJobReplicateResourceS3 BatchJobReplicateResourceType = "s3"
|
||||||
|
|
||||||
|
// add future targets
|
||||||
|
)
|
||||||
|
|
||||||
|
// BatchJobReplicateCredentials access credentials for batch replication it may
|
||||||
|
// be either for target or source.
|
||||||
|
type BatchJobReplicateCredentials struct {
|
||||||
|
AccessKey string `xml:"AccessKeyId" json:"accessKey,omitempty" yaml:"accessKey"`
|
||||||
|
SecretKey string `xml:"SecretAccessKey" json:"secretKey,omitempty" yaml:"secretKey"`
|
||||||
|
SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty" yaml:"sessionToken"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty indicates if credentials are not set
|
||||||
|
func (c BatchJobReplicateCredentials) Empty() bool {
|
||||||
|
return c.AccessKey == "" && c.SecretKey == "" && c.SessionToken == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates if credentials are valid
|
||||||
|
func (c BatchJobReplicateCredentials) Validate() error {
|
||||||
|
if !auth.IsAccessKeyValid(c.AccessKey) || !auth.IsSecretKeyValid(c.SecretKey) {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobReplicateTarget describes target element of the replication job that receives
|
||||||
|
// the filtered data from source
|
||||||
|
type BatchJobReplicateTarget struct {
|
||||||
|
Type BatchJobReplicateResourceType `yaml:"type" json:"type"`
|
||||||
|
Bucket string `yaml:"bucket" json:"bucket"`
|
||||||
|
Prefix string `yaml:"prefix" json:"prefix"`
|
||||||
|
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
||||||
|
Path string `yaml:"path" json:"path"`
|
||||||
|
Creds BatchJobReplicateCredentials `yaml:"credentials" json:"credentials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidPath returns true if path is valid
|
||||||
|
func (t BatchJobReplicateTarget) ValidPath() bool {
|
||||||
|
return t.Path == "on" || t.Path == "off" || t.Path == "auto" || t.Path == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobReplicateSource describes source element of the replication job that is
|
||||||
|
// the source of the data for the target
|
||||||
|
type BatchJobReplicateSource struct {
|
||||||
|
Type BatchJobReplicateResourceType `yaml:"type" json:"type"`
|
||||||
|
Bucket string `yaml:"bucket" json:"bucket"`
|
||||||
|
Prefix string `yaml:"prefix" json:"prefix"`
|
||||||
|
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
||||||
|
Path string `yaml:"path" json:"path"`
|
||||||
|
Creds BatchJobReplicateCredentials `yaml:"credentials" json:"credentials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidPath returns true if path is valid
|
||||||
|
func (s BatchJobReplicateSource) ValidPath() bool {
|
||||||
|
switch s.Path {
|
||||||
|
case "on", "off", "auto", "":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobReplicateV1 v1 of batch job replication
|
||||||
|
type BatchJobReplicateV1 struct {
|
||||||
|
APIVersion string `yaml:"apiVersion" json:"apiVersion"`
|
||||||
|
Flags BatchJobReplicateFlags `yaml:"flags" json:"flags"`
|
||||||
|
Target BatchJobReplicateTarget `yaml:"target" json:"target"`
|
||||||
|
Source BatchJobReplicateSource `yaml:"source" json:"source"`
|
||||||
|
|
||||||
|
clnt *miniogo.Core `msg:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteToLocal returns true if source is remote and target is local
|
||||||
|
func (r BatchJobReplicateV1) RemoteToLocal() bool {
|
||||||
|
return !r.Source.Creds.Empty()
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,688 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobReplicateCredentials(t *testing.T) {
|
||||||
|
v := BatchJobReplicateCredentials{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobReplicateCredentials(b *testing.B) {
|
||||||
|
v := BatchJobReplicateCredentials{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobReplicateCredentials(b *testing.B) {
|
||||||
|
v := BatchJobReplicateCredentials{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobReplicateCredentials(b *testing.B) {
|
||||||
|
v := BatchJobReplicateCredentials{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobReplicateCredentials(t *testing.T) {
|
||||||
|
v := BatchJobReplicateCredentials{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobReplicateCredentials Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobReplicateCredentials{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobReplicateCredentials(b *testing.B) {
|
||||||
|
v := BatchJobReplicateCredentials{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobReplicateCredentials(b *testing.B) {
|
||||||
|
v := BatchJobReplicateCredentials{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobReplicateFlags(t *testing.T) {
|
||||||
|
v := BatchJobReplicateFlags{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobReplicateFlags(b *testing.B) {
|
||||||
|
v := BatchJobReplicateFlags{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobReplicateFlags(b *testing.B) {
|
||||||
|
v := BatchJobReplicateFlags{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobReplicateFlags(b *testing.B) {
|
||||||
|
v := BatchJobReplicateFlags{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobReplicateFlags(t *testing.T) {
|
||||||
|
v := BatchJobReplicateFlags{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobReplicateFlags Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobReplicateFlags{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobReplicateFlags(b *testing.B) {
|
||||||
|
v := BatchJobReplicateFlags{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobReplicateFlags(b *testing.B) {
|
||||||
|
v := BatchJobReplicateFlags{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobReplicateSource(t *testing.T) {
|
||||||
|
v := BatchJobReplicateSource{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobReplicateSource(b *testing.B) {
|
||||||
|
v := BatchJobReplicateSource{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobReplicateSource(b *testing.B) {
|
||||||
|
v := BatchJobReplicateSource{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobReplicateSource(b *testing.B) {
|
||||||
|
v := BatchJobReplicateSource{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobReplicateSource(t *testing.T) {
|
||||||
|
v := BatchJobReplicateSource{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobReplicateSource Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobReplicateSource{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobReplicateSource(b *testing.B) {
|
||||||
|
v := BatchJobReplicateSource{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobReplicateSource(b *testing.B) {
|
||||||
|
v := BatchJobReplicateSource{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobReplicateTarget(t *testing.T) {
|
||||||
|
v := BatchJobReplicateTarget{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobReplicateTarget(b *testing.B) {
|
||||||
|
v := BatchJobReplicateTarget{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobReplicateTarget(b *testing.B) {
|
||||||
|
v := BatchJobReplicateTarget{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobReplicateTarget(b *testing.B) {
|
||||||
|
v := BatchJobReplicateTarget{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobReplicateTarget(t *testing.T) {
|
||||||
|
v := BatchJobReplicateTarget{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobReplicateTarget Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobReplicateTarget{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobReplicateTarget(b *testing.B) {
|
||||||
|
v := BatchJobReplicateTarget{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobReplicateTarget(b *testing.B) {
|
||||||
|
v := BatchJobReplicateTarget{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobReplicateV1(t *testing.T) {
|
||||||
|
v := BatchJobReplicateV1{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobReplicateV1(b *testing.B) {
|
||||||
|
v := BatchJobReplicateV1{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobReplicateV1(b *testing.B) {
|
||||||
|
v := BatchJobReplicateV1{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobReplicateV1(b *testing.B) {
|
||||||
|
v := BatchJobReplicateV1{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobReplicateV1(t *testing.T) {
|
||||||
|
v := BatchJobReplicateV1{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobReplicateV1 Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobReplicateV1{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobReplicateV1(b *testing.B) {
|
||||||
|
v := BatchJobReplicateV1{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobReplicateV1(b *testing.B) {
|
||||||
|
v := BatchJobReplicateV1{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchReplicateFilter(t *testing.T) {
|
||||||
|
v := BatchReplicateFilter{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchReplicateFilter(b *testing.B) {
|
||||||
|
v := BatchReplicateFilter{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchReplicateFilter(b *testing.B) {
|
||||||
|
v := BatchReplicateFilter{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchReplicateFilter(b *testing.B) {
|
||||||
|
v := BatchReplicateFilter{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchReplicateFilter(t *testing.T) {
|
||||||
|
v := BatchReplicateFilter{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchReplicateFilter Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchReplicateFilter{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchReplicateFilter(b *testing.B) {
|
||||||
|
v := BatchReplicateFilter{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchReplicateFilter(b *testing.B) {
|
||||||
|
v := BatchReplicateFilter{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/tags"
|
||||||
|
"github.com/minio/minio/internal/crypto"
|
||||||
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
|
"github.com/minio/minio/internal/kms"
|
||||||
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/pkg/v2/env"
|
||||||
|
"github.com/minio/pkg/v2/workers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// keyrotate:
|
||||||
|
// apiVersion: v1
|
||||||
|
// bucket: BUCKET
|
||||||
|
// prefix: PREFIX
|
||||||
|
// encryption:
|
||||||
|
// type: sse-s3 # valid values are sse-s3 and sse-kms
|
||||||
|
// key: <new-kms-key> # valid only for sse-kms
|
||||||
|
// context: <new-kms-key-context> # valid only for sse-kms
|
||||||
|
// # optional flags based filtering criteria
|
||||||
|
// # for all objects
|
||||||
|
// flags:
|
||||||
|
// filter:
|
||||||
|
// newerThan: "7d" # match objects newer than this value (e.g. 7d10h31s)
|
||||||
|
// olderThan: "7d" # match objects older than this value (e.g. 7d10h31s)
|
||||||
|
// createdAfter: "date" # match objects created after "date"
|
||||||
|
// createdBefore: "date" # match objects created before "date"
|
||||||
|
// tags:
|
||||||
|
// - key: "name"
|
||||||
|
// value: "pick*" # match objects with tag 'name', with all values starting with 'pick'
|
||||||
|
// metadata:
|
||||||
|
// - key: "content-type"
|
||||||
|
// value: "image/*" # match objects with 'content-type', with all values starting with 'image/'
|
||||||
|
// kmskey: "key-id" # match objects with KMS key-id (applicable only for sse-kms)
|
||||||
|
// notify:
|
||||||
|
// endpoint: "https://notify.endpoint" # notification endpoint to receive job status events
|
||||||
|
// token: "Bearer xxxxx" # optional authentication token for the notification endpoint
|
||||||
|
|
||||||
|
// retry:
|
||||||
|
// attempts: 10 # number of retries for the job before giving up
|
||||||
|
// delay: "500ms" # least amount of delay between each retry
|
||||||
|
|
||||||
|
//go:generate msgp -file $GOFILE -unexported
|
||||||
|
|
||||||
|
// BatchKeyRotationType defines key rotation type
|
||||||
|
type BatchKeyRotationType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
sses3 BatchKeyRotationType = "sse-s3"
|
||||||
|
ssekms BatchKeyRotationType = "sse-kms"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BatchJobKeyRotateEncryption defines key rotation encryption options passed
|
||||||
|
type BatchJobKeyRotateEncryption struct {
|
||||||
|
Type BatchKeyRotationType `yaml:"type" json:"type"`
|
||||||
|
Key string `yaml:"key" json:"key"`
|
||||||
|
Context string `yaml:"context" json:"context"`
|
||||||
|
kmsContext kms.Context `msg:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates input key rotation encryption options.
|
||||||
|
func (e BatchJobKeyRotateEncryption) Validate() error {
|
||||||
|
if e.Type != sses3 && e.Type != ssekms {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
spaces := strings.HasPrefix(e.Key, " ") || strings.HasSuffix(e.Key, " ")
|
||||||
|
if e.Type == ssekms && spaces {
|
||||||
|
return crypto.ErrInvalidEncryptionKeyID
|
||||||
|
}
|
||||||
|
if e.Type == ssekms && GlobalKMS != nil {
|
||||||
|
ctx := kms.Context{}
|
||||||
|
if e.Context != "" {
|
||||||
|
b, err := base64.StdEncoding.DecodeString(e.Context)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
if err := json.Unmarshal(b, &ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.kmsContext = kms.Context{}
|
||||||
|
for k, v := range ctx {
|
||||||
|
e.kmsContext[k] = v
|
||||||
|
}
|
||||||
|
ctx["MinIO batch API"] = "batchrotate" // Context for a test key operation
|
||||||
|
if _, err := GlobalKMS.GenerateKey(GlobalContext, e.Key, ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchKeyRotateFilter holds all the filters currently supported for batch replication
|
||||||
|
type BatchKeyRotateFilter struct {
|
||||||
|
NewerThan time.Duration `yaml:"newerThan,omitempty" json:"newerThan"`
|
||||||
|
OlderThan time.Duration `yaml:"olderThan,omitempty" json:"olderThan"`
|
||||||
|
CreatedAfter time.Time `yaml:"createdAfter,omitempty" json:"createdAfter"`
|
||||||
|
CreatedBefore time.Time `yaml:"createdBefore,omitempty" json:"createdBefore"`
|
||||||
|
Tags []BatchJobKV `yaml:"tags,omitempty" json:"tags"`
|
||||||
|
Metadata []BatchJobKV `yaml:"metadata,omitempty" json:"metadata"`
|
||||||
|
KMSKeyID string `yaml:"kmskeyid" json:"kmskey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchKeyRotateNotification success or failure notification endpoint for each job attempts
|
||||||
|
type BatchKeyRotateNotification struct {
|
||||||
|
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
||||||
|
Token string `yaml:"token" json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobKeyRotateFlags various configurations for replication job definition currently includes
|
||||||
|
// - filter
|
||||||
|
// - notify
|
||||||
|
// - retry
|
||||||
|
type BatchJobKeyRotateFlags struct {
|
||||||
|
Filter BatchKeyRotateFilter `yaml:"filter" json:"filter"`
|
||||||
|
Notify BatchJobNotification `yaml:"notify" json:"notify"`
|
||||||
|
Retry BatchJobRetry `yaml:"retry" json:"retry"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchJobKeyRotateV1 v1 of batch key rotation job
|
||||||
|
type BatchJobKeyRotateV1 struct {
|
||||||
|
APIVersion string `yaml:"apiVersion" json:"apiVersion"`
|
||||||
|
Flags BatchJobKeyRotateFlags `yaml:"flags" json:"flags"`
|
||||||
|
Bucket string `yaml:"bucket" json:"bucket"`
|
||||||
|
Prefix string `yaml:"prefix" json:"prefix"`
|
||||||
|
Endpoint string `yaml:"endpoint" json:"endpoint"`
|
||||||
|
Encryption BatchJobKeyRotateEncryption `yaml:"encryption" json:"encryption"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify notifies notification endpoint if configured regarding job failure or success.
|
||||||
|
func (r BatchJobKeyRotateV1) Notify(ctx context.Context, ri *batchJobInfo) error {
|
||||||
|
return notifyEndpoint(ctx, ri, r.Flags.Notify.Endpoint, r.Flags.Notify.Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyRotate rotates encryption key of an object
|
||||||
|
func (r *BatchJobKeyRotateV1) KeyRotate(ctx context.Context, api ObjectLayer, objInfo ObjectInfo) error {
|
||||||
|
srcBucket := r.Bucket
|
||||||
|
srcObject := objInfo.Name
|
||||||
|
|
||||||
|
if objInfo.DeleteMarker || !objInfo.VersionPurgeStatus.Empty() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
sseKMS := crypto.S3KMS.IsEncrypted(objInfo.UserDefined)
|
||||||
|
sseS3 := crypto.S3.IsEncrypted(objInfo.UserDefined)
|
||||||
|
if !sseKMS && !sseS3 { // neither sse-s3 nor sse-kms disallowed
|
||||||
|
return errInvalidEncryptionParameters
|
||||||
|
}
|
||||||
|
if sseKMS && r.Encryption.Type == sses3 { // previously encrypted with sse-kms, now sse-s3 disallowed
|
||||||
|
return errInvalidEncryptionParameters
|
||||||
|
}
|
||||||
|
versioned := globalBucketVersioningSys.PrefixEnabled(srcBucket, srcObject)
|
||||||
|
versionSuspended := globalBucketVersioningSys.PrefixSuspended(srcBucket, srcObject)
|
||||||
|
|
||||||
|
lock := api.NewNSLock(r.Bucket, objInfo.Name)
|
||||||
|
lkctx, err := lock.GetLock(ctx, globalOperationTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx = lkctx.Context()
|
||||||
|
defer lock.Unlock(lkctx)
|
||||||
|
|
||||||
|
opts := ObjectOptions{
|
||||||
|
VersionID: objInfo.VersionID,
|
||||||
|
Versioned: versioned,
|
||||||
|
VersionSuspended: versionSuspended,
|
||||||
|
NoLock: true,
|
||||||
|
}
|
||||||
|
obj, err := api.GetObjectInfo(ctx, r.Bucket, objInfo.Name, opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
oi := obj.Clone()
|
||||||
|
var (
|
||||||
|
newKeyID string
|
||||||
|
newKeyContext kms.Context
|
||||||
|
)
|
||||||
|
encMetadata := make(map[string]string)
|
||||||
|
for k, v := range oi.UserDefined {
|
||||||
|
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||||
|
encMetadata[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sseKMS || sseS3) && r.Encryption.Type == ssekms {
|
||||||
|
if err = r.Encryption.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newKeyID = strings.TrimPrefix(r.Encryption.Key, crypto.ARNPrefix)
|
||||||
|
newKeyContext = r.Encryption.kmsContext
|
||||||
|
}
|
||||||
|
if err = rotateKey(ctx, []byte{}, newKeyID, []byte{}, r.Bucket, oi.Name, encMetadata, newKeyContext); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we are rotating the keys, make sure to update the metadata.
|
||||||
|
oi.metadataOnly = true
|
||||||
|
oi.keyRotation = true
|
||||||
|
for k, v := range encMetadata {
|
||||||
|
oi.UserDefined[k] = v
|
||||||
|
}
|
||||||
|
if _, err := api.CopyObject(ctx, r.Bucket, oi.Name, r.Bucket, oi.Name, oi, ObjectOptions{
|
||||||
|
VersionID: oi.VersionID,
|
||||||
|
}, ObjectOptions{
|
||||||
|
VersionID: oi.VersionID,
|
||||||
|
NoLock: true,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
batchKeyRotationName = "batch-rotate.bin"
|
||||||
|
batchKeyRotationFormat = 1
|
||||||
|
batchKeyRotateVersionV1 = 1
|
||||||
|
batchKeyRotateVersion = batchKeyRotateVersionV1
|
||||||
|
batchKeyRotateAPIVersion = "v1"
|
||||||
|
batchKeyRotateJobDefaultRetries = 3
|
||||||
|
batchKeyRotateJobDefaultRetryDelay = 25 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start the batch key rottion job, resumes if there was a pending job via "job.ID"
|
||||||
|
func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job BatchJobRequest) error {
|
||||||
|
ri := &batchJobInfo{
|
||||||
|
JobID: job.ID,
|
||||||
|
JobType: string(job.Type()),
|
||||||
|
StartTime: job.Started,
|
||||||
|
}
|
||||||
|
if err := ri.load(ctx, api, job); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
|
lastObject := ri.Object
|
||||||
|
|
||||||
|
delay := job.KeyRotate.Flags.Retry.Delay
|
||||||
|
if delay == 0 {
|
||||||
|
delay = batchKeyRotateJobDefaultRetryDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
|
skip := func(info FileInfo) (ok bool) {
|
||||||
|
if r.Flags.Filter.OlderThan > 0 && time.Since(info.ModTime) < r.Flags.Filter.OlderThan {
|
||||||
|
// skip all objects that are newer than specified older duration
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Flags.Filter.NewerThan > 0 && time.Since(info.ModTime) >= r.Flags.Filter.NewerThan {
|
||||||
|
// skip all objects that are older than specified newer duration
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.Flags.Filter.CreatedAfter.IsZero() && r.Flags.Filter.CreatedAfter.Before(info.ModTime) {
|
||||||
|
// skip all objects that are created before the specified time.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !r.Flags.Filter.CreatedBefore.IsZero() && r.Flags.Filter.CreatedBefore.After(info.ModTime) {
|
||||||
|
// skip all objects that are created after the specified time.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Flags.Filter.Tags) > 0 {
|
||||||
|
// Only parse object tags if tags filter is specified.
|
||||||
|
tagMap := map[string]string{}
|
||||||
|
tagStr := info.Metadata[xhttp.AmzObjectTagging]
|
||||||
|
if len(tagStr) != 0 {
|
||||||
|
t, err := tags.ParseObjectTags(tagStr)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
tagMap = t.ToMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kv := range r.Flags.Filter.Tags {
|
||||||
|
for t, v := range tagMap {
|
||||||
|
if kv.Match(BatchJobKV{Key: t, Value: v}) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// None of the provided tags filter match skip the object
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Flags.Filter.Metadata) > 0 {
|
||||||
|
for _, kv := range r.Flags.Filter.Metadata {
|
||||||
|
for k, v := range info.Metadata {
|
||||||
|
if !stringsHasPrefixFold(k, "x-amz-meta-") && !isStandardHeader(k) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// We only need to match x-amz-meta or standardHeaders
|
||||||
|
if kv.Match(BatchJobKV{Key: k, Value: v}) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// None of the provided metadata filters match skip the object.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if r.Flags.Filter.KMSKeyID != "" {
|
||||||
|
if v, ok := info.Metadata[xhttp.AmzServerSideEncryptionKmsID]; ok && strings.TrimPrefix(v, crypto.ARNPrefix) != r.Flags.Filter.KMSKeyID {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
workerSize, err := strconv.Atoi(env.Get("_MINIO_BATCH_KEYROTATION_WORKERS", strconv.Itoa(runtime.GOMAXPROCS(0)/2)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
wk, err := workers.New(workerSize)
|
||||||
|
if err != nil {
|
||||||
|
// invalid worker size.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
retryAttempts := ri.RetryAttempts
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
results := make(chan ObjectInfo, 100)
|
||||||
|
if err := api.Walk(ctx, r.Bucket, r.Prefix, results, ObjectOptions{
|
||||||
|
WalkMarker: lastObject,
|
||||||
|
WalkFilter: skip,
|
||||||
|
}); err != nil {
|
||||||
|
cancel()
|
||||||
|
// Do not need to retry if we can't list objects on source.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for result := range results {
|
||||||
|
result := result
|
||||||
|
sseKMS := crypto.S3KMS.IsEncrypted(result.UserDefined)
|
||||||
|
sseS3 := crypto.S3.IsEncrypted(result.UserDefined)
|
||||||
|
if !sseKMS && !sseS3 { // neither sse-s3 nor sse-kms disallowed
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wk.Take()
|
||||||
|
go func() {
|
||||||
|
defer wk.Give()
|
||||||
|
for attempts := 1; attempts <= retryAttempts; attempts++ {
|
||||||
|
attempts := attempts
|
||||||
|
stopFn := globalBatchJobsMetrics.trace(batchKeyRotationMetricObject, job.ID, attempts, result)
|
||||||
|
success := true
|
||||||
|
if err := r.KeyRotate(ctx, api, result); err != nil {
|
||||||
|
stopFn(err)
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
success = false
|
||||||
|
} else {
|
||||||
|
stopFn(nil)
|
||||||
|
}
|
||||||
|
ri.trackCurrentBucketObject(r.Bucket, result, success)
|
||||||
|
ri.RetryAttempts = attempts
|
||||||
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
|
// persist in-memory state to disk after every 10secs.
|
||||||
|
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||||
|
if success {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if delay > 0 {
|
||||||
|
time.Sleep(delay + time.Duration(rnd.Float64()*float64(delay)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wk.Wait()
|
||||||
|
|
||||||
|
ri.Complete = ri.ObjectsFailed == 0
|
||||||
|
ri.Failed = ri.ObjectsFailed > 0
|
||||||
|
globalBatchJobsMetrics.save(job.ID, ri)
|
||||||
|
// persist in-memory state to disk.
|
||||||
|
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
||||||
|
|
||||||
|
if err := r.Notify(ctx, ri); err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("unable to notify %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//msgp:ignore batchKeyRotationJobError
|
||||||
|
type batchKeyRotationJobError struct {
|
||||||
|
Code string
|
||||||
|
Description string
|
||||||
|
HTTPStatusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e batchKeyRotationJobError) Error() string {
|
||||||
|
return e.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the job definition input
|
||||||
|
func (r *BatchJobKeyRotateV1) Validate(ctx context.Context, job BatchJobRequest, o ObjectLayer) error {
|
||||||
|
if r == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.APIVersion != batchKeyRotateAPIVersion {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Bucket == "" {
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := o.GetBucketInfo(ctx, r.Bucket, BucketOptions{}); err != nil {
|
||||||
|
if isErrBucketNotFound(err) {
|
||||||
|
return batchKeyRotationJobError{
|
||||||
|
Code: "NoSuchSourceBucket",
|
||||||
|
Description: "The specified source bucket does not exist",
|
||||||
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if GlobalKMS == nil {
|
||||||
|
return errKMSNotConfigured
|
||||||
|
}
|
||||||
|
if err := r.Encryption.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range r.Flags.Filter.Tags {
|
||||||
|
if err := tag.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, meta := range r.Flags.Filter.Metadata {
|
||||||
|
if err := meta.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.Flags.Retry.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,575 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinylib/msgp/msgp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobKeyRotateEncryption(t *testing.T) {
|
||||||
|
v := BatchJobKeyRotateEncryption{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobKeyRotateEncryption(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateEncryption{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobKeyRotateEncryption(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateEncryption{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobKeyRotateEncryption(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateEncryption{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobKeyRotateEncryption(t *testing.T) {
|
||||||
|
v := BatchJobKeyRotateEncryption{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobKeyRotateEncryption Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobKeyRotateEncryption{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobKeyRotateEncryption(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateEncryption{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobKeyRotateEncryption(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateEncryption{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobKeyRotateFlags(t *testing.T) {
|
||||||
|
v := BatchJobKeyRotateFlags{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobKeyRotateFlags(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateFlags{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobKeyRotateFlags(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateFlags{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobKeyRotateFlags(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateFlags{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobKeyRotateFlags(t *testing.T) {
|
||||||
|
v := BatchJobKeyRotateFlags{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobKeyRotateFlags Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobKeyRotateFlags{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobKeyRotateFlags(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateFlags{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobKeyRotateFlags(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateFlags{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchJobKeyRotateV1(t *testing.T) {
|
||||||
|
v := BatchJobKeyRotateV1{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchJobKeyRotateV1(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateV1{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchJobKeyRotateV1(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateV1{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchJobKeyRotateV1(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateV1{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchJobKeyRotateV1(t *testing.T) {
|
||||||
|
v := BatchJobKeyRotateV1{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchJobKeyRotateV1 Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchJobKeyRotateV1{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchJobKeyRotateV1(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateV1{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchJobKeyRotateV1(b *testing.B) {
|
||||||
|
v := BatchJobKeyRotateV1{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchKeyRotateFilter(t *testing.T) {
|
||||||
|
v := BatchKeyRotateFilter{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchKeyRotateFilter(b *testing.B) {
|
||||||
|
v := BatchKeyRotateFilter{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchKeyRotateFilter(b *testing.B) {
|
||||||
|
v := BatchKeyRotateFilter{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchKeyRotateFilter(b *testing.B) {
|
||||||
|
v := BatchKeyRotateFilter{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchKeyRotateFilter(t *testing.T) {
|
||||||
|
v := BatchKeyRotateFilter{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchKeyRotateFilter Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchKeyRotateFilter{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchKeyRotateFilter(b *testing.B) {
|
||||||
|
v := BatchKeyRotateFilter{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchKeyRotateFilter(b *testing.B) {
|
||||||
|
v := BatchKeyRotateFilter{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUnmarshalBatchKeyRotateNotification(t *testing.T) {
|
||||||
|
v := BatchKeyRotateNotification{}
|
||||||
|
bts, err := v.MarshalMsg(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
left, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
|
||||||
|
left, err = msgp.Skip(bts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(left) > 0 {
|
||||||
|
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalMsgBatchKeyRotateNotification(b *testing.B) {
|
||||||
|
v := BatchKeyRotateNotification{}
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.MarshalMsg(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendMsgBatchKeyRotateNotification(b *testing.B) {
|
||||||
|
v := BatchKeyRotateNotification{}
|
||||||
|
bts := make([]byte, 0, v.Msgsize())
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bts, _ = v.MarshalMsg(bts[0:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshalBatchKeyRotateNotification(b *testing.B) {
|
||||||
|
v := BatchKeyRotateNotification{}
|
||||||
|
bts, _ := v.MarshalMsg(nil)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.SetBytes(int64(len(bts)))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := v.UnmarshalMsg(bts)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeDecodeBatchKeyRotateNotification(t *testing.T) {
|
||||||
|
v := BatchKeyRotateNotification{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
|
||||||
|
m := v.Msgsize()
|
||||||
|
if buf.Len() > m {
|
||||||
|
t.Log("WARNING: TestEncodeDecodeBatchKeyRotateNotification Msgsize() is inaccurate")
|
||||||
|
}
|
||||||
|
|
||||||
|
vn := BatchKeyRotateNotification{}
|
||||||
|
err := msgp.Decode(&buf, &vn)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
err = msgp.NewReader(&buf).Skip()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeBatchKeyRotateNotification(b *testing.B) {
|
||||||
|
v := BatchKeyRotateNotification{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
en := msgp.NewWriter(msgp.Nowhere)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.EncodeMsg(en)
|
||||||
|
}
|
||||||
|
en.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeBatchKeyRotateNotification(b *testing.B) {
|
||||||
|
v := BatchKeyRotateNotification{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
msgp.Encode(&buf, &v)
|
||||||
|
b.SetBytes(int64(buf.Len()))
|
||||||
|
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||||
|
dc := msgp.NewReader(rd)
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := v.DecodeMsg(dc)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
// Code generated by "stringer -type=batchJobMetric -trimprefix=batchJobMetric batch-handlers.go"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[batchReplicationMetricObject-0]
|
||||||
|
_ = x[batchKeyRotationMetricObject-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _batchJobMetric_name = "batchReplicationMetricObjectbatchKeyRotationMetricObject"
|
||||||
|
|
||||||
|
var _batchJobMetric_index = [...]uint8{0, 28, 56}
|
||||||
|
|
||||||
|
func (i batchJobMetric) String() string {
|
||||||
|
if i >= batchJobMetric(len(_batchJobMetric_index)-1) {
|
||||||
|
return "batchJobMetric(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _batchJobMetric_name[_batchJobMetric_index[i]:_batchJobMetric_index[i+1]]
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ func runPutObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) {
|
|||||||
// obtains random bucket name.
|
// obtains random bucket name.
|
||||||
bucket := getRandomBucketName()
|
bucket := getRandomBucketName()
|
||||||
// create bucket.
|
// create bucket.
|
||||||
err = obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
|
err = obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ func runPutObjectPartBenchmark(b *testing.B, obj ObjectLayer, partSize int) {
|
|||||||
object := getRandomObjectName()
|
object := getRandomObjectName()
|
||||||
|
|
||||||
// create bucket.
|
// create bucket.
|
||||||
err = obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
|
err = obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ func runPutObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) {
|
|||||||
// obtains random bucket name.
|
// obtains random bucket name.
|
||||||
bucket := getRandomBucketName()
|
bucket := getRandomBucketName()
|
||||||
// create bucket.
|
// create bucket.
|
||||||
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
|
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-6
@@ -24,6 +24,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
@@ -93,16 +94,23 @@ func newStreamingBitrotWriter(disk StorageAPI, volume, filePath string, length i
|
|||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
h := algo.New()
|
h := algo.New()
|
||||||
|
|
||||||
bw := &streamingBitrotWriter{iow: w, closeWithErr: w.CloseWithError, h: h, shardSize: shardSize, canClose: &sync.WaitGroup{}}
|
bw := &streamingBitrotWriter{
|
||||||
|
iow: ioutil.NewDeadlineWriter(w, diskMaxTimeout),
|
||||||
|
closeWithErr: w.CloseWithError,
|
||||||
|
h: h,
|
||||||
|
shardSize: shardSize,
|
||||||
|
canClose: &sync.WaitGroup{},
|
||||||
|
}
|
||||||
bw.canClose.Add(1)
|
bw.canClose.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
defer bw.canClose.Done()
|
||||||
|
|
||||||
totalFileSize := int64(-1) // For compressed objects length will be unknown (represented by length=-1)
|
totalFileSize := int64(-1) // For compressed objects length will be unknown (represented by length=-1)
|
||||||
if length != -1 {
|
if length != -1 {
|
||||||
bitrotSumsTotalSize := ceilFrac(length, shardSize) * int64(h.Size()) // Size used for storing bitrot checksums.
|
bitrotSumsTotalSize := ceilFrac(length, shardSize) * int64(h.Size()) // Size used for storing bitrot checksums.
|
||||||
totalFileSize = bitrotSumsTotalSize + length
|
totalFileSize = bitrotSumsTotalSize + length
|
||||||
}
|
}
|
||||||
r.CloseWithError(disk.CreateFile(context.TODO(), volume, filePath, totalFileSize, r))
|
r.CloseWithError(disk.CreateFile(context.TODO(), volume, filePath, totalFileSize, r))
|
||||||
bw.canClose.Done()
|
|
||||||
}()
|
}()
|
||||||
return bw
|
return bw
|
||||||
}
|
}
|
||||||
@@ -146,16 +154,28 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
|||||||
// Can never happen unless there are programmer bugs
|
// Can never happen unless there are programmer bugs
|
||||||
return 0, errUnexpected
|
return 0, errUnexpected
|
||||||
}
|
}
|
||||||
|
ignoredErrs := []error{
|
||||||
|
errDiskNotFound,
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(b.volume, minioMetaBucket) {
|
||||||
|
ignoredErrs = append(ignoredErrs,
|
||||||
|
errFileNotFound,
|
||||||
|
errVolumeNotFound,
|
||||||
|
errFileVersionNotFound,
|
||||||
|
)
|
||||||
|
}
|
||||||
if b.rc == nil {
|
if b.rc == nil {
|
||||||
// For the first ReadAt() call we need to open the stream for reading.
|
// For the first ReadAt() call we need to open the stream for reading.
|
||||||
b.currOffset = offset
|
b.currOffset = offset
|
||||||
streamOffset := (offset/b.shardSize)*int64(b.h.Size()) + offset
|
streamOffset := (offset/b.shardSize)*int64(b.h.Size()) + offset
|
||||||
if len(b.data) == 0 && b.tillOffset != streamOffset {
|
if len(b.data) == 0 && b.tillOffset != streamOffset {
|
||||||
b.rc, err = b.disk.ReadFileStream(context.TODO(), b.volume, b.filePath, streamOffset, b.tillOffset-streamOffset)
|
b.rc, err = b.disk.ReadFileStream(context.TODO(), b.volume, b.filePath, streamOffset, b.tillOffset-streamOffset)
|
||||||
if err != nil && err != errDiskNotFound {
|
if err != nil {
|
||||||
logger.LogIf(GlobalContext,
|
if !IsErr(err, ignoredErrs...) {
|
||||||
fmt.Errorf("Reading erasure shards at (%s: %s/%s) returned '%w', will attempt to reconstruct if we have quorum",
|
logger.LogOnceIf(GlobalContext,
|
||||||
b.disk, b.volume, b.filePath, err))
|
fmt.Errorf("Reading erasure shards at (%s: %s/%s) returned '%w', will attempt to reconstruct if we have quorum",
|
||||||
|
b.disk, b.volume, b.filePath, err), "bitrot-read-file-stream-"+b.volume+"-"+b.filePath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
b.rc = io.NewSectionReader(bytes.NewReader(b.data), streamOffset, b.tillOffset-streamOffset)
|
b.rc = io.NewSectionReader(bytes.NewReader(b.data), streamOffset, b.tillOffset-streamOffset)
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/minio/madmin-go/v3"
|
||||||
|
"github.com/minio/minio/internal/pubsub"
|
||||||
|
)
|
||||||
|
|
||||||
|
const bootstrapTraceLimit = 4 << 10
|
||||||
|
|
||||||
|
type bootstrapTracer struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
info []madmin.TraceInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
var globalBootstrapTracer = &bootstrapTracer{}
|
||||||
|
|
||||||
|
func (bs *bootstrapTracer) Record(info madmin.TraceInfo) {
|
||||||
|
bs.mu.Lock()
|
||||||
|
defer bs.mu.Unlock()
|
||||||
|
|
||||||
|
if len(bs.info) > bootstrapTraceLimit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bs.info = append(bs.info, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs *bootstrapTracer) Events() []madmin.TraceInfo {
|
||||||
|
traceInfo := make([]madmin.TraceInfo, 0, bootstrapTraceLimit)
|
||||||
|
|
||||||
|
bs.mu.RLock()
|
||||||
|
for _, i := range bs.info {
|
||||||
|
traceInfo = append(traceInfo, i)
|
||||||
|
}
|
||||||
|
bs.mu.RUnlock()
|
||||||
|
|
||||||
|
return traceInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs *bootstrapTracer) Publish(ctx context.Context, trace *pubsub.PubSub[madmin.TraceInfo, madmin.TraceType]) {
|
||||||
|
for _, bsEvent := range bs.Events() {
|
||||||
|
if bsEvent.Message != "" {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
default:
|
||||||
|
trace.Publish(bsEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user