-
Notifications
You must be signed in to change notification settings - Fork 273
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
feat(backup): make remote immutable #6928
Conversation
caf37eb
to
3736909
Compare
3736909
to
7c9fd53
Compare
23b19ab
to
9776785
Compare
87019e1
to
6d83b84
Compare
|
||
## Making a file immutable | ||
|
||
when marking a file or a folder immutable, it create an alias file in `.immutable/<hash of full path>.<file|dir>.<extension>` containing the path to the folder/file and make this file also immutable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO we should add a directory level to prevent issues when lots of entries: hash[0]/hash[1]
.
It's pretty easy and can help if there are a lot of entries.
|
||
## Making a file immutable | ||
|
||
when marking a file or a folder immutable, it create an alias file in `.immutable/<hash of full path>.<file|dir>.<extension>` containing the path to the folder/file and make this file also immutable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The path format used for hashing must be properly documented: /
vs \
, relative vs absolute, leading or trailing slash, etc.
|
||
## Making a file immutable | ||
|
||
when marking a file or a folder immutable, it create an alias file in `.immutable/<hash of full path>.<file|dir>.<extension>` containing the path to the folder/file and make this file also immutable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm ill-at-ease with the fact that the content of these files will not be encrypted even if the rest of remote is.
What bothers me is the inconsistency.
Possible solutions:
- use symlinks instead
- put these files in a specific directory which will only contain unencrypted files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- symlinks can't be made immutable
chattr: Operation not supported while reading flags on source
- I think we don't really need to have xo use the hidden index folder
|
||
## Configuring | ||
|
||
this package uses app-conf to store its config. The application name id `immutable-backup` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO, the application name should contain xo
, example: xo-immutable-manager
40e38e6
to
052b93c
Compare
the merge process on vhd file rename files, it is correctly detected as a change event by chokidar, thus, not making he merged vhd immutable anew |
} | ||
await fs.writeFile(settingPath, | ||
JSON.stringify({ | ||
since: +new Date(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since: +new Date(), | |
since: Date.now(), |
13f4af5
to
48dafaa
Compare
async function handleExistingFile(root, immutabilityIndexPath, path) { | ||
try { | ||
// a vhd block directory is completly immutable | ||
if (path.split('/').length === 8) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should watch against dirname = .vhd
// with awaitWriteFinish we have complete files here | ||
// we can make them immutable | ||
|
||
if (path.split('/').length === 8) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should watch against dirname = .vhd
if (path.split('/').length === 8) { | ||
// watching a vhd block | ||
// wait for header/footer and BAT before making this immutable recursively | ||
const splitted = path.split('/') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
path.split(nodePath.separator)
const vmUuid = splitted[1] | ||
const vdiUuid = splitted[4] | ||
const uniqPath = `${vmUuid}/${vdiUuid}` | ||
const { existing } = pendingVhds.get(uniqPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const { existing } = pendingVhds.get(uniqPath) ?? {}
} else { | ||
// already two of the key files,and we got the last one | ||
if (existing === 2) { | ||
Directory.makeImmutable(join(root, dirname(path))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete pendingvhds entry after
await File.makeImmutable(testPath, immutabilityCachePath) | ||
try { | ||
await fs.writeFile(testPath, `test immut change 2 ${new Date()}`) | ||
await fs.unlink(testPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unlink
is not tested due to rejection from writeFile
.
} catch (error) { | ||
if (error.code !== 'EPERM') { | ||
throw error | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use assert.rejects
.
for await (const { index, target } of listOlderTargets(immutabilityCachePath, immutabilityDuration)) { | ||
await Directory.liftImmutability(target, immutabilityCachePath) | ||
await fs.unlink(index) | ||
if (target.match(/xo-vm-backups\/[^/]+\/[^/]+\.json$/)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extract this method
const { remotes } = await loadConfig(APP_NAME, { | ||
appDir: APP_DIR, | ||
ignoreUnknownFormats: true, | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extract in _config.mjs
.
}) | ||
|
||
for (const [remoteId, {indexPath,immutabilityDuration}] of Object.entries(remotes)) { | ||
const basePath = indexPath ?? process.env.XDG_DATA_HOME ?? join('~', '.local', 'share') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extract in _indexPath.mjs
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put the logic in the _loadConfig.mjs , my intention is that the config is either rejected or ready to use
await fs.unlink(index) | ||
if (target.match(/xo-vm-backups\/[^/]+\/[^/]+\.json$/)) { | ||
// snipe vm metadata cache to force XO to update it | ||
await fs.unlink(join(dirname(target), 'cache.json.gz')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extract in _deleteCache.mjs
.
@xen-orchestra/fs/src/abstract.js
Outdated
get immutability(){ | ||
return this.#immutability | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO, this should not be in fs
, move into async RemoteAdapter.getImmutability()
and it can have a cache.
@xen-orchestra/fs/src/local.js
Outdated
return await this.#addSyncStackTrace(retry, () => fs.readFile(filePath, options), this.#retriesOnEagain) | ||
const isPath = typeof file === 'string' | ||
return await this.#addSyncStackTrace(retry, () => { | ||
if(isPath){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prettier did not passed on this file.
let fd | ||
try { | ||
// this will trigger e EPERM error if the file is immutable | ||
fd = (await handler.openFile(path, 'r+')).fd |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need to do that, you can pass flags to readFile
.
19e54c2
to
dc3462f
Compare
734136e
to
8d16242
Compare
0d9339b
to
9494f64
Compare
4147fc2
to
d0c7a8e
Compare
d0c7a8e
to
fff92a5
Compare
Description
in the doc folder of the new package
one watcher per vm should not overload the remote, even with a few thousands VMs saved ( default inotify allow 8192 watcher )
review by commit
Checklist
Fixes #007
,See xoa-support#42
,See https://...
)Introduced by
CHANGELOG.unreleased.md