* @author Morris Jobke * @author Robin Appelman * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see * */ namespace OC\Files\ObjectStore; use Guzzle\Http\Exception\ClientErrorResponseException; use OCP\Files\ObjectStore\IObjectStore; use OCP\Files\StorageAuthException; use OCP\Files\StorageNotAvailableException; use OpenCloud\Common\Exceptions\EndpointError; use OpenCloud\Common\Service\Catalog; use OpenCloud\Common\Service\CatalogItem; use OpenCloud\ObjectStore\Service; use OpenCloud\OpenStack; use OpenCloud\Rackspace; class Swift implements IObjectStore { /** * @var \OpenCloud\OpenStack */ private $client; /** * @var array */ private $params; /** * @var \OpenCloud\ObjectStore\Service */ private $objectStoreService; /** * @var \OpenCloud\ObjectStore\Resource\Container */ private $container; public function __construct($params) { if (isset($params['bucket'])) { $params['container'] = $params['bucket']; } if (!isset($params['container'])) { $params['container'] = 'owncloud'; } if (!isset($params['autocreate'])) { // should only be true for tests $params['autocreate'] = false; } if (isset($params['apiKey'])) { $this->client = new Rackspace($params['url'], $params); } else { $this->client = new OpenStack($params['url'], $params); } $this->params = $params; } protected function init() { if ($this->container) { return; } try { $this->client->authenticate(); } catch (ClientErrorResponseException $e) { $statusCode = $e->getResponse()->getStatusCode(); if ($statusCode == 412) { throw new StorageAuthException('Precondition failed, verify the keystone url', $e); } else if ($statusCode === 401) { throw new StorageAuthException('Authentication failed, verify the username, password and possibly tenant', $e); } else { throw new StorageAuthException('Unknown error', $e); } } /** @var Catalog $catalog */ $catalog = $this->client->getCatalog(); if (isset($this->params['serviceName'])) { $serviceName = $this->params['serviceName']; } else { $serviceName = Service::DEFAULT_NAME; } if (isset($this->params['urlType'])) { $urlType = $this->params['urlType']; if ($urlType !== 'internalURL' && $urlType !== 'publicURL') { throw new StorageNotAvailableException('Invalid url type'); } } else { $urlType = Service::DEFAULT_URL_TYPE; } $catalogItem = $this->getCatalogForService($catalog, $serviceName); if (!$catalogItem) { $available = implode(', ', $this->getAvailableServiceNames($catalog)); throw new StorageNotAvailableException( "Service $serviceName not found in service catalog, available services: $available" ); } else if (isset($this->params['region'])) { $this->validateRegion($catalogItem, $this->params['region']); } $this->objectStoreService = $this->client->objectStoreService($serviceName, $this->params['region'], $urlType); try { $this->container = $this->objectStoreService->getContainer($this->params['container']); } catch (ClientErrorResponseException $ex) { // if the container does not exist and autocreate is true try to create the container on the fly if (isset($this->params['autocreate']) && $this->params['autocreate'] === true) { $this->container = $this->objectStoreService->createContainer($this->params['container']); } else { throw $ex; } } } /** * @param Catalog $catalog * @param $name * @return null|CatalogItem */ private function getCatalogForService(Catalog $catalog, $name) { foreach ($catalog->getItems() as $item) { /** @var CatalogItem $item */ if ($item->hasType(Service::DEFAULT_TYPE) && $item->hasName($name)) { return $item; } } return null; } private function validateRegion(CatalogItem $item, $region) { $endPoints = $item->getEndpoints(); foreach ($endPoints as $endPoint) { if ($endPoint->region === $region) { return; } } $availableRegions = implode(', ', array_map(function ($endpoint) { return $endpoint->region; }, $endPoints)); throw new StorageNotAvailableException("Invalid region '$region', available regions: $availableRegions"); } private function getAvailableServiceNames(Catalog $catalog) { return array_map(function (CatalogItem $item) { return $item->getName(); }, array_filter($catalog->getItems(), function (CatalogItem $item) { return $item->hasType(Service::DEFAULT_TYPE); })); } /** * @return string the container name where objects are stored */ public function getStorageId() { return $this->params['container']; } /** * @param string $urn the unified resource name used to identify the object * @param resource $stream stream with the data to write * @throws Exception from openstack lib when something goes wrong */ public function writeObject($urn, $stream) { $this->init(); $this->container->uploadObject($urn, $stream); } /** * @param string $urn the unified resource name used to identify the object * @return resource stream with the read data * @throws Exception from openstack lib when something goes wrong */ public function readObject($urn) { $this->init(); $object = $this->container->getObject($urn); // we need to keep a reference to objectContent or // the stream will be closed before we can do anything with it /** @var $objectContent \Guzzle\Http\EntityBody * */ $objectContent = $object->getContent(); $objectContent->rewind(); $stream = $objectContent->getStream(); // save the object content in the context of the stream to prevent it being gc'd until the stream is closed stream_context_set_option($stream, 'swift', 'content', $objectContent); return $stream; } /** * @param string $urn Unified Resource Name * @return void * @throws Exception from openstack lib when something goes wrong */ public function deleteObject($urn) { $this->init(); // see https://github.com/rackspace/php-opencloud/issues/243#issuecomment-30032242 $this->container->dataObject()->setName($urn)->delete(); } public function deleteContainer($recursive = false) { $this->init(); $this->container->delete($recursive); } }