Compare commits

...

2 Commits

Author SHA1 Message Date
Florent Beauchamp
0957b5b6b1 doc 2022-11-22 09:23:00 +01:00
Florent Beauchamp
33b758d0b2 feat(vhd-cli): implement deeper checks for vhd 2022-11-22 09:09:47 +01:00
10 changed files with 132 additions and 16 deletions

View File

@@ -91,6 +91,7 @@ export default class RemoteHandlerAbstract {
const sharedLimit = limitConcurrency(options.maxParallelOperations ?? DEFAULT_MAX_PARALLEL_OPERATIONS)
this.closeFile = sharedLimit(this.closeFile)
this.copy = sharedLimit(this.copy)
this.exists = sharedLimit(this.exists)
this.getInfo = sharedLimit(this.getInfo)
this.getSize = sharedLimit(this.getSize)
this.list = sharedLimit(this.list)
@@ -314,6 +315,14 @@ export default class RemoteHandlerAbstract {
await this._rmtree(normalizePath(dir))
}
async _exists(file){
throw new Error('not implemented')
}
async exists(file){
return this._exists(normalizePath(file))
}
// Asks the handler to sync the state of the effective remote with its'
// metadata
//

View File

@@ -198,4 +198,9 @@ export default class LocalHandler extends RemoteHandlerAbstract {
_writeFile(file, data, { flags }) {
return this._addSyncStackTrace(fs.writeFile, this._getFilePath(file), data, { flag: flags })
}
async _exists(file){
const exists = await fs.pathExists(this._getFilePath(file))
return exists
}
}

View File

@@ -537,4 +537,17 @@ export default class S3Handler extends RemoteHandlerAbstract {
useVhdDirectory() {
return true
}
async _exists(file){
try{
await this._s3.send(new HeadObjectCommand(this._createParams(file)))
return true
}catch(error){
// normalize this error code
if (error.name === 'NoSuchKey') {
return false
}
throw error
}
}
}

View File

@@ -30,7 +30,8 @@
> Keep this list alphabetically ordered to avoid merge conflicts
<!--packages-start-->
- vhd-lib minor
- vhd-cli major
- @xen-orchestra/backups-cli major
- @xen-orchestra/log minor
- xo-cli patch

View File

@@ -0,0 +1,44 @@
```
> vhd-cli
Usage:
vhd-cli check <path>
Detects issues with VHD. path is relative to the remote url
Options:
--remote <url> the remote url, / if not specified
--bat check if the blocks listed in the bat are present on the file system (vhddirectory only)
--blocks read all the blocks of the vhd
--chain instantiate a vhd with all its parent and check it
vhd-cli compare <sourceRemoteUrl> <source VHD> <destionationRemoteUrl> <destination>
Check if two VHD contains the same data
vhd-cli copy <sourceRemoteUrl> <source VHD> <destionationRemoteUrl> <destination> --directory
Copy a Vhd.
Options:
--directory : the destination vhd will be created as a vhd directory
vhd-cli info <path>
Read informations of a VHD, path is relative to /
vhd-cli merge <child VHD> <parent VHD>
Merge child in parent, paths are relatives to /
vhd-cli raw <path>
extract the raw content of a VHD, path is relative to /
vhd-cli repl
create a REPL environnement in the local folder
```

View File

@@ -1,30 +1,51 @@
'use strict'
const { VhdFile, checkVhdChain } = require('vhd-lib')
const { openVhd, VhdSynthetic } = require('vhd-lib')
const getopts = require('getopts')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { resolve } = require('path')
const { Disposable } = require('promise-toolbox')
const checkVhd = (handler, path) => new VhdFile(handler, path).readHeaderAndFooter()
module.exports = async function check(rawArgs) {
const { chain, _: args } = getopts(rawArgs, {
boolean: ['chain'],
if (args.length < 2 || args.some(_ => _ === '-h' || _ === '--help')) {
return `Usage: ${this.command} <path> [--remote <remoteURL>] [--chain] [--bat] [--blocks] `
}
const { chain, bat, blocks, remote, _: args } = getopts(rawArgs, {
boolean: ['chain', 'bat', 'blocks'],
default: {
chain: false,
bat: false,
blocks: false
},
})
const check = chain ? checkVhdChain : checkVhd
await Disposable.use(getSyncedHandler({ url: 'file:///' }), async handler => {
for (const vhd of args) {
try {
await check(handler, resolve(vhd))
console.log('ok:', vhd)
} catch (error) {
console.error('nok:', vhd, error)
const vhdPath = args[0]
await Disposable.factory( async function * open(remote, vhdPath) {
const handler = yield getSyncedHandler({url : remote ?? 'file:///'})
const vhd = chain? yield VhdSynthetic.fromVhdChain(handler, vhdPath) : yield openVhd(handler, vhdPath)
await vhd.readBlockAllocationTable()
if(bat){
const nBlocks = vhd.header.maxTableEntries
let nbErrors = 0
for (let blockId = 0; blockId < nBlocks; ++blockId) {
if(!vhd.containsBlock(blockId)){
continue
}
const ok = await vhd.checkBlock(blockId)
if(!ok){
console.warn(`block ${blockId} is invalid`)
nbErrors ++
}
}
console.log('BAT check done ', nbErrors === 0 ? 'OK': `${nbErrors} block(s) faileds`)
}
})
if(blocks){
for await(const _ of vhd.blocks()){
}
console.log('Blocks check done')
}
})(remote, vhdPath)
}

View File

@@ -394,4 +394,14 @@ exports.VhdAbstract = class VhdAbstract {
assert.strictEqual(copied, length, 'invalid length')
return copied
}
_checkBlock() {
throw new Error('not implemented')
}
// check if a block is ok without reading it
// there still can be error when reading the block later (if it's deleted, if right are incorrects,...)
async checkBlock(blockId) {
return this._checkBlock(blockId)
}
}

View File

@@ -317,4 +317,9 @@ exports.VhdDirectory = class VhdDirectory extends VhdAbstract {
})
this.#compressor = getCompressor(chunkFilters[0])
}
async _checkBlock(blockId){
const path = this._getFullBlockPath(blockId)
return this._handler.exists(path)
}
}

View File

@@ -466,4 +466,8 @@ exports.VhdFile = class VhdFile extends VhdAbstract {
async getSize() {
return await this._handler.getSize(this._path)
}
_checkBlock(blockId){
return true
}
}

View File

@@ -113,6 +113,10 @@ const VhdSynthetic = class VhdSynthetic extends VhdAbstract {
return vhd?._getFullBlockPath(blockId)
}
_checkBlock(blockId) {
const vhd = this.#getVhdWithBlock(blockId)
return vhd?._checkBlock(blockId) ?? false
}
// return true if all the vhds ar an instance of cls
checkVhdsClass(cls) {
return this.#vhds.every(vhd => vhd instanceof cls)