Skip to content

Implement storage quotas - #2565

Merged
enkore merged 3 commits into
borgbackup:masterfrom
enkore:issue/2517
Jun 1, 2017
Merged

Implement storage quotas#2565
enkore merged 3 commits into
borgbackup:masterfrom
enkore:issue/2517

Conversation

@enkore

@enkore enkore commented May 25, 2017

Copy link
Copy Markdown
Contributor

Fixes #2517

I gave #2517 some thought; implementing it in Borg has some advantages:

  • Error messages are more helpful
  • Our pre-commit checks do not capture FS quotas
  • As implemented, the client can always back out of a 100.0 % used quota, which is not possible with (hard) FS quotas

Disadvantages:

  • More difficult to deploy compared to FS quotas

    Perhaps a complementary option to --restrict-to-path that matches the path exactly would simplify handling of repo hosting in general?

  • The storage quota and the actual bound on disk space use have a somewhat loose relationship.

    (In case anyone feels reminded of file systems and filling them up completely... it's the same structures, the same math — an exercise for the reader: design a transactional data store that has a fixed upper bound X, independent of contents, such that using SPACE(X) the store's contents can still be cleaned up :)

TODO: Add tests for quota tracking.

re-acquiring quota data in the process, or
- edit the msgpacked ``hints.N`` file (not recommended and thus not
documented further).

@enkore enkore May 25, 2017

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The trade-offs documented here drastically simplify the implementation. (More than half of this PR is documentation!)

@ThomasWaldmann ThomasWaldmann left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nice feature. :)

a transaction modify the currently used quota:

- A PUT adds the size of the *log entry* to the quota,
i.e. the length of the data plus the 41 byte header.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

maybe do not mention the specific size, would get out of sync if we ever change that.
so just "length of the put command header"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

"if ever"

Comment thread docs/internals/data-structures.rst Outdated

- A PUT adds the size of the *log entry* to the quota,
i.e. the length of the data plus the 41 byte header.
- A DELETE substracts the size of the deleted log entry to the quota,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

subtracts ... from the quota.

- A DELETE substracts the size of the deleted log entry to the quota,
which includes the header.

Thus, PUT and DELETE are symmetric and cancel each other out precisely.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

not sure: "cancel out each other"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Both forms are used

The quota is enforcible only if *all* :ref:`borg_serve` versions
accessible to clients support quotas (see next section). Further, quota is
per repository. Therefore, ensure clients can only access a defined set of repositories
with their quotas set, using ``--restrict-to-path``.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

hmm, iirc --restrict-to-path was only meant to restrict the path prefix ("base dir"), but not meaning that there is only 1 repo there.

so, would a provider pre-create some repos below the base dir and revoke write permissions to the base dir afterwards for the client so the client can not create any further repos (directories)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

See PR comment

More difficult to deploy compared to FS quotas

Perhaps a complementary option to --restrict-to-path that matches the path exactly would simplify handling of repo hosting in general?

Comment thread src/borg/archiver.py Outdated
def parse_storage_quota(storage_quota):
parsed = parse_file_size(storage_quota)
if parsed < parse_file_size('10M'):
raise argparse.ArgumentTypeError('quota is too small (%s), must exceed 10M' % storage_quota)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

"exceed" (from: excessive) sounds strange here. rather "must be at least 10M"?

also: refactor to not repeat '10M'?

Comment thread src/borg/repository.py

def __init__(self, path, create=False, exclusive=False, lock_wait=None, lock=True, append_only=False):
class StorageQuotaExceeded(Error):
"""The storage quota ({}) has been exceeded ({}). Try deleting some archives."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

add this to handle_error in remote.py?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The generic error handling in protocol v2 means that we don't have to do that unless the type of the exception is relevant, i.e. some code expects that exception. For user-facing error messages there is no need.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Actually that wasn't quite right, since the .traceback attributes of Error was not propagated this did not quite work as intended. Fixed below.

@codecov-io

codecov-io commented May 31, 2017

Copy link
Copy Markdown

Codecov Report

Merging #2565 into master will increase coverage by 0.04%.
The diff coverage is 91.11%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2565      +/-   ##
==========================================
+ Coverage   83.58%   83.62%   +0.04%     
==========================================
  Files          22       22              
  Lines        8052     8166     +114     
  Branches     1375     1390      +15     
==========================================
+ Hits         6730     6829      +99     
- Misses        941      956      +15     
  Partials      381      381
Impacted Files Coverage Δ
src/borg/archiver.py 83.01% <100%> (+0.12%) ⬆️
src/borg/remote.py 77.02% <75%> (-0.02%) ⬇️
src/borg/repository.py 87.11% <90.62%> (+0.09%) ⬆️
src/borg/logger.py 70.68% <0%> (-2.59%) ⬇️
src/borg/crypto/key.py 88.8% <0%> (-1.2%) ⬇️
src/borg/cache.py 87.07% <0%> (+0.33%) ⬆️
src/borg/crypto/file_integrity.py 96.87% <0%> (+2.43%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0268917...bcf4b44. Read the comment docs.

@ThomasWaldmann ThomasWaldmann left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm, 2 minor things.

Comment thread src/borg/repository.py
if self.storage_quota:
config.set('repository', 'storage_quota', str(self.storage_quota))
else:
config.set('repository', 'storage_quota', '0')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

set(..., str(self.storage_quota if self.storage_quota else 0))

Comment thread src/borg/repository.py
b'segments': self.segments,
b'compact': self.compact}
b'compact': self.compact,
b'storage_quota_use': self.storage_quota_use, }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nitpick: that way of using a trailing comma does not minimize the diff if a new element is added afterwards.

should be rather:

{a: 1,
 b: 2,
}

@enkore
enkore merged commit 23d591c into borgbackup:master Jun 1, 2017
@enkore
enkore deleted the issue/2517 branch June 1, 2017 12:39

If no quota data is stored in the hints file, Borg assumes zero quota is used.
Thus, if a repository with an enabled quota is written to with an older version
that does not understand quotas, then the quota usage will be erased.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does not this allow users to circumvent this feature? Say a server admins wants to enforce a 50 GB quota and puts this limit there. Then the user can just access the repo with an old borg client and voila… the quota is gone.
That's how I understand that sentence.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

see section headline - it is referring to servers here, not clients.

but maybe it makes sense to actually say that in that sentence, just to avoid misunderstandings.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optional space limit

4 participants