Overview
  • Namespace
  • Class

Namespaces

  • OpenCloud
    • Autoscale
      • Resource
    • CloudMonitoring
      • Collection
      • Exception
      • Resource
    • Common
      • Collection
      • Constants
      • Exceptions
      • Http
        • Message
      • Log
      • Resource
      • Service
    • Compute
      • Constants
      • Exception
      • Resource
    • Database
      • Resource
    • DNS
      • Collection
      • Resource
    • Identity
      • Constants
      • Resource
    • Image
      • Enum
      • Resource
        • JsonPatch
        • Schema
    • LoadBalancer
      • Collection
      • Enum
      • Resource
    • Networking
      • Resource
    • ObjectStore
      • Constants
      • Exception
      • Resource
      • Upload
    • Orchestration
      • Resource
    • Queues
      • Collection
      • Exception
      • Resource
    • Volume
      • Resource

Classes

  • OpenCloud\Volume\Resource\Snapshot
  • OpenCloud\Volume\Resource\Volume
  • OpenCloud\Volume\Resource\VolumeType
  1 <?php
  2 /**
  3  * Copyright 2012-2014 Rackspace US, 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  * distributed under the License is distributed on an "AS IS" BASIS,
 13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  * See the License for the specific language governing permissions and
 15  * limitations under the License.
 16  */
 17 
 18 namespace OpenCloud\ObjectStore\Resource;
 19 
 20 use Guzzle\Http\EntityBody;
 21 use Guzzle\Http\Exception\BadResponseException;
 22 use Guzzle\Http\Exception\ClientErrorResponseException;
 23 use Guzzle\Http\Message\Response;
 24 use Guzzle\Http\Url;
 25 use OpenCloud\Common\Constants\Size;
 26 use OpenCloud\Common\Exceptions;
 27 use OpenCloud\Common\Service\ServiceInterface;
 28 use OpenCloud\ObjectStore\Constants\Header as HeaderConst;
 29 use OpenCloud\ObjectStore\Exception\ContainerException;
 30 use OpenCloud\ObjectStore\Exception\ObjectNotFoundException;
 31 use OpenCloud\ObjectStore\Upload\DirectorySync;
 32 use OpenCloud\ObjectStore\Upload\TransferBuilder;
 33 
 34 /**
 35  * A container is a storage compartment for your data and provides a way for you
 36  * to organize your data. You can think of a container as a folder in Windows
 37  * or a directory in Unix. The primary difference between a container and these
 38  * other file system concepts is that containers cannot be nested.
 39  *
 40  * A container can also be CDN-enabled (for public access), in which case you
 41  * will need to interact with a CDNContainer object instead of this one.
 42  */
 43 class Container extends AbstractContainer
 44 {
 45     const METADATA_LABEL = 'Container';
 46 
 47     /**
 48      * This is the object that holds all the CDN functionality. This Container therefore acts as a simple wrapper and is
 49      * interested in storage concerns only.
 50      *
 51      * @var CDNContainer|null
 52      */
 53     private $cdn;
 54 
 55     public function __construct(ServiceInterface $service, $data = null)
 56     {
 57         parent::__construct($service, $data);
 58 
 59         // Set metadata items for collection listings
 60         if (isset($data->count)) {
 61             $this->metadata->setProperty('Object-Count', $data->count);
 62         }
 63         if (isset($data->bytes)) {
 64             $this->metadata->setProperty('Bytes-Used', $data->bytes);
 65         }
 66     }
 67 
 68     /**
 69      * Factory method that instantiates an object from a Response object.
 70      *
 71      * @param Response         $response
 72      * @param ServiceInterface $service
 73      * @return static
 74      */
 75     public static function fromResponse(Response $response, ServiceInterface $service)
 76     {
 77         $self = parent::fromResponse($response, $service);
 78 
 79         $segments = Url::factory($response->getEffectiveUrl())->getPathSegments();
 80         $self->name = end($segments);
 81 
 82         return $self;
 83     }
 84 
 85     /**
 86      * Get the CDN object.
 87      *
 88      * @return null|CDNContainer
 89      * @throws \OpenCloud\Common\Exceptions\CdnNotAvailableError
 90      */
 91     public function getCdn()
 92     {
 93         if (!$this->isCdnEnabled()) {
 94             throw new Exceptions\CdnNotAvailableError(
 95                 'Either this container is not CDN-enabled or the CDN is not available'
 96             );
 97         }
 98 
 99         return $this->cdn;
100     }
101 
102     /**
103      * It would be awesome to put these convenience methods (which are identical to the ones in the Account object) in
104      * a trait, but we have to wait for v5.3 EOL first...
105      *
106      * @return null|string|int
107      */
108     public function getObjectCount()
109     {
110         return $this->metadata->getProperty('Object-Count');
111     }
112 
113     /**
114      * @return null|string|int
115      */
116     public function getBytesUsed()
117     {
118         return $this->metadata->getProperty('Bytes-Used');
119     }
120 
121     /**
122      * @param $value
123      * @return mixed
124      */
125     public function setCountQuota($value)
126     {
127         $this->metadata->setProperty('Quota-Count', $value);
128 
129         return $this->saveMetadata($this->metadata->toArray());
130     }
131 
132     /**
133      * @return null|string|int
134      */
135     public function getCountQuota()
136     {
137         return $this->metadata->getProperty('Quota-Count');
138     }
139 
140     /**
141      * @param $value
142      * @return mixed
143      */
144     public function setBytesQuota($value)
145     {
146         $this->metadata->setProperty('Quota-Bytes', $value);
147 
148         return $this->saveMetadata($this->metadata->toArray());
149     }
150 
151     /**
152      * @return null|string|int
153      */
154     public function getBytesQuota()
155     {
156         return $this->metadata->getProperty('Quota-Bytes');
157     }
158 
159     public function delete($deleteObjects = false)
160     {
161         if ($deleteObjects === true) {
162             // Delegate to auxiliary method
163             return $this->deleteWithObjects();
164         }
165 
166         try {
167             return $this->getClient()->delete($this->getUrl())->send();
168         } catch (ClientErrorResponseException $e) {
169             if ($e->getResponse()->getStatusCode() == 409) {
170                 throw new ContainerException(sprintf(
171                     'The API returned this error: %s. You might have to delete all existing objects before continuing.',
172                     (string) $e->getResponse()->getBody()
173                 ));
174             } else {
175                 throw $e;
176             }
177         }
178     }
179 
180     public function deleteWithObjects($secondsToWait = null)
181     {
182         // If container is empty, just delete it
183         $numObjects = (int) $this->retrieveMetadata()->getProperty('Object-Count');
184         if (0 === $numObjects) {
185             return $this->delete();
186         }
187 
188         // If timeout ($secondsToWait) is not specified by caller,
189         // try to estimate it based on number of objects in container
190         if (null === $secondsToWait) {
191             $secondsToWait = round($numObjects / 2);
192         }
193 
194         // Attempt to delete all objects and container
195         $endTime = time() + $secondsToWait;
196         $containerDeleted = false;
197         while ((time() < $endTime) && !$containerDeleted) {
198             $this->deleteAllObjects();
199             try {
200                 $response = $this->delete();
201                 $containerDeleted = true;
202             } catch (ContainerException $e) {
203                 // Ignore exception and try again
204             } catch (ClientErrorResponseException $e) {
205                 if ($e->getResponse()->getStatusCode() == 404) {
206                     // Container has been deleted
207                     $containerDeleted = true;
208                 } else {
209                     throw $e;
210                 }
211             }
212         }
213 
214         if (!$containerDeleted) {
215             throw new ContainerException('Container and all its objects could not be deleted.');
216         }
217 
218         return $response;
219     }
220 
221     /**
222      * Deletes all objects that this container currently contains. Useful when doing operations (like a delete) that
223      * require an empty container first.
224      *
225      * @return mixed
226      */
227     public function deleteAllObjects()
228     {
229         $paths = array();
230         $objects = $this->objectList();
231         foreach ($objects as $object) {
232             $paths[] = sprintf('/%s/%s', $this->getName(), $object->getName());
233         }
234         return $this->getService()->batchDelete($paths);
235     }
236 
237     /**
238      * Creates a Collection of objects in the container
239      *
240      * @param array $params associative array of parameter values.
241      *                      * account/tenant - The unique identifier of the account/tenant.
242      *                      * container- The unique identifier of the container.
243      *                      * limit (Optional) - The number limit of results.
244      *                      * marker (Optional) - Value of the marker, that the object names
245      *                      greater in value than are returned.
246      *                      * end_marker (Optional) - Value of the marker, that the object names
247      *                      less in value than are returned.
248      *                      * prefix (Optional) - Value of the prefix, which the returned object
249      *                      names begin with.
250      *                      * format (Optional) - Value of the serialized response format, either
251      *                      json or xml.
252      *                      * delimiter (Optional) - Value of the delimiter, that all the object
253      *                      names nested in the container are returned.
254      * @link   http://api.openstack.org for a list of possible parameter
255      *                      names and values
256      * @return \OpenCloud\Common\Collection
257      * @throws ObjFetchError
258      */
259     public function objectList(array $params = array())
260     {
261         $params['format'] = 'json';
262 
263         return $this->getService()->resourceList('DataObject', $this->getUrl(null, $params), $this);
264     }
265 
266     /**
267      * Turn on access logs, which track all the web traffic that your data objects accrue.
268      *
269      * @return \Guzzle\Http\Message\Response
270      */
271     public function enableLogging()
272     {
273         return $this->saveMetadata($this->appendToMetadata(array(
274             HeaderConst::ACCESS_LOGS => 'True'
275         )));
276     }
277 
278     /**
279      * Disable access logs.
280      *
281      * @return \Guzzle\Http\Message\Response
282      */
283     public function disableLogging()
284     {
285         return $this->saveMetadata($this->appendToMetadata(array(
286             HeaderConst::ACCESS_LOGS => 'False'
287         )));
288     }
289 
290     /**
291      * Enable this container for public CDN access.
292      *
293      * @param null $ttl
294      */
295     public function enableCdn($ttl = null)
296     {
297         $headers = array('X-CDN-Enabled' => 'True');
298         if ($ttl) {
299             $headers['X-TTL'] = (int) $ttl;
300         }
301 
302         $this->getClient()->put($this->getCdnService()->getUrl($this->name), $headers)->send();
303         $this->refresh();
304     }
305 
306     /**
307      * Disables the containers CDN function. Note that the container will still
308      * be available on the CDN until its TTL expires.
309      *
310      * @return \Guzzle\Http\Message\Response
311      */
312     public function disableCdn()
313     {
314         $headers = array('X-CDN-Enabled' => 'False');
315 
316         return $this->getClient()
317             ->put($this->getCdnService()->getUrl($this->name), $headers)
318             ->send();
319     }
320 
321     public function refresh($id = null, $url = null)
322     {
323         $headers = $this->createRefreshRequest()->send()->getHeaders();
324         $this->setMetadata($headers, true);
325 
326         try {
327             if (null !== ($cdnService = $this->getService()->getCDNService())) {
328                 $cdn = new CDNContainer($cdnService);
329                 $cdn->setName($this->name);
330 
331                 $response = $cdn->createRefreshRequest()->send();
332 
333                 if ($response->isSuccessful()) {
334                     $this->cdn = $cdn;
335                     $this->cdn->setMetadata($response->getHeaders(), true);
336                 }
337             } else {
338                 $this->cdn = null;
339             }
340         } catch (ClientErrorResponseException $e) {
341         }
342     }
343 
344     /**
345      * Get either a fresh data object (no $info), or get an existing one by passing in data for population.
346      *
347      * @param  mixed $info
348      * @return DataObject
349      */
350     public function dataObject($info = null)
351     {
352         return new DataObject($this, $info);
353     }
354 
355     /**
356      * Retrieve an object from the API. Apart from using the name as an
357      * identifier, you can also specify additional headers that will be used
358      * fpr a conditional GET request. These are
359      *
360      * * `If-Match'
361      * * `If-None-Match'
362      * * `If-Modified-Since'
363      * * `If-Unmodified-Since'
364      * * `Range'  For example:
365      *      bytes=-5    would mean the last 5 bytes of the object
366      *      bytes=10-15 would mean 5 bytes after a 10 byte offset
367      *      bytes=32-   would mean all dat after first 32 bytes
368      *
369      * These are also documented in RFC 2616.
370      *
371      * @param string $name
372      * @param array  $headers
373      * @return DataObject
374      */
375     public function getObject($name, array $headers = array())
376     {
377         try {
378             $response = $this->getClient()
379                 ->get($this->getUrl($name), $headers)
380                 ->send();
381         } catch (BadResponseException $e) {
382             if ($e->getResponse()->getStatusCode() == 404) {
383                 throw ObjectNotFoundException::factory($name, $e);
384             }
385             throw $e;
386         }
387 
388         return $this->dataObject()
389             ->populateFromResponse($response)
390             ->setName($name);
391     }
392 
393     /**
394      * Essentially the same as {@see getObject()}, except only the metadata is fetched from the API.
395      * This is useful for cases when the user does not want to fetch the full entity body of the
396      * object, only its metadata.
397      *
398      * @param       $name
399      * @param array $headers
400      * @return $this
401      */
402     public function getPartialObject($name, array $headers = array())
403     {
404         $response = $this->getClient()
405             ->head($this->getUrl($name), $headers)
406             ->send();
407 
408         return $this->dataObject()
409             ->populateFromResponse($response)
410             ->setName($name);
411     }
412 
413     /**
414      * Check if an object exists inside a container. Uses {@see getPartialObject()}
415      * to save on bandwidth and time.
416      *
417      * @param  $name    Object name
418      * @return boolean  True, if object exists in this container; false otherwise.
419      */
420     public function objectExists($name)
421     {
422         try {
423             // Send HEAD request to check resource existence
424             $url = clone $this->getUrl();
425             $url->addPath((string) $name);
426             $this->getClient()->head($url)->send();
427         } catch (ClientErrorResponseException $e) {
428             // If a 404 was returned, then the object doesn't exist
429             if ($e->getResponse()->getStatusCode() === 404) {
430                 return false;
431             } else {
432                 throw $e;
433             }
434         }
435 
436         return true;
437     }
438 
439     /**
440      * Upload a single file to the API.
441      *
442      * @param       $name    Name that the file will be saved as in your container.
443      * @param       $data    Either a string or stream representation of the file contents to be uploaded.
444      * @param array $headers Optional headers that will be sent with the request (useful for object metadata).
445      * @return DataObject
446      */
447     public function uploadObject($name, $data, array $headers = array())
448     {
449         $entityBody = EntityBody::factory($data);
450 
451         $url = clone $this->getUrl();
452         $url->addPath($name);
453 
454         // @todo for new major release: Return response rather than populated DataObject
455 
456         $response = $this->getClient()->put($url, $headers, $entityBody)->send();
457 
458         return $this->dataObject()
459             ->populateFromResponse($response)
460             ->setName($name)
461             ->setContent($entityBody);
462     }
463 
464     /**
465      * Upload an array of objects for upload. This method optimizes the upload procedure by batching requests for
466      * faster execution. This is a very useful procedure when you just have a bunch of unremarkable files to be
467      * uploaded quickly. Each file must be under 5GB.
468      *
469      * @param array $files   With the following array structure:
470      *                       `name' Name that the file will be saved as in your container. Required.
471      *                       `path' Path to an existing file, OR
472      *                       `body' Either a string or stream representation of the file contents to be uploaded.
473      * @param array $headers Optional headers that will be sent with the request (useful for object metadata).
474      *
475      * @throws \OpenCloud\Common\Exceptions\InvalidArgumentError
476      * @return \Guzzle\Http\Message\Response
477      */
478     public function uploadObjects(array $files, array $commonHeaders = array())
479     {
480         $requests = $entities = array();
481 
482         foreach ($files as $entity) {
483             if (empty($entity['name'])) {
484                 throw new Exceptions\InvalidArgumentError('You must provide a name.');
485             }
486 
487             if (!empty($entity['path']) && file_exists($entity['path'])) {
488                 $body = fopen($entity['path'], 'r+');
489             } elseif (!empty($entity['body'])) {
490                 $body = $entity['body'];
491             } else {
492                 throw new Exceptions\InvalidArgumentError('You must provide either a readable path or a body');
493             }
494 
495             $entityBody = $entities[] = EntityBody::factory($body);
496 
497             // @codeCoverageIgnoreStart
498             if ($entityBody->getContentLength() >= 5 * Size::GB) {
499                 throw new Exceptions\InvalidArgumentError(
500                     'For multiple uploads, you cannot upload more than 5GB per '
501                     . ' file. Use the UploadBuilder for larger files.'
502                 );
503             }
504             // @codeCoverageIgnoreEnd
505 
506             // Allow custom headers and common
507             $headers = (isset($entity['headers'])) ? $entity['headers'] : $commonHeaders;
508 
509             $url = clone $this->getUrl();
510             $url->addPath($entity['name']);
511 
512             $requests[] = $this->getClient()->put($url, $headers, $entityBody);
513         }
514 
515         $responses = $this->getClient()->send($requests);
516 
517         foreach ($entities as $entity) {
518             $entity->close();
519         }
520 
521         return $responses;
522     }
523 
524     /**
525      * When uploading large files (+5GB), you need to upload the file as chunks using multibyte transfer. This method
526      * sets up the transfer, and in order to execute the transfer, you need to call upload() on the returned object.
527      *
528      * @param array Options
529      * @see \OpenCloud\ObjectStore\Upload\UploadBuilder::setOptions for a list of accepted options.
530      * @throws \OpenCloud\Common\Exceptions\InvalidArgumentError
531      * @return mixed
532      */
533     public function setupObjectTransfer(array $options = array())
534     {
535         // Name is required
536         if (empty($options['name'])) {
537             throw new Exceptions\InvalidArgumentError('You must provide a name.');
538         }
539 
540         // As is some form of entity body
541         if (!empty($options['path']) && file_exists($options['path'])) {
542             $body = fopen($options['path'], 'r+');
543         } elseif (!empty($options['body'])) {
544             $body = $options['body'];
545         } else {
546             throw new Exceptions\InvalidArgumentError('You must provide either a readable path or a body');
547         }
548 
549         // Build upload
550         $transfer = TransferBuilder::newInstance()
551             ->setOption('objectName', $options['name'])
552             ->setEntityBody(EntityBody::factory($body))
553             ->setContainer($this);
554 
555         // Add extra options
556         if (!empty($options['metadata'])) {
557             $transfer->setOption('metadata', $options['metadata']);
558         }
559         if (!empty($options['partSize'])) {
560             $transfer->setOption('partSize', $options['partSize']);
561         }
562         if (!empty($options['concurrency'])) {
563             $transfer->setOption('concurrency', $options['concurrency']);
564         }
565         if (!empty($options['progress'])) {
566             $transfer->setOption('progress', $options['progress']);
567         }
568 
569         return $transfer->build();
570     }
571 
572     /**
573      * Upload the contents of a local directory to a remote container, effectively syncing them.
574      *
575      * @param $path The local path to the directory.
576      */
577     public function uploadDirectory($path)
578     {
579         $sync = DirectorySync::factory($path, $this);
580         $sync->execute();
581     }
582 
583     public function isCdnEnabled()
584     {
585         return ($this->cdn instanceof CDNContainer) && $this->cdn->isCdnEnabled();
586     }
587 }
588 
API documentation generated by ApiGen