storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/mint/run/core/aws-sdk-php/quick-tests.php (about) 1 <?php 2 # 3 # Mint, (C) 2017 Minio, Inc. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 13 # distributed under the License is distributed on an "AS IS" BASIS, 14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 # See the License for the specific language governing permissions and 16 # limitations under the License. 17 # 18 19 require 'vendor/autoload.php'; 20 21 use Aws\S3\S3Client; 22 use Aws\Credentials; 23 use Aws\Exception\AwsException; 24 use GuzzleHttp\Psr7; 25 use GuzzleHttp\Psr7\Request; 26 use GuzzleHttp\Client; 27 28 // Constants 29 const FILE_1_KB = "datafile-1-kB"; 30 const FILE_5_MB = "datafile-5-MB"; 31 const HTTP_OK = "200"; 32 const HTTP_NOCONTENT = "204"; 33 const HTTP_BADREQUEST = "400"; 34 const HTTP_NOTIMPLEMENTED = "501"; 35 const HTTP_INTERNAL_ERROR = "500"; 36 const TEST_METADATA = ['param_1' => 'val-1']; 37 38 /** 39 * ClientConfig abstracts configuration details to connect to a 40 * S3-like service 41 */ 42 class ClientConfig { 43 public $creds; 44 public $endpoint; 45 public $region; 46 47 function __construct(string $access_key, string $secret_key, string $host, string $secure, string $region) { 48 $this->creds = new Aws\Credentials\Credentials($access_key, $secret_key); 49 50 if ($secure == "1") { 51 $this->endpoint = "https://" . $host; 52 } else { 53 $this->endpoint = "http://" . $host; 54 } 55 56 $this->region = $region; 57 } 58 } 59 60 /** 61 * randomName returns a name prefixed by aws-sdk-php using uniqid() 62 * from standard library 63 * 64 * @return string 65 */ 66 function randomName():string { 67 return uniqid("aws-sdk-php-"); 68 } 69 70 /** 71 * getStatusCode returns HTTP status code of the given result. 72 * 73 * @param $result - AWS\S3 result object 74 * 75 * @return string - HTTP status code. E.g, "400" for Bad Request. 76 */ 77 function getStatusCode($result):string { 78 return $result->toArray()['@metadata']['statusCode']; 79 } 80 81 /** 82 * runExceptionalTests executes a collection of tests that will throw 83 * a known exception. 84 * 85 * @param $s3Client AWS\S3\S3Client object 86 * 87 * @param $apiCall Name of the S3Client API method to call 88 * 89 * @param $exceptionMatcher Name of Aws\S3\Exception\S3Exception 90 * method to fetch exception details 91 * 92 * @param $exceptionParamMap Associative array of exception names to 93 * API parameters. E.g, 94 * $apiCall = 'headBucket' 95 * $exceptionMatcher = 'getStatusCode' 96 * $exceptionParamMap = [ 97 * // Non existent bucket 98 * '404' => ['Bucket' => $bucket['Name'] . '--'], 99 * 100 * // Non existent bucket 101 * '404' => ['Bucket' => $bucket['Name'] . '-non-existent'], 102 * ]; 103 * 104 * @return string - HTTP status code. E.g, "404" for Non existent bucket. 105 */ 106 function runExceptionalTests($s3Client, $apiCall, $exceptionMatcher, $exceptionParamMap) { 107 foreach($exceptionParamMap as $exn => $params) { 108 $exceptionCaught = false; 109 try { 110 $result = $s3Client->$apiCall($params); 111 } catch(Aws\S3\Exception\S3Exception $e) { 112 $exceptionCaught = true; 113 switch ($e->$exceptionMatcher()) { 114 case $exn: 115 // This is expected 116 continue 2; 117 default: 118 throw $e; 119 } 120 } 121 finally { 122 if (!$exceptionCaught) { 123 $message = sprintf("Expected %s to fail with %s", $apiCall, $exn); 124 throw new Exception($message); 125 } 126 } 127 } 128 } 129 130 /** 131 * testListBuckets tests ListBuckets S3 API 132 * 133 * @param $s3Client AWS\S3\S3Client object 134 * 135 * @return void 136 */ 137 function testListBuckets(S3Client $s3Client) { 138 $buckets = $s3Client->listBuckets(); 139 $debugger = $GLOBALS['debugger']; 140 foreach ($buckets['Buckets'] as $bucket){ 141 $debugger->out($bucket['Name'] . "\n"); 142 } 143 } 144 145 /** 146 * testBucketExists tests HEAD Bucket S3 API 147 * 148 * @param $s3Client AWS\S3\S3Client object 149 * 150 * @return void 151 */ 152 function testBucketExists(S3Client $s3Client) { 153 // List all buckets 154 $buckets = $s3Client->listBuckets(); 155 // All HEAD on existing buckets must return success 156 foreach($buckets['Buckets'] as $bucket) { 157 $result = $s3Client->headBucket(['Bucket' => $bucket['Name']]); 158 if (getStatusCode($result) != HTTP_OK) 159 throw new Exception('headBucket API failed for ' . $bucket['Name']); 160 } 161 162 // Run failure tests 163 $params = [ 164 // Non existent bucket 165 '404' => ['Bucket' => $bucket['Name'] . '--'], 166 167 // Non existent bucket 168 '404' => ['Bucket' => $bucket['Name'] . '-non-existent'], 169 ]; 170 runExceptionalTests($s3Client, 'headBucket', 'getStatusCode', $params); 171 } 172 173 174 /** 175 * testHeadObject tests HeadObject S3 API 176 * 177 * @param $s3Client AWS\S3\S3Client object 178 * 179 * @param $objects Associative array of buckets and objects 180 * 181 * @return void 182 */ 183 function testHeadObject($s3Client, $objects) { 184 foreach($objects as $bucket => $object) { 185 $result = $s3Client->headObject(['Bucket' => $bucket, 'Key' => $object]); 186 if (getStatusCode($result) != HTTP_OK) 187 throw new Exception('headObject API failed for ' . 188 $bucket . '/' . $object); 189 if (strtolower(json_encode($result['Metadata'])) != strtolower(json_encode(TEST_METADATA))) { 190 throw new Exception("headObject API Metadata didn't match for " . 191 $bucket . '/' . $object); 192 } 193 } 194 195 // Run failure tests 196 $params = [ 197 '404' => ['Bucket' => $bucket, 'Key' => $object . '-non-existent'] 198 ]; 199 runExceptionalTests($s3Client, 'headObject', 'getStatusCode', $params); 200 } 201 202 /** 203 * testListObjects tests ListObjectsV1 and V2 S3 APIs 204 * 205 * @param $s3Client AWS\S3\S3Client object 206 * 207 * @param $params associative array containing bucket and object names 208 * 209 * @return void 210 */ 211 function testListObjects($s3Client, $params) { 212 $bucket = $params['Bucket']; 213 $object = $params['Object']; 214 $debugger = $GLOBALS['debugger']; 215 try { 216 for ($i = 0; $i < 5; $i++) { 217 $copyKey = $object . '-copy-' . strval($i); 218 $result = $s3Client->copyObject([ 219 'Bucket' => $bucket, 220 'Key' => $copyKey, 221 'CopySource' => $bucket . '/' . $object, 222 ]); 223 if (getStatusCode($result) != HTTP_OK) 224 throw new Exception("copyObject API failed for " . $bucket . '/' . $object); 225 } 226 227 $paginator = $s3Client->getPaginator('ListObjects', ['Bucket' => $bucket]); 228 foreach ($paginator->search('Contents[].Key') as $key) { 229 $debugger->out('key = ' . $key . "\n"); 230 } 231 232 $paginator = $s3Client->getPaginator('ListObjectsV2', ['Bucket' => $bucket]); 233 foreach ($paginator->search('Contents[].Key') as $key) { 234 $debugger->out('key = ' . $key . "\n"); 235 } 236 237 $prefix = 'obj'; 238 $result = $s3Client->listObjects(['Bucket' => $bucket, 'Prefix' => $prefix]); 239 if (getStatusCode($result) != HTTP_OK || $result['Prefix'] != $prefix) 240 throw new Exception("listObject API failed for " . $bucket . '/' . $object); 241 242 $maxKeys = 1; 243 $result = $s3Client->listObjects(['Bucket' => $bucket, 'MaxKeys' => $maxKeys]); 244 if (getStatusCode($result) != HTTP_OK || count($result['Contents']) != $maxKeys) 245 throw new Exception("listObject API failed for " . $bucket . '/' . $object); 246 247 $params = [ 248 'InvalidArgument' => ['Bucket' => $bucket, 'MaxKeys' => -1], 249 'NoSuchBucket' => ['Bucket' => $bucket . '-non-existent'] 250 ]; 251 runExceptionalTests($s3Client, 'listObjects', 'getAwsErrorCode', $params); 252 253 } finally { 254 $s3Client->deleteObjects([ 255 'Bucket' => $bucket, 256 'Delete' => [ 257 'Objects' => array_map(function($a, $b) { 258 return ['Key' => $a . '-copy-' . strval($b)]; 259 }, array_fill(0, 5, $object), range(0,4)) 260 ], 261 ]); 262 } 263 } 264 265 /** 266 * testListMultipartUploads tests ListMultipartUploads, ListParts and 267 * UploadPartCopy S3 APIs 268 * 269 * @param $s3Client AWS\S3\S3Client object 270 * 271 * @param $params associative array containing bucket and object names 272 * 273 * @return void 274 */ 275 function testListMultipartUploads($s3Client, $params) { 276 $bucket = $params['Bucket']; 277 $object = $params['Object']; 278 $debugger = $GLOBALS['debugger']; 279 280 $data_dir = $GLOBALS['MINT_DATA_DIR']; 281 // Initiate multipart upload 282 $result = $s3Client->createMultipartUpload([ 283 'Bucket' => $bucket, 284 'Key' => $object . '-copy', 285 ]); 286 if (getStatusCode($result) != HTTP_OK) 287 throw new Exception('createMultipartupload API failed for ' . 288 $bucket . '/' . $object); 289 290 // upload 5 parts 291 $uploadId = $result['UploadId']; 292 $parts = []; 293 try { 294 for ($i = 0; $i < 5; $i++) { 295 $result = $s3Client->uploadPartCopy([ 296 'Bucket' => $bucket, 297 'Key' => $object . '-copy', 298 'UploadId' => $uploadId, 299 'PartNumber' => $i+1, 300 'CopySource' => $bucket . '/' . $object, 301 ]); 302 if (getStatusCode($result) != HTTP_OK) { 303 throw new Exception('uploadPart API failed for ' . 304 $bucket . '/' . $object); 305 } 306 array_push($parts, [ 307 'ETag' => $result['ETag'], 308 'PartNumber' => $i+1, 309 ]); 310 } 311 312 // ListMultipartUploads and ListParts may return empty 313 // responses in the case of minio gateway gcs and minio server 314 // FS mode. So, the following tests don't make assumptions on 315 // result response. 316 $paginator = $s3Client->getPaginator('ListMultipartUploads', 317 ['Bucket' => $bucket]); 318 foreach ($paginator->search('Uploads[].{Key: Key, UploadId: UploadId}') as $keyHash) { 319 $debugger->out('key = ' . $keyHash['Key'] . ' uploadId = ' . $keyHash['UploadId'] . "\n"); 320 } 321 322 $paginator = $s3Client->getPaginator('ListParts', [ 323 'Bucket' => $bucket, 324 'Key' => $object . '-copy', 325 'UploadId' => $uploadId, 326 ]); 327 foreach ($paginator->search('Parts[].{PartNumber: PartNumber, ETag: ETag}') as $partsHash) { 328 $debugger->out('partNumber = ' . $partsHash['PartNumber'] . ' ETag = ' . $partsHash['ETag'] . "\n"); 329 } 330 331 }finally { 332 $s3Client->abortMultipartUpload([ 333 'Bucket' => $bucket, 334 'Key' => $object . '-copy', 335 'UploadId' => $uploadId 336 ]); 337 } 338 } 339 340 /** 341 * initSetup creates buckets and objects necessary for the functional 342 * tests to run 343 * 344 * @param $s3Client AWS\S3\S3Client object 345 * 346 * @param $objects Associative array of buckets and objects 347 * 348 * @return void 349 */ 350 function initSetup(S3Client $s3Client, $objects) { 351 $MINT_DATA_DIR = $GLOBALS['MINT_DATA_DIR']; 352 foreach($objects as $bucket => $object) { 353 $s3Client->createBucket(['Bucket' => $bucket]); 354 $stream = NULL; 355 try { 356 if (!file_exists($MINT_DATA_DIR . '/' . FILE_1_KB)) 357 throw new Exception('File not found ' . $MINT_DATA_DIR . '/' . FILE_1_KB); 358 359 $stream = Psr7\stream_for(fopen($MINT_DATA_DIR . '/' . FILE_1_KB, 'r')); 360 $result = $s3Client->putObject([ 361 'Bucket' => $bucket, 362 'Key' => $object, 363 'Body' => $stream, 364 'Metadata' => TEST_METADATA, 365 ]); 366 if (getStatusCode($result) != HTTP_OK) 367 throw new Exception("putObject API failed for " . $bucket . '/' . $object); 368 } 369 370 finally { 371 // close data file 372 if (!is_null($stream)) 373 $stream->close(); 374 } 375 } 376 377 // Create an empty bucket for bucket policy + delete tests 378 $result = $s3Client->createBucket(['Bucket' => $GLOBALS['emptyBucket']]); 379 if (getStatusCode($result) != HTTP_OK) 380 throw new Exception("createBucket API failed for " . $bucket); 381 382 } 383 384 385 /** 386 * testGetPutObject tests GET/PUT object S3 API 387 * 388 * @param $s3Client AWS\S3\S3Client object 389 * 390 * @param $params associative array containing bucket and object names 391 * 392 * @return void 393 */ 394 function testGetPutObject($s3Client, $params) { 395 $bucket = $params['Bucket']; 396 $object = $params['Object']; 397 398 // Upload a 10KB file 399 $MINT_DATA_DIR = $GLOBALS['MINT_DATA_DIR']; 400 try { 401 $stream = Psr7\stream_for(fopen($MINT_DATA_DIR . '/' . FILE_1_KB, 'r')); 402 $result = $s3Client->putObject([ 403 'Bucket' => $bucket, 404 'Key' => $object, 405 'Body' => $stream, 406 ]); 407 } 408 finally { 409 $stream->close(); 410 } 411 412 if (getStatusCode($result) != HTTP_OK) 413 throw new Exception("putObject API failed for " . $bucket . '/' . $object); 414 415 // Download the same object and verify size 416 $result = $s3Client->getObject([ 417 'Bucket' => $bucket, 418 'Key' => $object, 419 ]); 420 if (getStatusCode($result) != HTTP_OK) 421 throw new Exception("getObject API failed for " . $bucket . '/' . $object); 422 423 $body = $result['Body']; 424 $bodyLen = 0; 425 while (!$body->eof()) { 426 $bodyLen += strlen($body->read(4096)); 427 } 428 429 if ($bodyLen != 1 * 1024) { 430 throw new Exception("Object downloaded has different content length than uploaded object " 431 . $bucket . '/' . $object); 432 } 433 } 434 435 /** 436 * testMultipartUploadFailure tests MultipartUpload failures 437 * 438 * @param $s3Client AWS\S3\S3Client object 439 * 440 * @param $params associative array containing bucket and object names 441 * 442 * @return void 443 */ 444 function testMultipartUploadFailure($s3Client, $params) { 445 $bucket = $params['Bucket']; 446 $object = $params['Object']; 447 448 $MINT_DATA_DIR = $GLOBALS['MINT_DATA_DIR']; 449 // Initiate multipart upload 450 $result = $s3Client->createMultipartUpload([ 451 'Bucket' => $bucket, 452 'Key' => $object, 453 ]); 454 if (getStatusCode($result) != HTTP_OK) 455 throw new Exception('createMultipartupload API failed for ' . 456 $bucket . '/' . $object); 457 458 // upload 2 parts 459 $uploadId = $result['UploadId']; 460 $parts = []; 461 try { 462 for ($i = 0; $i < 2; $i++) { 463 $stream = Psr7\stream_for(fopen($MINT_DATA_DIR . '/' . FILE_5_MB, 'r')); 464 $limitedStream = new Psr7\LimitStream($stream, 4 * 1024 * 1024, 0); 465 $result = $s3Client->uploadPart([ 466 'Bucket' => $bucket, 467 'Key' => $object, 468 'UploadId' => $uploadId, 469 'ContentLength' => 4 * 1024 * 1024, 470 'Body' => $limitedStream, 471 'PartNumber' => $i+1, 472 ]); 473 if (getStatusCode($result) != HTTP_OK) { 474 throw new Exception('uploadPart API failed for ' . 475 $bucket . '/' . $object); 476 } 477 array_push($parts, [ 478 'ETag' => $result['ETag'], 479 'PartNumber' => $i+1, 480 ]); 481 482 $limitedStream->close(); 483 $limitedStream = NULL; 484 } 485 } 486 finally { 487 if (!is_null($limitedStream)) 488 $limitedStream->close(); 489 } 490 491 $params = [ 492 'EntityTooSmall' => [ 493 'Bucket' => $bucket, 494 'Key' => $object, 495 'UploadId' => $uploadId, 496 'MultipartUpload' => [ 497 'Parts' => $parts, 498 ], 499 ], 500 'NoSuchUpload' => [ 501 'Bucket' => $bucket, 502 'Key' => $object, 503 'UploadId' => 'non-existent', 504 'MultipartUpload' => [ 505 'Parts' => $parts, 506 ], 507 ], 508 ]; 509 try { 510 runExceptionalTests($s3Client, 'completeMultipartUpload', 'getAwsErrorCode', $params); 511 }finally { 512 $s3Client->abortMultipartUpload([ 513 'Bucket' => $bucket, 514 'Key' => $object, 515 'UploadId' => $uploadId 516 ]); 517 } 518 } 519 520 /** 521 * testMultipartUpload tests MultipartUpload S3 APIs 522 * 523 * @param $s3Client AWS\S3\S3Client object 524 * 525 * @param $params associative array containing bucket and object names 526 * 527 * @return void 528 */ 529 function testMultipartUpload($s3Client, $params) { 530 $bucket = $params['Bucket']; 531 $object = $params['Object']; 532 533 $MINT_DATA_DIR = $GLOBALS['MINT_DATA_DIR']; 534 // Initiate multipart upload 535 $result = $s3Client->createMultipartUpload([ 536 'Bucket' => $bucket, 537 'Key' => $object, 538 ]); 539 if (getStatusCode($result) != HTTP_OK) 540 throw new Exception('createMultipartupload API failed for ' . 541 $bucket . '/' . $object); 542 543 // upload 2 parts 544 $uploadId = $result['UploadId']; 545 $parts = []; 546 try { 547 for ($i = 0; $i < 2; $i++) { 548 $stream = Psr7\stream_for(fopen($MINT_DATA_DIR . '/' . FILE_5_MB, 'r')); 549 $result = $s3Client->uploadPart([ 550 'Bucket' => $bucket, 551 'Key' => $object, 552 'UploadId' => $uploadId, 553 'ContentLength' => 5 * 1024 * 1024, 554 'Body' => $stream, 555 'PartNumber' => $i+1, 556 ]); 557 if (getStatusCode($result) != HTTP_OK) { 558 throw new Exception('uploadPart API failed for ' . 559 $bucket . '/' . $object); 560 } 561 array_push($parts, [ 562 'ETag' => $result['ETag'], 563 'PartNumber' => $i+1, 564 ]); 565 566 $stream->close(); 567 $stream = NULL; 568 } 569 } 570 finally { 571 if (!is_null($stream)) 572 $stream->close(); 573 } 574 575 // complete multipart upload 576 $result = $s3Client->completeMultipartUpload([ 577 'Bucket' => $bucket, 578 'Key' => $object, 579 'UploadId' => $uploadId, 580 'MultipartUpload' => [ 581 'Parts' => $parts, 582 ], 583 ]); 584 if (getStatusCode($result) != HTTP_OK) { 585 throw new Exception('completeMultipartupload API failed for ' . 586 $bucket . '/' . $object); 587 } 588 } 589 590 /** 591 * testAbortMultipartUpload tests aborting of a multipart upload 592 * 593 * @param $s3Client AWS\S3\S3Client object 594 * 595 * @param $params associative array containing bucket and object names 596 * 597 * @return void 598 */ 599 function testAbortMultipartUpload($s3Client, $params) { 600 $bucket = $params['Bucket']; 601 $object = $params['Object']; 602 603 $MINT_DATA_DIR = $GLOBALS['MINT_DATA_DIR']; 604 // Initiate multipart upload 605 $result = $s3Client->createMultipartUpload([ 606 'Bucket' => $bucket, 607 'Key' => $object, 608 ]); 609 if (getStatusCode($result) != HTTP_OK) 610 throw new Exception('createMultipartupload API failed for ' . 611 $bucket . '/' . $object); 612 613 // Abort multipart upload 614 $uploadId = $result['UploadId']; 615 $result = $s3Client->abortMultipartUpload([ 616 'Bucket' => $bucket, 617 'Key' => $object, 618 'UploadId' => $uploadId, 619 ]); 620 if (getStatusCode($result) != HTTP_NOCONTENT) 621 throw new Exception('abortMultipartupload API failed for ' . 622 $bucket . '/' . $object); 623 624 //Run failure tests 625 $params = [ 626 // Upload doesn't exist 627 'NoSuchUpload' => [ 628 'Bucket' => $bucket, 629 'Key' => $object, 630 'UploadId' => 'non-existent', 631 ], 632 ]; 633 runExceptionalTests($s3Client, 'abortMultipartUpload', 'getAwsErrorCode', $params); 634 } 635 636 /** 637 * testGetBucketLocation tests GET bucket location S3 API 638 * 639 * @param $s3Client AWS\S3\S3Client object 640 * 641 * @param $params associative array containing bucket name 642 * 643 * @return void 644 */ 645 function testGetBucketLocation($s3Client, $params) { 646 $bucket = $params['Bucket']; 647 648 // Valid test 649 $result = $s3Client->getBucketLocation(['Bucket' => $bucket]); 650 if (getStatusCode($result) != HTTP_OK) 651 throw new Exception('getBucketLocation API failed for ' . 652 $bucket); 653 654 // Run failure tests. 655 $params = [ 656 // Non existent bucket 657 'NoSuchBucket' => ['Bucket' => $bucket . '--'], 658 659 // Bucket not found 660 'NoSuchBucket' => ['Bucket' => $bucket . '-non-existent'], 661 ]; 662 runExceptionalTests($s3Client, 'getBucketLocation', 'getAwsErrorCode', $params); 663 } 664 665 /** 666 * testCopyObject tests copy object S3 API 667 * 668 * @param $s3Client AWS\S3\S3Client object 669 * 670 * @param $params associative array containing bucket and object name 671 * 672 * @return void 673 */ 674 function testCopyObject($s3Client, $params) { 675 $bucket = $params['Bucket']; 676 $object = $params['Object']; 677 678 $result = $s3Client->copyObject([ 679 'Bucket' => $bucket, 680 'Key' => $object . '-copy', 681 'CopySource' => $bucket . '/' . $object, 682 ]); 683 if (getStatusCode($result) != HTTP_OK) 684 throw new Exception('copyObject API failed for ' . 685 $bucket); 686 687 $s3Client->deleteObject([ 688 'Bucket' => $bucket, 689 'Key' => $object . '-copy', 690 ]); 691 692 // Run failure tests 693 $params = [ 694 // Invalid copy source format 695 'InvalidArgument' => [ 696 'Bucket' => $bucket, 697 'Key' => $object . '-copy', 698 'CopySource' => $bucket . $object 699 ], 700 701 // Missing source object 702 'NoSuchKey' => [ 703 'Bucket' => $bucket, 704 'Key' => $object . '-copy', 705 'CopySource' => $bucket . '/' . $object . '-non-existent' 706 ], 707 ]; 708 runExceptionalTests($s3Client, 'copyObject', 'getAwsErrorCode', $params); 709 } 710 711 /** 712 * testDeleteObjects tests Delete Objects S3 API 713 * 714 * @param $s3Client AWS\S3\S3Client object 715 * 716 * @param $params associative array containing bucket and object names 717 * 718 * @return void 719 */ 720 function testDeleteObjects($s3Client, $params) { 721 $bucket = $params['Bucket']; 722 $object = $params['Object']; 723 724 $copies = []; 725 for ($i = 0; $i < 3; $i++) { 726 $copyKey = $object . '-copy' . strval($i); 727 $result = $s3Client->copyObject([ 728 'Bucket' => $bucket, 729 'Key' => $copyKey, 730 'CopySource' => $bucket . '/' . $object, 731 ]); 732 if (getstatuscode($result) != HTTP_OK) 733 throw new Exception('copyobject API failed for ' . 734 $bucket); 735 array_push($copies, ['Key' => $copyKey]); 736 } 737 738 $result = $s3Client->deleteObjects([ 739 'Bucket' => $bucket, 740 'Delete' => [ 741 'Objects' => $copies, 742 ], 743 ]); 744 if (getstatuscode($result) != HTTP_OK) 745 throw new Exception('deleteObjects api failed for ' . 746 $bucket); 747 } 748 749 /** 750 * testAnonDeleteObjects tests Delete Objects S3 API for anonymous requests. 751 * The test case checks this scenario: 752 * http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html#multiobjectdeleteapi-examples 753 * 754 * @param $s3Client AWS\S3\S3Client object 755 * 756 * @param $params associative array containing bucket and object names 757 * 758 * @return void 759 */ 760 function testAnonDeleteObjects($s3Client, $params) { 761 $bucket = $params['Bucket']; 762 $object = $params['Object']; 763 764 // Create anonymous config object 765 $anonConfig = new ClientConfig("", "", $GLOBALS['endpoint'], $GLOBALS['secure'], $GLOBALS['region']); 766 767 // Create anonymous S3 client 768 $anonymousClient = new S3Client([ 769 'credentials' => false, 770 'endpoint' => $anonConfig->endpoint, 771 'use_path_style_endpoint' => true, 772 'region' => $anonConfig->region, 773 'version' => '2006-03-01' 774 ]); 775 776 $copies = []; 777 for ($i = 0; $i < 3; $i++) { 778 $copyKey = $object . '-copy' . strval($i); 779 $result = $s3Client->copyObject([ 780 'Bucket' => $bucket, 781 'Key' => $copyKey, 782 'CopySource' => $bucket . '/' . $object, 783 ]); 784 if (getstatuscode($result) != HTTP_OK) 785 throw new Exception('copyobject API failed for ' . 786 $bucket); 787 array_push($copies, ['Key' => $copyKey]); 788 } 789 790 // Try anonymous delete. 791 $result = $anonymousClient->deleteObjects([ 792 'Bucket' => $bucket, 793 'Delete' => [ 794 'Objects' => $copies, 795 ], 796 ]); 797 // Response code should be 200 798 if (getstatuscode($result) != HTTP_OK) 799 throw new Exception('deleteObjects returned incorrect response ' . 800 getStatusCode($result)); 801 802 // Each object should have error code AccessDenied 803 for ($i = 0; $i < 3; $i++) { 804 if ($result["Errors"][$i]["Code"] != "AccessDenied") 805 throw new Exception('Incorrect response deleteObjects anonymous 806 call for ' .$bucket); 807 } 808 809 // Delete objects after the test passed 810 $result = $s3Client->deleteObjects([ 811 'Bucket' => $bucket, 812 'Delete' => [ 813 'Objects' => $copies, 814 ], 815 ]); 816 817 if (getstatuscode($result) != HTTP_OK) 818 throw new Exception('deleteObjects api failed for ' . 819 $bucket); 820 821 // Each object should have empty code in case of successful delete 822 for ($i = 0; $i < 3; $i++) { 823 if (isset($result["Errors"][$i]) && $result["Errors"][$i]["Code"] != "") 824 throw new Exception('Incorrect response deleteObjects anonymous 825 call for ' .$bucket); 826 } 827 } 828 829 // Check if the policy statements are equal 830 function are_statements_equal($expected, $got) { 831 $expected = json_decode($expected, TRUE); 832 $got = json_decode($got, TRUE); 833 834 function are_actions_equal($action1, $action2) { 835 return ( 836 is_array($action1) 837 && is_array($action2) 838 && count($action1) == count($action2) 839 && array_diff($action1, $action2) === array_diff($action2, $action1) 840 ); 841 } 842 843 foreach ($expected['Statement'] as $index => $value) { 844 if (!are_actions_equal($value['Action'], $got['Statement'][$index]['Action'])) 845 return FALSE; 846 } 847 848 return TRUE; 849 850 } 851 /** 852 * testBucketPolicy tests GET/PUT/DELETE Bucket policy S3 APIs 853 * 854 * @param $s3Client AWS\S3\S3Client object 855 * 856 * @param $params associative array containing bucket and object names 857 * 858 * @return void 859 */ 860 function testBucketPolicy($s3Client, $params) { 861 $bucket = $params['Bucket']; 862 863 $downloadPolicy = sprintf('{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetBucketLocation","s3:ListBucket","s3:GetObject"],"Resource":["arn:aws:s3:::%s","arn:aws:s3:::%s/*"]}]}', $bucket, $bucket); 864 865 $result = $s3Client->putBucketPolicy([ 866 'Bucket' => $bucket, 867 'Policy' => $downloadPolicy 868 ]); 869 if (getstatuscode($result) != HTTP_NOCONTENT) 870 throw new Exception('putBucketPolicy API failed for ' . 871 $bucket); 872 $result = $s3Client->getBucketPolicy(['Bucket' => $bucket]); 873 if (getstatuscode($result) != HTTP_OK) 874 throw new Exception('getBucketPolicy API failed for ' . 875 $bucket); 876 877 if ($result['Policy'] != $downloadPolicy) 878 if (!are_statements_equal($result['Policy'], $downloadPolicy)) 879 throw new Exception('bucket policy we got is not we set'); 880 881 $result = $s3Client->getBucketPolicyStatus(['Bucket' => $bucket]); 882 $result = $result->get("PolicyStatus")["IsPublic"]; 883 if ($result) 884 throw new Exception('getBucketPolicyStatus API failed for ' . 885 $bucket); 886 887 // Delete the bucket, make the bucket (again) and check if policy is none 888 // Ref: https://github.com/minio/minio/issues/4714 889 $result = $s3Client->deleteBucket(['Bucket' => $bucket]); 890 if (getstatuscode($result) != HTTP_NOCONTENT) 891 throw new Exception('deleteBucket API failed for ' . 892 $bucket); 893 894 try { 895 $s3Client->getBucketPolicy(['Bucket' => $bucket]); 896 } catch (AWSException $e) { 897 switch ($e->getAwsErrorCode()) { 898 case 'NoSuchBucket': 899 break; 900 } 901 } 902 903 // Sleep is needed for Minio Gateway for Azure, ref: 904 // https://docs.microsoft.com/en-us/rest/api/storageservices/Delete-Container#remarks 905 sleep(40); 906 907 $s3Client->createBucket(['Bucket' => $bucket]); 908 909 $params = [ 910 '404' => ['Bucket' => $bucket] 911 ]; 912 runExceptionalTests($s3Client, 'getBucketPolicy', 'getStatusCode', $params); 913 914 try { 915 $MINT_DATA_DIR = $GLOBALS['MINT_DATA_DIR']; 916 // Create an object to test anonymous GET object 917 $object = 'test-anon'; 918 if (!file_exists($MINT_DATA_DIR . '/' . FILE_1_KB)) 919 throw new Exception('File not found ' . $MINT_DATA_DIR . '/' . FILE_1_KB); 920 921 $stream = Psr7\stream_for(fopen($MINT_DATA_DIR . '/' . FILE_1_KB, 'r')); 922 $result = $s3Client->putObject([ 923 'Bucket' => $bucket, 924 'Key' => $object, 925 'Body' => $stream, 926 ]); 927 if (getstatuscode($result) != HTTP_OK) 928 throw new Exception('createBucket API failed for ' . 929 $bucket); 930 931 $anonConfig = new ClientConfig("", "", $GLOBALS['endpoint'], $GLOBALS['secure'], $GLOBALS['region']); 932 $anonymousClient = new S3Client([ 933 'credentials' => false, 934 'endpoint' => $anonConfig->endpoint, 935 'use_path_style_endpoint' => true, 936 'region' => $anonConfig->region, 937 'version' => '2006-03-01' 938 ]); 939 runExceptionalTests($anonymousClient, 'getObject', 'getStatusCode', [ 940 '403' => [ 941 'Bucket' => $bucket, 942 'Key' => $object, 943 ] 944 ]); 945 946 $result = $s3Client->putBucketPolicy([ 947 'Bucket' => $bucket, 948 'Policy' => $downloadPolicy 949 ]); 950 if (getstatuscode($result) != HTTP_NOCONTENT) 951 throw new Exception('putBucketPolicy API failed for ' . 952 $bucket); 953 $result = $s3Client->getBucketPolicy(['Bucket' => $bucket]); 954 if (getstatuscode($result) != HTTP_OK) 955 throw new Exception('getBucketPolicy API failed for ' . 956 $bucket); 957 958 $result = $s3Client->deleteBucketPolicy(['Bucket' => $bucket]); 959 if (getstatuscode($result) != HTTP_NOCONTENT) 960 throw new Exception('deleteBucketPolicy API failed for ' . 961 $bucket); 962 } finally { 963 // close data file 964 if (!is_null($stream)) 965 $stream->close(); 966 $s3Client->deleteObject(['Bucket' => $bucket, 'Key' => $object]); 967 } 968 969 } 970 971 /** 972 * cleanupSetup removes all buckets and objects created during the 973 * functional test 974 * 975 * @param $s3Client AWS\S3\S3Client object 976 * 977 * @param $objects Associative array of buckets to objects 978 * 979 * @return void 980 */ 981 function cleanupSetup($s3Client, $objects) { 982 // Delete all objects 983 foreach ($objects as $bucket => $object) { 984 $s3Client->deleteObject(['Bucket' => $bucket, 'Key' => $object]); 985 } 986 987 // Delete the buckets incl. emptyBucket 988 $allBuckets = array_keys($objects); 989 array_push($allBuckets, $GLOBALS['emptyBucket']); 990 foreach ($allBuckets as $bucket) { 991 try { 992 // Delete the bucket 993 $s3Client->deleteBucket(['Bucket' => $bucket]); 994 995 // Wait until the bucket is removed from object store 996 $s3Client->waitUntil('BucketNotExists', ['Bucket' => $bucket]); 997 } catch (Exception $e) { 998 // Ignore exceptions thrown during cleanup 999 } 1000 } 1001 } 1002 1003 1004 /** 1005 * runTest helper function to wrap a test function and log 1006 * success or failure accordingly. 1007 * 1008 * @param myfunc name of test function to be run 1009 * 1010 * @param fnSignature function signature of the main S3 SDK API 1011 * 1012 * @param args parameters to be passed to test function 1013 * 1014 * @return void 1015 */ 1016 function runTest($s3Client, $myfunc, $fnSignature, $args = []) { 1017 try { 1018 $start_time = microtime(true); 1019 $status = "PASS"; 1020 $error = ""; 1021 $message = ""; 1022 $myfunc($s3Client, $args); 1023 } catch (AwsException $e) { 1024 $errorCode = $e->getAwsErrorCode(); 1025 // $fnSignature holds the specific API that is being 1026 // tested. It is possible that functions used to create the 1027 // test setup may not be implemented. 1028 if ($errorCode != "NotImplemented") { 1029 $status = "FAIL"; 1030 $error = $e->getMessage(); 1031 } else { 1032 $status = "NA"; 1033 $error = $e->getMessage(); 1034 $alert = sprintf("%s or a related API is NOT IMPLEMENTED, see \"error\" for exact details.", $fnSignature); 1035 } 1036 1037 } catch (Exception $e) { 1038 // This exception handler handles high-level custom exceptions. 1039 $status = "FAIL"; 1040 $error = $e->getMessage(); 1041 } finally { 1042 $end_time = microtime(true); 1043 $json_log = [ 1044 "name" => "aws-sdk-php", 1045 "function" => $fnSignature, 1046 "args" => $args, 1047 "duration" => sprintf("%d", ($end_time - $start_time) * 1000), // elapsed time in ms 1048 "status" => $status, 1049 ]; 1050 if ($error !== "") { 1051 $json_log["error"] = $error; 1052 } 1053 if ($message !== "") { 1054 $json_log["message"] = $message; 1055 } 1056 print_r(json_encode($json_log)."\n"); 1057 1058 // Exit on first failure. 1059 switch ($status) { 1060 case "FAIL": 1061 exit(1); 1062 } 1063 } 1064 } 1065 1066 // Get client configuration from environment variables 1067 $GLOBALS['access_key'] = getenv("ACCESS_KEY"); 1068 $GLOBALS['secret_key'] = getenv("SECRET_KEY"); 1069 $GLOBALS['endpoint'] = getenv("SERVER_ENDPOINT"); 1070 $GLOBALS['region'] = getenv("SERVER_REGION"); 1071 $GLOBALS['secure'] = getenv("ENABLE_HTTPS"); 1072 1073 /** 1074 * @global string $GLOBALS['MINT_DATA_DIR'] 1075 * @name $MINT_DATA_DIR 1076 */ 1077 $GLOBALS['MINT_DATA_DIR'] = '/mint/data'; 1078 $GLOBALS['MINT_DATA_DIR'] = getenv("MINT_DATA_DIR"); 1079 1080 1081 // Useful for debugging test failures; Set $debugmode it to true when required 1082 $debugmode = false; 1083 1084 interface Debugger { 1085 public function out($data); 1086 } 1087 1088 class EchoDebugger implements Debugger { 1089 public function out($data) { 1090 echo $data; 1091 } 1092 } 1093 1094 class NullDebugger implements Debugger { 1095 public function out($data) { 1096 // Do nothing 1097 } 1098 } 1099 1100 if($debugmode) 1101 $debugger = new EchoDebugger(); 1102 else 1103 $debugger = new NullDebugger(); 1104 1105 // Make $debugger global 1106 $GLOBALS['debugger'] = $debugger; 1107 1108 // Create config object 1109 $config = new ClientConfig($GLOBALS['access_key'], $GLOBALS['secret_key'], 1110 $GLOBALS['endpoint'], $GLOBALS['secure'], 1111 $GLOBALS['region']); 1112 1113 // Create a S3Client 1114 $s3Client = new S3Client([ 1115 'credentials' => $config->creds, 1116 'endpoint' => $config->endpoint, 1117 'use_path_style_endpoint' => true, 1118 'region' => $config->region, 1119 'version' => '2006-03-01' 1120 ]); 1121 1122 // Used by initSetup 1123 $emptyBucket = randomName(); 1124 $objects = [ 1125 randomName() => 'obj1', 1126 randomName() => 'obj2', 1127 ]; 1128 1129 try { 1130 initSetup($s3Client, $objects); 1131 $firstBucket = array_keys($objects)[0]; 1132 $firstObject = $objects[$firstBucket]; 1133 $testParams = ['Bucket' => $firstBucket, 'Object' => $firstObject]; 1134 runTest($s3Client, 'testGetBucketLocation', "getBucketLocation ( array \$params = [] )", ['Bucket' => $firstBucket]); 1135 runTest($s3Client, 'testListBuckets', "listBuckets ( array \$params = [] )"); 1136 runTest($s3Client, 'testListObjects', "listObjects ( array \$params = [] )", $testParams); 1137 runTest($s3Client, 'testListMultipartUploads', "listMultipartUploads ( array \$params = [] )", $testParams); 1138 runTest($s3Client, 'testBucketExists', "headBucket ( array \$params = [] )", array_keys($objects)); 1139 runTest($s3Client, 'testHeadObject', "headObject ( array \$params = [] )", $objects); 1140 runTest($s3Client, 'testGetPutObject', "getObject ( array \$params = [] )", $testParams); 1141 runTest($s3Client, 'testCopyObject', "copyObject ( array \$params = [] )", $testParams); 1142 runTest($s3Client, 'testDeleteObjects', "deleteObjects (array \$params = [] )", $testParams); 1143 runTest($s3Client, 'testAnonDeleteObjects', "anonDeleteObjects ( array \$params = [] )", $testParams); 1144 runTest($s3Client, 'testMultipartUpload', "createMultipartUpload ( array \$params = [] )", $testParams); 1145 runTest($s3Client, 'testMultipartUploadFailure', "uploadPart ( array \$params = [] )", $testParams); 1146 runTest($s3Client, 'testAbortMultipartUpload', "abortMultipartupload ( array \$params = [] )", $testParams); 1147 runTest($s3Client, 'testBucketPolicy', "getBucketPolicy ( array \$params = [] )", ['Bucket' => $emptyBucket]); 1148 } 1149 finally { 1150 cleanupSetup($s3Client, $objects); 1151 } 1152 1153 ?>