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  ?>