Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to estimate free space when using object storage #33221

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions lib/private/Files/ObjectStore/Azure.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
use MicrosoftAzure\Storage\Blob\Models\CreateBlockBlobOptions;
use MicrosoftAzure\Storage\Common\Exceptions\ServiceException;
use OCP\Files\FileInfo;
use OCP\Files\ObjectStore\IObjectStore;

class Azure implements IObjectStore {
Expand Down Expand Up @@ -133,4 +134,17 @@ public function objectExists($urn) {
public function copyObject($from, $to) {
$this->getBlobClient()->copyBlob($this->containerName, $to, $this->containerName, $from);
}

public function bytesUsed(): int {
// The only way to get the bytes used is by listing every object
// in the blob and sum their size
return FileInfo::SPACE_UNKNOWN;
}

public function bytesQuota(): int {
// Azure doesn't have a way to set a quota on a specific blob container,
// only on a storage account
// https://learn.microsoft.com/en-us/answers/questions/627442/blob-general-v2-container-quota.html
return FileInfo::SPACE_UNLIMITED;
}
}
5 changes: 3 additions & 2 deletions lib/private/Files/ObjectStore/HomeObjectStoreStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@

use OC\User\User;
use OCP\IUser;
use OCP\Files\IHomeStorage;

class HomeObjectStoreStorage extends ObjectStoreStorage implements \OCP\Files\IHomeStorage {
class HomeObjectStoreStorage extends ObjectStoreStorage implements IHomeStorage {
/**
* The home user storage requires a user object to create a unique storage id
* @param array $params
Expand Down Expand Up @@ -60,7 +61,7 @@ public function getOwner($path) {

/**
* @param string $path, optional
* @return \OC\User\User
* @return User
*/
public function getUser($path = null): IUser {
return $this->user;
Expand Down
68 changes: 68 additions & 0 deletions lib/private/Files/ObjectStore/ObjectStoreStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload;
use OCP\Files\Storage\IChunkedFileWrite;
use OCP\Files\Storage\IStorage;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Server;

class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFileWrite {
use CopyDirectory;
Expand Down Expand Up @@ -738,4 +742,68 @@ public function cancelChunkedWrite(string $targetPath, string $writeToken): void
$urn = $this->getURN($cacheEntry->getId());
$this->objectStore->abortMultipartUpload($urn, $writeToken);
}

/**
* In case the admin has overwritten the global instance quota,
* default to the quota minus the used object storage size known by Nextcloud
* instead of unknown
*
* @param $path
*/
public function free_space($path) {
$cacheFactory = Server::get(ICacheFactory::class);
$memcache = $cacheFactory->createLocal('object_storage_info');
$cacheKey = 'object_store_used_size';
$size = $memcache->get($cacheKey);
if ($size) {
return $size;
}

// Ask for the object store backend
$quota = $this->objectStore->bytesQuota();

// If not given, by the object store backend, check if we have an override
if ($quota < 0) {
/** @var IConfig $config */
$config = Server::get(IConfig::class);
$quota = (int) $config->getAppValue('core', 'quota_instance_global', '0');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably makes sense to move this option under the rest of the object store options, since it is only applicable for object stores

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean the objectstore config.php entries? Makes sense indeed, will do.

}

// If there's a quota in place
if ($quota > 0) {
// The used size, directly given by the object store backend
$size = $this->objectStore->bytesUsed();
if ($size < 0) {
// Otherwise we just make the sum of all of the storages sizes currently in Nextcloud
// Note: This will not take into account object versioning handled
// on the object store backend's side such as when using files_versions_s3
$size = $this->getUsedObjectStorageSizeInNextcloud();
}
$freeSpace = $quota - $size;
$memcache->set($cacheKey, $freeSpace, 5 * 60);
return $freeSpace;
}
// Otherwise default to unknown
return parent::free_space($path);
}

/**
* Returns the sum of file sizes from all of the users storages using object storage,
* as well as the global object store storage.
*/
public function getUsedObjectStorageSizeInNextcloud(): ?int {
$qb = Server::get(IDBConnection::class)->getQueryBuilder();
$result = $qb->select($qb->func()->sum('size'))
->from('filecache', 'fc')
->join('fc', 'storages', 's', $qb->expr()->eq('fc.storage', 's.numeric_id'))
->where($qb->expr()->like('s.id', $qb->createNamedParameter('object::%')))
->andWhere($qb->expr()->eq('fc.path', $qb->createNamedParameter('')))
->executeQuery();

$row = $result->fetchOne();
if ($row) {
return (int) $row;
}
return null;
}
}
13 changes: 13 additions & 0 deletions lib/private/Files/ObjectStore/S3.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

use Aws\Result;
use Exception;
use OCP\Files\FileInfo;
use OCP\Files\ObjectStore\IObjectStore;
use OCP\Files\ObjectStore\IObjectStoreMultiPartUpload;

Expand Down Expand Up @@ -110,4 +111,16 @@ public function abortMultipartUpload($urn, $uploadId): void {
'UploadId' => $uploadId
]);
}

public function bytesUsed(): int {
// The only way to get the bytes used is by listing every object
// in the bucket and sum their size
return FileInfo::SPACE_UNKNOWN;
}

public function bytesQuota(): int {
// The quota is not obtainable through the S3 API, only through the
// specific Amazon Service Quotas API
return FileInfo::SPACE_UNLIMITED;
}
}
9 changes: 9 additions & 0 deletions lib/private/Files/ObjectStore/StorageObjectStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
namespace OC\Files\ObjectStore;

use OCP\Files\FileInfo;
use OCP\Files\ObjectStore\IObjectStore;
use OCP\Files\Storage\IStorage;
use function is_resource;
Expand Down Expand Up @@ -91,4 +92,12 @@ public function objectExists($urn) {
public function copyObject($from, $to) {
$this->storage->copy($from, $to);
}

public function bytesUsed(): int {
return FileInfo::SPACE_UNKNOWN;
}

public function bytesQuota(): int {
return FileInfo::SPACE_UNLIMITED;
}
}
9 changes: 9 additions & 0 deletions lib/private/Files/ObjectStore/Swift.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Psr7\Utils;
use Icewind\Streams\RetryWrapper;
use OCP\Files\FileInfo;
use OCP\Files\NotFoundException;
use OCP\Files\ObjectStore\IObjectStore;
use OCP\Files\StorageAuthException;
Expand Down Expand Up @@ -152,4 +153,12 @@ public function copyObject($from, $to) {
'destination' => $this->getContainer()->name . '/' . $to
]);
}

public function bytesUsed(): int {
return $this->getContainer()->bytesUsed;
}

public function bytesQuota(): int {
return $this->getContainer()->getMetadata()['Quota-Bytes'] ?? FileInfo::SPACE_UNLIMITED;
}
}
14 changes: 14 additions & 0 deletions lib/public/Files/ObjectStore/IObjectStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,18 @@ public function objectExists($urn);
* @since 21.0.0
*/
public function copyObject($from, $to);

/**
* Returns the number of bytes used in the object store
*
* @since 26.0.0
*/
public function bytesUsed(): int;

/**
* Returns the eventual quota of the object store, in bytes
*
* @since 26.0.0
*/
public function bytesQuota(): int;
}
9 changes: 9 additions & 0 deletions tests/lib/Files/ObjectStore/FailDeleteObjectStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

namespace Test\Files\ObjectStore;

use OCP\Files\FileInfo;
use OCP\Files\ObjectStore\IObjectStore;

class FailDeleteObjectStore implements IObjectStore {
Expand Down Expand Up @@ -55,4 +56,12 @@ public function objectExists($urn) {
public function copyObject($from, $to) {
$this->objectStore->copyObject($from, $to);
}

public function bytesUsed(): int {
return FileInfo::SPACE_UNKNOWN;
}

public function bytesQuota(): int {
return FileInfo::SPACE_UNLIMITED;
}
}
9 changes: 9 additions & 0 deletions tests/lib/Files/ObjectStore/FailWriteObjectStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

namespace Test\Files\ObjectStore;

use OCP\Files\FileInfo;
use OCP\Files\ObjectStore\IObjectStore;

class FailWriteObjectStore implements IObjectStore {
Expand Down Expand Up @@ -56,4 +57,12 @@ public function objectExists($urn) {
public function copyObject($from, $to) {
$this->objectStore->copyObject($from, $to);
}

public function bytesUsed(): int {
return FileInfo::SPACE_UNKNOWN;
}

public function bytesQuota(): int {
return FileInfo::SPACE_UNLIMITED;
}
}
Loading