Compare commits
2 Commits
feat_sizes
...
lite/neste
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
677a9c958c | ||
|
|
2978ad1486 |
@@ -669,7 +669,7 @@ class RemoteAdapter {
|
||||
const handler = this._handler
|
||||
if (this.useVhdDirectory()) {
|
||||
const dataPath = `${dirname(path)}/data/${uuidv4()}.vhd`
|
||||
const sizes = await createVhdDirectoryFromStream(handler, dataPath, input, {
|
||||
const size = await createVhdDirectoryFromStream(handler, dataPath, input, {
|
||||
concurrency: writeBlockConcurrency,
|
||||
compression: this.#getCompressionType(),
|
||||
async validator() {
|
||||
@@ -678,14 +678,9 @@ class RemoteAdapter {
|
||||
},
|
||||
})
|
||||
await VhdAbstract.createAlias(handler, path, dataPath)
|
||||
return sizes
|
||||
return size
|
||||
} else {
|
||||
const size = this.outputStream(path, input, { checksum, validator })
|
||||
return {
|
||||
compressedSize: size,
|
||||
sourceSize: size,
|
||||
writtenSize: size,
|
||||
}
|
||||
return this.outputStream(path, input, { checksum, validator })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
const sum = require('lodash/sum')
|
||||
const UUID = require('uuid')
|
||||
const { asyncMap } = require('@xen-orchestra/async-map')
|
||||
const { Constants, openVhd, VhdAbstract } = require('vhd-lib')
|
||||
const { Constants, openVhd, VhdAbstract, VhdFile } = require('vhd-lib')
|
||||
const { isVhdAlias, resolveVhdAlias } = require('vhd-lib/aliases')
|
||||
const { dirname, resolve } = require('path')
|
||||
const { DISK_TYPES } = Constants
|
||||
@@ -15,14 +15,24 @@ const { Task } = require('./Task.js')
|
||||
const { Disposable } = require('promise-toolbox')
|
||||
const handlerPath = require('@xen-orchestra/fs/path')
|
||||
|
||||
// checking the size of a vhd directory is costly
|
||||
// 1 Http Query per 1000 blocks
|
||||
// we only check size of all the vhd are VhdFiles
|
||||
function shouldComputeVhdsSize(handler, vhds) {
|
||||
if (handler.isEncrypted) {
|
||||
return false
|
||||
}
|
||||
return vhds.every(vhd => vhd instanceof VhdFile)
|
||||
}
|
||||
|
||||
const computeVhdsSize = (handler, vhdPaths) =>
|
||||
Disposable.use(
|
||||
vhdPaths.map(vhdPath => openVhd(handler, vhdPath)),
|
||||
async vhds => {
|
||||
await Promise.all(vhds.map(vhd => vhd.readBlockAllocationTable()))
|
||||
// get file size for vhdfile, computed size from bat for vhd directory
|
||||
const sizes = await asyncMap(vhds, vhd => vhd.streamSize())
|
||||
return sum(sizes)
|
||||
if (shouldComputeVhdsSize(handler, vhds)) {
|
||||
const sizes = await asyncMap(vhds, vhd => vhd.getSize())
|
||||
return sum(sizes)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -524,6 +534,11 @@ exports.cleanVm = async function cleanVm(
|
||||
const linkedVhds = Object.keys(vhds).map(key => resolve('/', vmDir, vhds[key]))
|
||||
fileSystemSize = await computeVhdsSize(handler, linkedVhds)
|
||||
|
||||
// the size is not computed in some cases (e.g. VhdDirectory)
|
||||
if (fileSystemSize === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
// don't warn if the size has changed after a merge
|
||||
if (!merged && fileSystemSize !== size) {
|
||||
// FIXME: figure out why it occurs so often and, once fixed, log the real problems with `logWarn`
|
||||
@@ -541,8 +556,6 @@ exports.cleanVm = async function cleanVm(
|
||||
|
||||
// systematically update size after a merge
|
||||
if ((merged || fixMetadata) && size !== fileSystemSize) {
|
||||
// @todo add a cumulatedTransferSize property ?
|
||||
// @todo update writtenSize, compressedSize
|
||||
metadata.size = fileSystemSize
|
||||
mustRegenerateCache = true
|
||||
try {
|
||||
|
||||
@@ -205,7 +205,7 @@ class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrementalWrite
|
||||
// TODO remove when this has been done before the export
|
||||
await checkVhd(handler, parentPath)
|
||||
}
|
||||
// @todo : sum per property
|
||||
|
||||
transferSize += await adapter.writeVhd(path, deltaExport.streams[`${id}.vhd`], {
|
||||
// no checksum for VHDs, because they will be invalidated by
|
||||
// merges and chainings
|
||||
@@ -232,7 +232,7 @@ class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrementalWrite
|
||||
|
||||
return { size: transferSize }
|
||||
})
|
||||
metadataContent.size = size // @todo: transferSize
|
||||
metadataContent.size = size
|
||||
this._metadataFileName = await adapter.writeVmBackupMetadata(vm.uuid, metadataContent)
|
||||
|
||||
// TODO: run cleanup?
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# ChangeLog
|
||||
|
||||
## **0.2.0**
|
||||
## **next**
|
||||
|
||||
## **0.1.1** (2023-07-03)
|
||||
|
||||
- Invalidate sessionId token after logout (PR [#6480](https://github.com/vatesfr/xen-orchestra/pull/6480))
|
||||
- Settings page (PR [#6418](https://github.com/vatesfr/xen-orchestra/pull/6418))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@xen-orchestra/lite",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"scripts": {
|
||||
"dev": "GIT_HEAD=$(git rev-parse HEAD) vite",
|
||||
"build": "run-p type-check build-only",
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<form
|
||||
<Teleport :disabled="isNested" to="body">
|
||||
<component
|
||||
:is="isNested ? 'div' : 'form'"
|
||||
:class="className"
|
||||
class="ui-modal"
|
||||
v-bind="$attrs"
|
||||
@click.self="emit('close')"
|
||||
@click.self="!isNested && emit('close')"
|
||||
>
|
||||
<div class="container">
|
||||
<div :class="{ nested: isNested }" class="container">
|
||||
<span v-if="onClose" class="close-icon" @click="emit('close')">
|
||||
<UiIcon :icon="faXmark" />
|
||||
</span>
|
||||
@@ -24,22 +25,23 @@
|
||||
<div v-if="$slots.default" class="content">
|
||||
<slot />
|
||||
</div>
|
||||
<UiButtonGroup :color="color">
|
||||
<UiButtonGroup v-if="!isNested" :color="color">
|
||||
<slot name="buttons" />
|
||||
</UiButtonGroup>
|
||||
</div>
|
||||
</form>
|
||||
</component>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UiButtonGroup from "@/components/ui/UiButtonGroup.vue";
|
||||
import UiIcon from "@/components/ui/icon/UiIcon.vue";
|
||||
import UiButtonGroup from "@/components/ui/UiButtonGroup.vue";
|
||||
import UiTitle from "@/components/ui/UiTitle.vue";
|
||||
import { IK_MODAL_NESTED } from "@/types/injection-keys";
|
||||
import type { IconDefinition } from "@fortawesome/fontawesome-common-types";
|
||||
import { faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useMagicKeys, whenever } from "@vueuse/core";
|
||||
import { computed } from "vue";
|
||||
import { computed, inject, provide } from "vue";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -54,27 +56,39 @@ const emit = defineEmits<{
|
||||
(event: "close"): void;
|
||||
}>();
|
||||
|
||||
const isNested = inject(IK_MODAL_NESTED, false);
|
||||
provide(IK_MODAL_NESTED, true);
|
||||
|
||||
const { escape } = useMagicKeys();
|
||||
whenever(escape, () => emit("close"));
|
||||
|
||||
const className = computed(() => {
|
||||
return [`color-${props.color}`, { "has-icon": props.icon !== undefined }];
|
||||
return [
|
||||
`color-${props.color}`,
|
||||
{
|
||||
"has-icon": props.icon !== undefined,
|
||||
nested: isNested,
|
||||
},
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.ui-modal {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #00000080;
|
||||
|
||||
&:not(.nested) {
|
||||
background-color: #00000080;
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.color-success {
|
||||
@@ -103,11 +117,23 @@ const className = computed(() => {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-width: 40rem;
|
||||
padding: 4.2rem;
|
||||
text-align: center;
|
||||
border-radius: 1rem;
|
||||
background-color: var(--modal-background-color);
|
||||
box-shadow: var(--shadow-400);
|
||||
margin: 1rem 2rem;
|
||||
|
||||
&.nested {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&:not(.nested) {
|
||||
box-shadow: var(--shadow-400);
|
||||
padding: 4.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.container > div:last-child {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<ComponentStory
|
||||
v-slot="{ properties, settings }"
|
||||
:params="[
|
||||
colorProp(),
|
||||
iconProp(),
|
||||
@@ -11,17 +12,31 @@
|
||||
slot('buttons').help('Meant to receive UiButton components'),
|
||||
setting('title').preset('Modal Title').widget(),
|
||||
setting('subtitle').preset('Modal Subtitle').widget(),
|
||||
setting('nested_modal').widget(boolean()),
|
||||
]"
|
||||
v-slot="{ properties, settings }"
|
||||
>
|
||||
<UiButton type="button" @click="open">Open Modal</UiButton>
|
||||
|
||||
<UiModal v-bind="properties" v-if="isOpen">
|
||||
<UiModal v-if="isOpen" v-bind="properties">
|
||||
<template #title>{{ settings.title }}</template>
|
||||
<template #subtitle>{{ settings.subtitle }}</template>
|
||||
<template #buttons>
|
||||
<UiButton @click="close">Discard</UiButton>
|
||||
</template>
|
||||
<template v-if="settings.nested_modal">
|
||||
<UiModal :icon="faWarning" color="warning">
|
||||
<template #title>Warning</template>
|
||||
<template #subtitle> This is a warning "nested" modal.</template>
|
||||
<UiModal :icon="faInfoCircle" color="info">
|
||||
<template #title>Info</template>
|
||||
<template #subtitle> This is an info "nested" modal.</template>
|
||||
</UiModal>
|
||||
</UiModal>
|
||||
<UiModal :icon="faCheck" color="success">
|
||||
<template #title>Success</template>
|
||||
<template #subtitle> This is a success "deep nested" modal.</template>
|
||||
</UiModal>
|
||||
</template>
|
||||
</UiModal>
|
||||
</ComponentStory>
|
||||
</template>
|
||||
@@ -38,6 +53,12 @@ import {
|
||||
setting,
|
||||
slot,
|
||||
} from "@/libs/story/story-param";
|
||||
import {
|
||||
faCheck,
|
||||
faInfoCircle,
|
||||
faWarning,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { boolean } from "@/libs/story/story-widget";
|
||||
|
||||
const { open, close, isOpen } = useModal();
|
||||
</script>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { InjectionKey } from "vue";
|
||||
|
||||
export const IK_MENU_TELEPORTED = Symbol() as InjectionKey<boolean>;
|
||||
export const IK_MODAL_NESTED = Symbol() as InjectionKey<boolean>;
|
||||
|
||||
@@ -168,12 +168,7 @@ exports.VhdDirectory = class VhdDirectory extends VhdAbstract {
|
||||
// in case of VhdDirectory, we want to create the file if it does not exists
|
||||
const flags = this._opts?.flags === 'r+' ? 'w' : this._opts?.flags
|
||||
const compressed = await this.#compressor.compress(buffer)
|
||||
const writtenSize = await this._handler.outputFile(this.#getChunkPath(partName), compressed, { flags })
|
||||
return {
|
||||
size: buffer.length,
|
||||
compressedSize: compressed.length,
|
||||
writtenSize,
|
||||
}
|
||||
return this._handler.outputFile(this.#getChunkPath(partName), compressed, { flags })
|
||||
}
|
||||
|
||||
// put block in subdirectories to limit impact when doing directory listing
|
||||
@@ -233,7 +228,7 @@ exports.VhdDirectory = class VhdDirectory extends VhdAbstract {
|
||||
footer.checksum = checksumStruct(rawFooter, fuFooter)
|
||||
debug(`Write footer (checksum=${footer.checksum}). (data=${rawFooter.toString('hex')})`)
|
||||
|
||||
return await this._writeChunk('footer', rawFooter)
|
||||
await this._writeChunk('footer', rawFooter)
|
||||
}
|
||||
|
||||
async writeHeader() {
|
||||
@@ -241,9 +236,8 @@ exports.VhdDirectory = class VhdDirectory extends VhdAbstract {
|
||||
const rawHeader = fuHeader.pack(header)
|
||||
header.checksum = checksumStruct(rawHeader, fuHeader)
|
||||
debug(`Write header (checksum=${header.checksum}). (data=${rawHeader.toString('hex')})`)
|
||||
const sizes = await this._writeChunk('header', rawHeader)
|
||||
await this._writeChunk('header', rawHeader)
|
||||
await this.#writeChunkFilters()
|
||||
return sizes
|
||||
}
|
||||
|
||||
writeBlockAllocationTable() {
|
||||
@@ -291,9 +285,8 @@ exports.VhdDirectory = class VhdDirectory extends VhdAbstract {
|
||||
}
|
||||
|
||||
async writeEntireBlock(block) {
|
||||
const sizes = await this._writeChunk(this.#getBlockPath(block.id), block.buffer)
|
||||
await this._writeChunk(this.#getBlockPath(block.id), block.buffer)
|
||||
setBitmap(this.#blockTable, block.id)
|
||||
return sizes
|
||||
}
|
||||
|
||||
async _readParentLocatorData(id) {
|
||||
@@ -301,9 +294,8 @@ exports.VhdDirectory = class VhdDirectory extends VhdAbstract {
|
||||
}
|
||||
|
||||
async _writeParentLocatorData(id, data) {
|
||||
const sizes = await this._writeChunk('parentLocatorEntry' + id, data)
|
||||
await this._writeChunk('parentLocatorEntry' + id, data)
|
||||
this.header.parentLocatorEntry[id].platformDataOffset = 0
|
||||
return sizes
|
||||
}
|
||||
|
||||
async #writeChunkFilters() {
|
||||
|
||||
@@ -463,7 +463,7 @@ exports.VhdFile = class VhdFile extends VhdAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
async streamSize() {
|
||||
async getSize() {
|
||||
return await this._handler.getSize(this._path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,6 @@ const { warn } = createLogger('vhd-lib:createVhdDirectoryFromStream')
|
||||
|
||||
const buildVhd = Disposable.wrap(async function* (handler, path, inputStream, { concurrency, compression }) {
|
||||
const vhd = yield VhdDirectory.create(handler, path, { compression })
|
||||
const sizes = {
|
||||
compressedSize: 0,
|
||||
sourceSize: 0,
|
||||
writtenSize: 0,
|
||||
}
|
||||
const updateSums = ({ writtenSize, compressedSize, sourceSize }) => {
|
||||
sizes.writtenSize += writtenSize ?? 0
|
||||
sizes.compressedSize += compressedSize ?? 0
|
||||
sizes.sourceSize += sourceSize ?? 0
|
||||
}
|
||||
await asyncEach(
|
||||
parseVhdStream(inputStream),
|
||||
async function (item) {
|
||||
@@ -31,10 +21,10 @@ const buildVhd = Disposable.wrap(async function* (handler, path, inputStream, {
|
||||
vhd.header = item.header
|
||||
break
|
||||
case 'parentLocator':
|
||||
updateSums(await vhd.writeParentLocator({ ...item, data: item.buffer }))
|
||||
await vhd.writeParentLocator({ ...item, data: item.buffer })
|
||||
break
|
||||
case 'block':
|
||||
updateSums(await vhd.writeEntireBlock(item))
|
||||
await vhd.writeEntireBlock(item)
|
||||
break
|
||||
case 'bat':
|
||||
// it exists but I don't care
|
||||
@@ -46,18 +36,9 @@ const buildVhd = Disposable.wrap(async function* (handler, path, inputStream, {
|
||||
{
|
||||
concurrency,
|
||||
}
|
||||
)(await Promise.all([vhd.writeFooter(), vhd.writeHeader(), vhd.writeBlockAllocationTable()])).forEach(
|
||||
([footer, header, bat]) => {
|
||||
updateSums(footer)
|
||||
updateSums(header)
|
||||
updateSums(bat)
|
||||
}
|
||||
)
|
||||
const vhdSize = vhd.streamSize()
|
||||
return {
|
||||
...sizes,
|
||||
vhdSize,
|
||||
}
|
||||
await Promise.all([vhd.writeFooter(), vhd.writeHeader(), vhd.writeBlockAllocationTable()])
|
||||
return vhd.streamSize()
|
||||
})
|
||||
|
||||
exports.createVhdDirectoryFromStream = async function createVhdDirectoryFromStream(
|
||||
@@ -67,11 +48,11 @@ exports.createVhdDirectoryFromStream = async function createVhdDirectoryFromStre
|
||||
{ validator, concurrency = 16, compression } = {}
|
||||
) {
|
||||
try {
|
||||
const sizes = await buildVhd(handler, path, inputStream, { concurrency, compression })
|
||||
const size = await buildVhd(handler, path, inputStream, { concurrency, compression })
|
||||
if (validator !== undefined) {
|
||||
await validator.call(this, path)
|
||||
}
|
||||
return sizes
|
||||
return size
|
||||
} catch (error) {
|
||||
// cleanup on error
|
||||
await handler.rmtree(path).catch(warn)
|
||||
|
||||
@@ -142,11 +142,9 @@ const COLUMNS = [
|
||||
return
|
||||
}
|
||||
if (operationTask.message === 'transfer' && vmTransferSize === undefined) {
|
||||
// @todo handle if size is an object
|
||||
vmTransferSize = operationTask.result?.size
|
||||
}
|
||||
if (operationTask.message === 'merge' && vmMergeSize === undefined) {
|
||||
// @todo handle if size is an object
|
||||
vmMergeSize = operationTask.result?.size
|
||||
}
|
||||
|
||||
|
||||
@@ -330,7 +330,6 @@ const SrTask = ({ children, className, task }) => (
|
||||
)
|
||||
|
||||
const TransferMergeTask = ({ className, task }) => {
|
||||
// @todo : handle case when size is an object
|
||||
const size = defined(() => task.result.size, 0)
|
||||
if (task.status === 'success' && size === 0 && task.warnings?.length === 0) {
|
||||
return null
|
||||
|
||||
Reference in New Issue
Block a user