1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-21 14:50:08 +03:00

F #5516: Expose backup functionality (#2346)

This commit is contained in:
Frederick Borges 2022-11-15 10:59:12 +01:00 committed by GitHub
parent 0d5f9280a8
commit ea86e8ac15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 405 additions and 13 deletions

View File

@ -102,6 +102,7 @@ dialogs:
placement: true
sched_action: true
booting: true
backup: true
create_dialog:
ownership: true
capacity: true
@ -125,3 +126,4 @@ dialogs:
not_on:
- vcenter
- kvm
backup: true

View File

@ -40,6 +40,14 @@ const DS_ID = {
.default(() => undefined),
}
export const FIELDS = [DS_ID]
const RESET = {
name: 'reset',
label: T.Reset,
type: INPUT_TYPES.SWITCH,
validation: boolean(),
grid: { xs: 12, md: 6 },
}
export const FIELDS = [DS_ID, RESET]
export const SCHEMA = object(getValidationFromFields(FIELDS))

View File

@ -0,0 +1,60 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import PropTypes from 'prop-types'
import { RefreshDouble as BackupIcon } from 'iconoir-react'
import FormWithSchema from 'client/components/Forms/FormWithSchema'
import {
STEP_ID as EXTRA_ID,
TabType,
} from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
import {
SECTIONS,
FIELDS,
} from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/backup/schema'
import { T } from 'client/constants'
const Backup = () => (
<>
{SECTIONS.map(({ id, ...section }) => (
<FormWithSchema
key={id}
id={EXTRA_ID}
cy={`${EXTRA_ID}-${id}`}
{...section}
/>
))}
</>
)
Backup.propTypes = {
data: PropTypes.any,
setFormData: PropTypes.func,
}
Backup.displayName = 'Backup'
/** @type {TabType} */
const TAB = {
id: 'backup',
name: T.Backup,
icon: BackupIcon,
Content: Backup,
getError: (error) => FIELDS.some(({ name }) => error?.[name]),
}
export default TAB

View File

@ -0,0 +1,80 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { string, boolean, number } from 'yup'
import { Field, Section, arrayToOptions } from 'client/utils'
import { T, INPUT_TYPES, FS_FREEZE_OPTIONS } from 'client/constants'
const BACKUP_VOLATILE_FIELD = {
name: 'BACKUP_CONFIG.BACKUP_VOLATILE',
label: T.BackupVolatileDisksQuestion,
type: INPUT_TYPES.SWITCH,
validation: boolean().yesOrNo().notRequired(),
}
const FS_FREEZE_FIELD = {
name: 'BACKUP_CONFIG.FS_FREEZE',
label: T.FSFreeze,
tooltip: T.FSFreezeConcept,
type: INPUT_TYPES.SELECT,
values: arrayToOptions(Object.keys(FS_FREEZE_OPTIONS), {
getText: (type) => type,
getValue: (type) => FS_FREEZE_OPTIONS[type],
}),
validation: string()
.trim()
.default(() => undefined),
}
const KEEP_LAST_FIELD = {
name: 'BACKUP_CONFIG.KEEP_LAST',
label: T.HowManyBackupsQuestion,
type: INPUT_TYPES.TEXT,
htmlType: 'number',
validation: number()
.notRequired()
.nullable(true)
.default(() => undefined)
.transform((_, val) => (val !== '' ? val : null)),
}
const MODE_FIELD = {
name: 'BACKUP_CONFIG.MODE',
label: T.FSFreeze,
tooltip: T.FSFreezeConcept,
type: INPUT_TYPES.SELECT,
values: arrayToOptions(Object.keys(BACKUP_MODE_OPTIONS), {
addEmpty: true,
getText: (type) => type,
getValue: (type) => BACKUP_MODE_OPTIONS[type],
}),
validation: string()
.trim()
.default(() => undefined),
}
/** @type {Section[]} Sections */
const SECTIONS = [
{
id: 'backup-configuration',
fields: [BACKUP_VOLATILE_FIELD, FS_FREEZE_FIELD, KEEP_LAST_FIELD, MODE_FIELD],
},
]
/** @type {Field[]} List of Placement fields */
const FIELDS = [BACKUP_VOLATILE_FIELD, FS_FREEZE_FIELD, KEEP_LAST_FIELD, MODE_FIELD]
export { SECTIONS, FIELDS }

View File

@ -31,6 +31,7 @@ import Booting from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraCo
import Context from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context'
import InputOutput from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput'
import Numa from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/numa'
import Backup from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/backup'
import { STEP_ID as GENERAL_ID } from 'client/components/Forms/VmTemplate/CreateForm/Steps/General'
import { SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema'
@ -58,6 +59,7 @@ export const TABS = [
ScheduleAction,
Placement,
Numa,
Backup,
]
const Content = ({ data, setFormData }) => {

View File

@ -18,6 +18,7 @@ import { array, object, ObjectSchema } from 'yup'
import { HYPERVISORS } from 'client/constants'
import { getObjectSchemaFromFields } from 'client/utils'
import { FIELDS as PLACEMENT_FIELDS } from './placement/schema'
import { FIELDS as BACKUP_FIELDS } from './backup/schema'
import { FIELDS as OS_FIELDS, BOOT_ORDER_FIELD } from './booting/schema'
import { SCHEMA as NUMA_SCHEMA, FIELDS as NUMA_FIELDS } from './numa/schema'
import { SCHEMA as IO_SCHEMA } from './inputOutput/schema'
@ -59,6 +60,7 @@ export const SCHEMA = (hypervisor) =>
.concat(
getObjectSchemaFromFields([...PLACEMENT_FIELDS, ...OS_FIELDS(hypervisor)])
)
.concat(getObjectSchemaFromFields([...BACKUP_FIELDS]))
.concat(NUMA_SCHEMA(hypervisor))
export {

View File

@ -688,6 +688,14 @@ module.exports = {
DatastorePolicyExpressionConcept: `
This field sets which attribute will be used to
sort the suitable datastores for this VM`,
/* VM Template schema - Backup */
BackupVolatileDisksQuestion: 'Backup volatile disks?',
FSFreeze: 'FS Freeze',
HowManyBackupsQuestion: 'How many backups do you want to keep?',
QEMUAgent: 'QEMU Agent',
FSFreezeConcept: `
How the FS is freeze for running VMs. When
possible backups are crash consistent`,
/* VM Template schema - OS & CPU */
/* VM Template schema - OS & CPU - boot */
Boot: 'Boot',

View File

@ -16,6 +16,7 @@
import * as ACTIONS from 'client/constants/actions'
// eslint-disable-next-line no-unused-vars
import { Permissions, LockInfo } from 'client/constants/common'
import { T } from 'client/constants'
/**
* @typedef VmTemplate
@ -105,3 +106,16 @@ export const TEMPLATE_LOGOS = {
'Windows xp': 'images/logos/windowsxp.png',
'Windows 10': 'images/logos/windows8.png',
}
/** @enum {string} FS freeze options type */
export const FS_FREEZE_OPTIONS = {
[T.None]: 'NONE',
[T.QEMUAgent]: 'QEMU-AGENT',
[T.Suspend]: 'SUSPEND',
}
/** @enum {string} Backup mode options type */
export const BACKUP_MODE_OPTIONS = {
[T.Full]: 'FULL',
[T.Increment]: 'INCREMENT',
}

View File

@ -882,6 +882,7 @@ const vmApi = oneApi.injectEndpoints({
* @param {object} params - Request parameters
* @param {string} params.id - Virtual machine id
* @param {number} params.dsId - Backup Datastore id
* @param {boolean} params.reset - Backup reset
* @returns {number} Virtual machine id
* @throws Fails when response isn't code 200
*/

View File

@ -688,6 +688,10 @@ module.exports = {
from: postBody,
default: 0,
},
reset: {
from: postBody,
default: 0,
},
},
},
[VM_POOL_INFO]: {

View File

@ -726,6 +726,7 @@ tabs:
panel_tabs:
backup_info_tab: true
backup_vms_tab: true
backup_increments_tab: true
table_columns:
- 0 # Checkbox
- 1 # ID

View File

@ -723,6 +723,7 @@ tabs:
panel_tabs:
backup_info_tab: true
backup_vms_tab: true
backup_increments_tab: true
table_columns:
- 0 # Checkbox
- 1 # ID

View File

@ -719,6 +719,7 @@ tabs:
panel_tabs:
backup_info_tab: true
backup_vms_tab: true
backup_increments_tab: true
table_columns:
- 0 # Checkbox
- 1 # ID

View File

@ -728,6 +728,7 @@ tabs:
panel_tabs:
backup_info_tab: true
backup_vms_tab: true
backup_increments_tab: true
table_columns:
- 0 # Checkbox
- 1 # ID

View File

@ -727,6 +727,7 @@ tabs:
panel_tabs:
backup_info_tab: true
backup_vms_tab: true
backup_increments_tab: true
table_columns:
- 0 # Checkbox
- 1 # ID

View File

@ -720,6 +720,7 @@ tabs:
panel_tabs:
backup_info_tab: true
backup_vms_tab: true
backup_increments_tab: true
table_columns:
- 0 # Checkbox
- 1 # ID

View File

@ -726,6 +726,7 @@ tabs:
panel_tabs:
backup_info_tab: true
backup_vms_tab: true
backup_increments_tab: true
table_columns:
- 0 # Checkbox
- 1 # ID

View File

@ -727,6 +727,7 @@ tabs:
panel_tabs:
backup_info_tab: true
backup_vms_tab: true
backup_increments_tab: true
table_columns:
- 0 # Checkbox
- 1 # ID

View File

@ -720,6 +720,7 @@ tabs:
panel_tabs:
backup_info_tab: true
backup_vms_tab: true
backup_increments_tab: true
table_columns:
- 0 # Checkbox
- 1 # ID

View File

@ -135,7 +135,7 @@ module OpenNebulaJSON
when 'sg_detach'
sg_detach(action_hash['params'])
when 'backup'
backup(action_hash['params']['dst_id'].to_i)
backup(action_hash['params']['dst_id'].to_i, action_hash['params']['reset'])
else
error_msg = "#{action_hash['perform']} action not " \
' available for this resource'

View File

@ -30,6 +30,7 @@ define(function(require) {
var _panels = [
require('./backups-tab/panels/info'),
require('./backups-tab/panels/vms'),
require('./backups-tab/panels/increments'),
];
var _panelsHooks = [

View File

@ -20,7 +20,6 @@ define(function(require) {
*/
var ImageCommonDataTable = require("tabs/images-tab/datatable-common");
var Locale = require("utils/locale");
var Humanize = require("utils/humanize");
var OpenNebulaImage = require("opennebula/image");

View File

@ -0,0 +1,81 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems */
/* */
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */
/* not use this file except in compliance with the License. You may obtain */
/* a copy of the License at */
/* */
/* http://www.apache.org/licenses/LICENSE-2.0 */
/* */
/* Unless required by applicable law or agreed to in writing, software */
/* distributed under the License is distributed on an "AS IS" BASIS, */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
/* See the License for the specific language governing permissions and */
/* limitations under the License. */
/* -------------------------------------------------------------------------- */
define(function(require){
/*
DEPENDENCIES
*/
var Locale = require('utils/locale');
var Humanize = require("utils/humanize");
var TemplateHTML = require("hbs!./increments/html");
/*
CONSTANTS
*/
var PANEL_ID = require('./increments/panelId');
var XML_ROOT = "IMAGE";
/*
CONSTRUCTOR
*/
function Panel(info) {
this.title = Locale.tr("Increments");
this.icon = "fa-angle-double-up";
this.element = info[XML_ROOT];
return this;
}
Panel.PANEL_ID = PANEL_ID;
Panel.prototype.html = _html;
Panel.prototype.setup = _setup;
return Panel;
/*
FUNCTION DEFINITIONS
*/
function _html() {
if (!this.element.BACKUP_INCREMENTS.INCREMENT){
return ""
}
var increments = Array.isArray(this.element.BACKUP_INCREMENTS.INCREMENT) ?
this.element.BACKUP_INCREMENTS.INCREMENT :
[this.element.BACKUP_INCREMENTS.INCREMENT]
var html = '<div class="backup_increments_content">'
increments.forEach(function(increment){
html += TemplateHTML({
id: increment.ID,
size: Humanize.sizeFromB(increment.SIZE),
date: Humanize.prettyTimeDatatable(increment.DATE),
type: increment.TYPE
})
});
html += "</div>"
return html
}
function _setup(context) {
return false;
}
})

View File

@ -0,0 +1,32 @@
{{! -------------------------------------------------------------------------- }}
{{! Copyright 2002-2022, OpenNebula Project, OpenNebula Systems }}
{{! }}
{{! Licensed under the Apache License, Version 2.0 (the "License"); you may }}
{{! not use this file except in compliance with the License. You may obtain }}
{{! a copy of the License at }}
{{! }}
{{! http://www.apache.org/licenses/LICENSE-2.0 }}
{{! }}
{{! Unless required by applicable law or agreed to in writing, software }}
{{! distributed under the License is distributed on an "AS IS" BASIS, }}
{{! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. }}
{{! See the License for the specific language governing permissions and }}
{{! limitations under the License. }}
{{! -------------------------------------------------------------------------- }}
<div id="backup-increment-{{id}}" class="backup-increment">
<div class="backup-increment-id">
#{{id}}
</div>
<div class="backup-increment-data">
<div class="backup-increment-date">
<i class="fas fa-calendar-alt"></i> {{date}}
</div>
<div class="backup-increment-size">
<b>{{tr "Size"}}</b>: {{size}}
</div>
<div class="backup-increment-type">
<b>{{tr "Type"}}</b>: {{type}}
</div>
</div>
</div>

View File

@ -0,0 +1,19 @@
/* -------------------------------------------------------------------------- */
/* Copyright 2002-2022, OpenNebula Project, OpenNebula Systems */
/* */
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */
/* not use this file except in compliance with the License. You may obtain */
/* a copy of the License at */
/* */
/* http://www.apache.org/licenses/LICENSE-2.0 */
/* */
/* Unless required by applicable law or agreed to in writing, software */
/* distributed under the License is distributed on an "AS IS" BASIS, */
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
/* See the License for the specific language governing permissions and */
/* limitations under the License. */
/* -------------------------------------------------------------------------- */
define(function(require){
return 'backup_increments_tab';
})

View File

@ -285,6 +285,7 @@ define(function(require) {
var compatible_sys_ds = $('#compatible_sys_ds', dialog).val();
var restic_password = $('#restic_password', dialog).val();
var restic_sftp_server = $('#restic_sftp_server', dialog).val();
var restic_sftp_user = $('#restic_sftp_user', dialog).val();
var restic_ionice = $('#restic_ionice', dialog).val();
var restic_nice = $('#restic_nice', dialog).val();
var restic_bwlimit = $('#restic_bwlimit', dialog).val();
@ -385,6 +386,9 @@ define(function(require) {
if (restic_sftp_server)
ds_obj.datastore.restic_sftp_server = restic_sftp_server;
if (restic_sftp_user)
ds_obj.datastore.restic_sftp_user = restic_sftp_user;
if (restic_ionice)
ds_obj.datastore.restic_ionice = restic_ionice;
@ -457,12 +461,12 @@ define(function(require) {
$('label[for="no_decompress"],input#no_decompress', dialog).parent().hide();
$('label[for="restic_password"]', dialog).parent().hide();
$('label[for="restic_sftp_server"]', dialog).parent().hide();
$('label[for="restic_sftp_user"]', dialog).parent().hide();
$('label[for="restic_ionice"]', dialog).parent().hide();
$('label[for="restic_nice"]', dialog).parent().hide();
$('label[for="restic_bwlimit"]', dialog).parent().hide();
$('label[for="restic_compression"]', dialog).parent().hide();
$('label[for="restic_connections"]', dialog).parent().hide();
$('label[for="restic_sftp_server"]', dialog).parent().hide();
$('label[for="rsync_host"]', dialog).parent().hide();
$('label[for="rsync_user"]', dialog).parent().hide();
@ -496,12 +500,12 @@ define(function(require) {
$('label[for="no_decompress"],input#no_decompress', dialog).parent().show();
$('label[for="restic_password"]', dialog).parent().show();
$('label[for="restic_sftp_server"]', dialog).parent().show();
$('label[for="restic_sftp_user"]', dialog).parent().show();
$('label[for="restic_ionice"]', dialog).parent().show();
$('label[for="restic_nice"]', dialog).parent().show();
$('label[for="restic_bwlimit"]', dialog).parent().show();
$('label[for="restic_compression"]', dialog).parent().show();
$('label[for="restic_connections"]', dialog).parent().show();
$('label[for="restic_sftp_server"]', dialog).parent().show();
$('label[for="rsync_host"]', dialog).parent().show();
$('label[for="rsync_user"]', dialog).parent().show();
@ -614,12 +618,12 @@ define(function(require) {
$('label[for="vcenter_cluster"],div#vcenter_cluster_wrapper', dialog).parent().hide();
$('label[for="restic_password"]', dialog).parent().fadeIn();
$('label[for="restic_sftp_server"]', dialog).parent().fadeIn();
$('label[for="restic_sftp_user"]', dialog).parent().fadeIn();
$('label[for="restic_ionice"]', dialog).parent().fadeIn();
$('label[for="restic_nice"]', dialog).parent().fadeIn();
$('label[for="restic_bwlimit"]', dialog).parent().fadeIn();
$('label[for="restic_compression"]', dialog).parent().fadeIn();
$('label[for="restic_connections"]', dialog).parent().fadeIn();
$('label[for="restic_sftp_server"]', dialog).parent().fadeIn();
}
function _selectRsync(dialog) {

View File

@ -254,6 +254,7 @@
</label>
<input type="text" name="restic_password" id="restic_password" />
</div>
<div class="medium-6 columns">
<label for="restic_sftp_server">
{{tr "Restic SFTP Server"}}
@ -261,6 +262,13 @@
<input type="text" name="restic_sftp_server" id="restic_sftp_server" />
</div>
<div class="medium-6 columns">
<label for="restic_sftp_user">
{{tr "Restic SFTP User"}}
</label>
<input type="text" name="restic_sftp_user" id="restic_sftp_user" />
</div>
<div class="medium-6 columns">
<label for="restic_ionice">
{{tr "Backup I/O Priority"}}

View File

@ -107,6 +107,14 @@ define(function(require) {
var element = element_json.IMAGE;
var type = OpenNebulaImage.typeStr(element.TYPE);
if (type === Locale.tr("BACKUP")) {
if (element.BACKUP_INCREMENTS.INCREMENT){
type = Locale.tr("INCREMENTAL")
}
else {
type = Locale.tr("FULL")
}
}
var state = OpenNebulaImage.stateStr(element.STATE);
var search = {

View File

@ -38,7 +38,8 @@ define(function(require) {
var idsElements = {
backup_volatile: "#backup-volatile",
fs_freeze: "#fs-freeze",
keep_last: "#keep-last"
keep_last: "#keep-last",
mode: "#mode"
}
/*
@ -84,6 +85,7 @@ define(function(require) {
var backupVolatile = $(idsElements.backup_volatile, context).is(':checked');
var fsFreeze = _getValue(idsElements.fs_freeze, context);
var keepLast = _getValue(idsElements.keep_last, context);
var mode = _getValue(idsElements.mode, context);
if (backupVolatile){
backupConfigJSON['BACKUP_VOLATILE'] = 'YES'
@ -97,6 +99,10 @@ define(function(require) {
backupConfigJSON['KEEP_LAST'] = keepLast
}
if (mode !== '-'){
backupConfigJSON['MODE'] = mode
}
return { 'BACKUP_CONFIG' : backupConfigJSON}
}
@ -122,6 +128,9 @@ define(function(require) {
if(configs && configs.KEEP_LAST){
_fillBootValue(idsElements.keep_last, context, configs.KEEP_LAST);
}
if(configs && configs.MODE){
_fillBootValue(idsElements.mode, context, configs.MODE);
}
}
}
});

View File

@ -47,5 +47,15 @@
<input type="number" id="keep-last" class="disabled_firecracker">
</div>
</div>
<div class="small-12 medium-4 columns">
<label for="mode">
{{tr "Mode"}}
</label>
<select id="mode">
<option value="-" selected>-</option>
<option value="FULL">{{tr "Full"}}</option>
<option value="INCREMENT">{{tr "Increment"}}</option>
</select>
</div>
</div>
</div>

View File

@ -78,7 +78,9 @@ define(function(require) {
$("#" + DIALOG_ID + "Form", dialog).submit(function() {
var sel_elems = Sunstone.getDataTable(VMS_TAB_ID).elements();
var extra_info = {};
var extra_info = {
"reset":$("#cb_backup_reset", dialog).prop("checked")
};
var targetDS = that.datastoreTable.retrieveResourceTableSelect();
extra_info["dst_id"] = targetDS;

View File

@ -22,13 +22,18 @@
<form id="{{dialogId}}Form" action="" class="custom creation">
<div class="row">
<fieldset>
<div class="backup_reset">
<label>
<input type="checkbox" id="cb_backup_reset" style="margin-left: 15px"> {{tr "Reset"}}
</label>
</div>
<legend>{{tr "Datastore to restore"}}</legend>
<div class="columns large-12">
<div class="large-12 columns">
</div>
{{{datastoreTableSelectHTML}}}
<div class="columns large-12">
<div class="large-12 columns">
</div>
</fieldset>
{{{datastoreTableSelectHTML}}}
</div>
</fieldset>
</div>
<div class="form_buttons row">
<button class="button radius right" id="vm_backup_button" value="VM.backup">{{tr "Backup"}}</button>

View File

@ -0,0 +1,23 @@
.backup_increments_content {
display: flex;
flex-wrap: wrap;
}
.backup-increment {
width: 30%;
margin: 1rem;
border: solid 1px #dfdfdf;
border-radius: 5px;
display: flex;
}
.backup-increment-data > div {
margin: 0.2rem;
}
.backup-increment-id {
font-size: xx-large;
font-weight: 400;
width: 30%;
text-align: center;
}

View File

@ -39,6 +39,7 @@
@import './visjs';
@import './flot';
@import './login';
@import './backups';