* [pve-devel] [PATCH widget-toolkit 0/1] Add GUI for importdisk @ 2020-07-07 10:04 Dominic Jäger 2020-07-07 10:04 ` [pve-devel] [PATCH widget-toolkit 1/3] EditWindow: Change url to 'importdisk' for import Dominic Jäger ` (2 more replies) 0 siblings, 3 replies; 8+ messages in thread From: Dominic Jäger @ 2020-07-07 10:04 UTC (permalink / raw) To: pve-devel This series makes importing disks possible via GUI which is one step to make migrating from other hypervisors easier. Dominic Jäger (1): EditWindow: Change url to 'importdisk' for import src/window/Edit.js | 4 ++++ 1 file changed, 4 insertions(+) -- 2.20.1 ^ permalink raw reply [flat|nested] 8+ messages in thread
* [pve-devel] [PATCH widget-toolkit 1/3] EditWindow: Change url to 'importdisk' for import 2020-07-07 10:04 [pve-devel] [PATCH widget-toolkit 0/1] Add GUI for importdisk Dominic Jäger @ 2020-07-07 10:04 ` Dominic Jäger 2020-07-07 10:04 ` [pve-devel] [PATCH manager 2/3] Hardware View: Add GUI for importdisk Dominic Jäger 2020-07-07 10:04 ` [pve-devel] [PATCH qemu-server 3/3] Move importdisk from qm to API Dominic Jäger 2 siblings, 0 replies; 8+ messages in thread From: Dominic Jäger @ 2020-07-07 10:04 UTC (permalink / raw) To: pve-devel Signed-off-by: Dominic Jäger <d.jaeger@proxmox.com> --- src/window/Edit.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/window/Edit.js b/src/window/Edit.js index c165141..816d68f 100644 --- a/src/window/Edit.js +++ b/src/window/Edit.js @@ -134,6 +134,10 @@ Ext.define('Proxmox.window.Edit', { values = undefined; } + if (me.isImport) { + url = url.replace('config', 'importdisk'); + } + Proxmox.Utils.API2Request({ url: url, waitMsgTarget: me, -- 2.20.1 ^ permalink raw reply [flat|nested] 8+ messages in thread
* [pve-devel] [PATCH manager 2/3] Hardware View: Add GUI for importdisk 2020-07-07 10:04 [pve-devel] [PATCH widget-toolkit 0/1] Add GUI for importdisk Dominic Jäger 2020-07-07 10:04 ` [pve-devel] [PATCH widget-toolkit 1/3] EditWindow: Change url to 'importdisk' for import Dominic Jäger @ 2020-07-07 10:04 ` Dominic Jäger 2020-07-29 13:25 ` Aaron Lauterer 2020-07-07 10:04 ` [pve-devel] [PATCH qemu-server 3/3] Move importdisk from qm to API Dominic Jäger 2 siblings, 1 reply; 8+ messages in thread From: Dominic Jäger @ 2020-07-07 10:04 UTC (permalink / raw) To: pve-devel Make importing single disks easier. Required to import a whole VM via GUI. Signed-off-by: Dominic Jäger <d.jaeger@proxmox.com> --- www/manager6/qemu/HDEdit.js | 80 ++++++++++++++++++++++--------- www/manager6/qemu/HardwareView.js | 18 +++++++ 2 files changed, 76 insertions(+), 22 deletions(-) diff --git a/www/manager6/qemu/HDEdit.js b/www/manager6/qemu/HDEdit.js index e2a5b914..7f6a7b2b 100644 --- a/www/manager6/qemu/HDEdit.js +++ b/www/manager6/qemu/HDEdit.js @@ -76,23 +76,43 @@ Ext.define('PVE.qemu.HDInputPanel', { me.drive.format = values.diskformat; } - PVE.Utils.propertyStringSet(me.drive, !values.backup, 'backup', '0'); - PVE.Utils.propertyStringSet(me.drive, values.noreplicate, 'replicate', 'no'); - PVE.Utils.propertyStringSet(me.drive, values.discard, 'discard', 'on'); - PVE.Utils.propertyStringSet(me.drive, values.ssd, 'ssd', 'on'); - PVE.Utils.propertyStringSet(me.drive, values.iothread, 'iothread', 'on'); - PVE.Utils.propertyStringSet(me.drive, values.cache, 'cache'); - - var names = ['mbps_rd', 'mbps_wr', 'iops_rd', 'iops_wr']; - Ext.Array.each(names, function(name) { - var burst_name = name + '_max'; - PVE.Utils.propertyStringSet(me.drive, values[name], name); - PVE.Utils.propertyStringSet(me.drive, values[burst_name], burst_name); - }); - - - params[confid] = PVE.Parser.printQemuDrive(me.drive); + if (me.isImport) { + // These keys & values are accepted by the API as they are + let simple = ['backup', 'ssd', 'iothread', 'cache']; + let burst = ['mbps_rd', 'mbps_wr', 'iops_rd', 'iops_wr']; + burst = burst.concat(burst.map(x => `${x}_max`)); + let available = simple.concat(burst); + let addValues = key => `${key}=${values[key]}`; + let selectedKeys = x => values[x]; + let options = available.filter(selectedKeys).map(addValues).join(); + // These need modification for the API + options += values.discard ? ',discard=on' : ''; + options += values.noreplicate ? ',replicate=0' : ''; + params.device_options = options; + } else { + PVE.Utils.propertyStringSet(me.drive, !values.backup, 'backup', '0'); + PVE.Utils.propertyStringSet(me.drive, values.noreplicate, 'replicate', 'no'); + PVE.Utils.propertyStringSet(me.drive, values.discard, 'discard', 'on'); + PVE.Utils.propertyStringSet(me.drive, values.ssd, 'ssd', 'on'); + PVE.Utils.propertyStringSet(me.drive, values.iothread, 'iothread', 'on'); + PVE.Utils.propertyStringSet(me.drive, values.cache, 'cache'); + + var names = ['mbps_rd', 'mbps_wr', 'iops_rd', 'iops_wr']; + Ext.Array.each(names, function(name) { + var burst_name = name + '_max'; + PVE.Utils.propertyStringSet(me.drive, values[name], name); + PVE.Utils.propertyStringSet(me.drive, values[burst_name], burst_name); + }); + } + if (me.isImport) { + params.source = values.inputImage; + params.device = values.controller + values.deviceid; + params.storage = values.hdstorage; + if (values.diskformat) params.format = values.diskformat; + } else { + params[confid] = PVE.Parser.printQemuDrive(me.drive); + } return params; }, @@ -199,14 +219,17 @@ Ext.define('PVE.qemu.HDInputPanel', { allowBlank: false }); me.column1.push(me.unusedDisks); - } else if (me.isCreate) { - me.column1.push({ + } else if (me.isCreate || me.isImport) { + let selector = { xtype: 'pveDiskStorageSelector', storageContent: 'images', name: 'disk', nodename: me.nodename, - autoSelect: me.insideWizard - }); + hideSize: me.isImport, + autoSelect: me.insideWizard || me.isImport, + }; + if (me.isImport) selector.storageLabel = 'Target storage'; + me.column1.push(selector); } else { me.column1.push({ xtype: 'textfield', @@ -231,6 +254,14 @@ Ext.define('PVE.qemu.HDInputPanel', { name: 'discard' } ); + if (me.isImport) { + me.column2.push({ + xtype: 'textfield', + fieldLabel: gettext('Source image'), + name: 'inputImage', + emptyText: '/home/user/disk.qcow2', + }); + } me.advancedColumn1.push( { @@ -372,14 +403,19 @@ Ext.define('PVE.qemu.HDEdit', { confid: me.confid, nodename: nodename, unused: unused, - isCreate: me.isCreate + isCreate: me.isCreate, + isImport: me.isImport, }); var subject; if (unused) { me.subject = gettext('Unused Disk'); + } else if (me.isImport) { + me.title = 'Import Hard Disk'; + me.submitText = 'Import'; + me.backgroundDelay = undefined; } else if (me.isCreate) { - me.subject = gettext('Hard Disk'); + me.subject = gettext('Hard Disk'); } else { me.subject = gettext('Hard Disk') + ' (' + me.confid + ')'; } diff --git a/www/manager6/qemu/HardwareView.js b/www/manager6/qemu/HardwareView.js index 40b3fe86..f9735998 100644 --- a/www/manager6/qemu/HardwareView.js +++ b/www/manager6/qemu/HardwareView.js @@ -436,6 +436,23 @@ Ext.define('PVE.qemu.HardwareView', { handler: run_move }); + var import_btn = new Proxmox.button.Button({ + text: gettext('Import disk'), + hidden: !(caps.vms['VM.Allocate'] && + caps.storage['Datastore.AllocateTemplate'] && + caps.storage['Datastore.AllocateSpace']), + handler: function() { + var win = Ext.create('PVE.qemu.HDEdit', { + method: 'POST', + url: `/api2/extjs/${baseurl}`, + pveSelNode: me.pveSelNode, + isImport: true, + }); + win.on('destroy', me.reload, me); + win.show(); + }, + }); + var remove_btn = new Proxmox.button.Button({ text: gettext('Remove'), defaultText: gettext('Remove'), @@ -752,6 +769,7 @@ Ext.define('PVE.qemu.HardwareView', { edit_btn, resize_btn, move_btn, + import_btn, revert_btn ], rows: rows, -- 2.20.1 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [pve-devel] [PATCH manager 2/3] Hardware View: Add GUI for importdisk 2020-07-07 10:04 ` [pve-devel] [PATCH manager 2/3] Hardware View: Add GUI for importdisk Dominic Jäger @ 2020-07-29 13:25 ` Aaron Lauterer 2020-07-30 10:20 ` Dominic Jäger 0 siblings, 1 reply; 8+ messages in thread From: Aaron Lauterer @ 2020-07-29 13:25 UTC (permalink / raw) To: pve-devel Some thing inline. On 7/7/20 12:04 PM, Dominic Jäger wrote: > Make importing single disks easier. > Required to import a whole VM via GUI. > > Signed-off-by: Dominic Jäger <d.jaeger@proxmox.com> > --- > www/manager6/qemu/HDEdit.js | 80 ++++++++++++++++++++++--------- > www/manager6/qemu/HardwareView.js | 18 +++++++ > 2 files changed, 76 insertions(+), 22 deletions(-) > > diff --git a/www/manager6/qemu/HDEdit.js b/www/manager6/qemu/HDEdit.js > index e2a5b914..7f6a7b2b 100644 > --- a/www/manager6/qemu/HDEdit.js > +++ b/www/manager6/qemu/HDEdit.js > @@ -76,23 +76,43 @@ Ext.define('PVE.qemu.HDInputPanel', { > me.drive.format = values.diskformat; > } > > - PVE.Utils.propertyStringSet(me.drive, !values.backup, 'backup', '0'); > - PVE.Utils.propertyStringSet(me.drive, values.noreplicate, 'replicate', 'no'); > - PVE.Utils.propertyStringSet(me.drive, values.discard, 'discard', 'on'); > - PVE.Utils.propertyStringSet(me.drive, values.ssd, 'ssd', 'on'); > - PVE.Utils.propertyStringSet(me.drive, values.iothread, 'iothread', 'on'); > - PVE.Utils.propertyStringSet(me.drive, values.cache, 'cache'); > - > - var names = ['mbps_rd', 'mbps_wr', 'iops_rd', 'iops_wr']; > - Ext.Array.each(names, function(name) { > - var burst_name = name + '_max'; > - PVE.Utils.propertyStringSet(me.drive, values[name], name); > - PVE.Utils.propertyStringSet(me.drive, values[burst_name], burst_name); > - }); > - > - > - params[confid] = PVE.Parser.printQemuDrive(me.drive); > + if (me.isImport) { > + // These keys & values are accepted by the API as they are > + let simple = ['backup', 'ssd', 'iothread', 'cache']; > + let burst = ['mbps_rd', 'mbps_wr', 'iops_rd', 'iops_wr']; > + burst = burst.concat(burst.map(x => `${x}_max`)); > + let available = simple.concat(burst); > + let addValues = key => `${key}=${values[key]}`; > + let selectedKeys = x => values[x]; > + let options = available.filter(selectedKeys).map(addValues).join(); > + // These need modification for the API > + options += values.discard ? ',discard=on' : ''; > + options += values.noreplicate ? ',replicate=0' : ''; > + params.device_options = options; One or two newlines in the above block would be nice to make the distinction between the logical blocks more obvious. > + } else { > + PVE.Utils.propertyStringSet(me.drive, !values.backup, 'backup', '0'); > + PVE.Utils.propertyStringSet(me.drive, values.noreplicate, 'replicate', 'no'); > + PVE.Utils.propertyStringSet(me.drive, values.discard, 'discard', 'on'); > + PVE.Utils.propertyStringSet(me.drive, values.ssd, 'ssd', 'on'); > + PVE.Utils.propertyStringSet(me.drive, values.iothread, 'iothread', 'on'); > + PVE.Utils.propertyStringSet(me.drive, values.cache, 'cache'); > + > + var names = ['mbps_rd', 'mbps_wr', 'iops_rd', 'iops_wr']; > + Ext.Array.each(names, function(name) { > + var burst_name = name + '_max'; > + PVE.Utils.propertyStringSet(me.drive, values[name], name); > + PVE.Utils.propertyStringSet(me.drive, values[burst_name], burst_name); > + }); > + } > > + if (me.isImport) { > + params.source = values.inputImage; > + params.device = values.controller + values.deviceid; > + params.storage = values.hdstorage; > + if (values.diskformat) params.format = values.diskformat; > + } else { > + params[confid] = PVE.Parser.printQemuDrive(me.drive); > + } > return params; > }, > > @@ -199,14 +219,17 @@ Ext.define('PVE.qemu.HDInputPanel', { > allowBlank: false > }); > me.column1.push(me.unusedDisks); > - } else if (me.isCreate) { > - me.column1.push({ > + } else if (me.isCreate || me.isImport) { > + let selector = { > xtype: 'pveDiskStorageSelector', > storageContent: 'images', > name: 'disk', > nodename: me.nodename, > - autoSelect: me.insideWizard > - }); > + hideSize: me.isImport, > + autoSelect: me.insideWizard || me.isImport, > + }; > + if (me.isImport) selector.storageLabel = 'Target storage'; this should be a `gettext('Target storage')` so it can be translated. > + me.column1.push(selector); > } else { > me.column1.push({ > xtype: 'textfield', > @@ -231,6 +254,14 @@ Ext.define('PVE.qemu.HDInputPanel', { > name: 'discard' > } > ); > + if (me.isImport) { > + me.column2.push({ > + xtype: 'textfield', > + fieldLabel: gettext('Source image'), > + name: 'inputImage', > + emptyText: '/home/user/disk.qcow2', > + }); > + } > > me.advancedColumn1.push( > { > @@ -372,14 +403,19 @@ Ext.define('PVE.qemu.HDEdit', { > confid: me.confid, > nodename: nodename, > unused: unused, > - isCreate: me.isCreate > + isCreate: me.isCreate, > + isImport: me.isImport, > }); > > var subject; > if (unused) { > me.subject = gettext('Unused Disk'); > + } else if (me.isImport) { > + me.title = 'Import Hard Disk'; for consistency I would not change the title and instead set the following: me.subject = gettext('Import Disk'), > + me.submitText = 'Import'; > + me.backgroundDelay = undefined; > } else if (me.isCreate) { > - me.subject = gettext('Hard Disk'); > + me.subject = gettext('Hard Disk'); > } else { > me.subject = gettext('Hard Disk') + ' (' + me.confid + ')'; > } > diff --git a/www/manager6/qemu/HardwareView.js b/www/manager6/qemu/HardwareView.js > index 40b3fe86..f9735998 100644 > --- a/www/manager6/qemu/HardwareView.js > +++ b/www/manager6/qemu/HardwareView.js > @@ -436,6 +436,23 @@ Ext.define('PVE.qemu.HardwareView', { > handler: run_move > }); > > + var import_btn = new Proxmox.button.Button({ > + text: gettext('Import disk'), > + hidden: !(caps.vms['VM.Allocate'] && > + caps.storage['Datastore.AllocateTemplate'] && > + caps.storage['Datastore.AllocateSpace']), > + handler: function() { > + var win = Ext.create('PVE.qemu.HDEdit', { > + method: 'POST', > + url: `/api2/extjs/${baseurl}`, > + pveSelNode: me.pveSelNode, > + isImport: true, > + }); > + win.on('destroy', me.reload, me); > + win.show(); > + }, > + }); > + > var remove_btn = new Proxmox.button.Button({ > text: gettext('Remove'), > defaultText: gettext('Remove'), > @@ -752,6 +769,7 @@ Ext.define('PVE.qemu.HardwareView', { > edit_btn, > resize_btn, > move_btn, > + import_btn, > revert_btn > ], > rows: rows, > ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [pve-devel] [PATCH manager 2/3] Hardware View: Add GUI for importdisk 2020-07-29 13:25 ` Aaron Lauterer @ 2020-07-30 10:20 ` Dominic Jäger 0 siblings, 0 replies; 8+ messages in thread From: Dominic Jäger @ 2020-07-30 10:20 UTC (permalink / raw) To: Proxmox VE development discussion Thank you for taking a look! > > + if (me.isImport) { > > + // These keys & values are accepted by the API as they are > > + let simple = ['backup', 'ssd', 'iothread', 'cache']; > > + let burst = ['mbps_rd', 'mbps_wr', 'iops_rd', 'iops_wr']; > > + burst = burst.concat(burst.map(x => `${x}_max`)); > > + let available = simple.concat(burst); > > + let addValues = key => `${key}=${values[key]}`; > > + let selectedKeys = x => values[x]; > > + let options = available.filter(selectedKeys).map(addValues).join(); > > + // These need modification for the API > > + options += values.discard ? ',discard=on' : ''; > > + options += values.noreplicate ? ',replicate=0' : ''; > > + params.device_options = options; > > One or two newlines in the above block would be nice to make the distinction between the logical blocks more obvious. Done. > > > + if (me.isImport) selector.storageLabel = 'Target storage'; > > this should be a `gettext('Target storage')` so it can be translated. Done. > for consistency I would not change the title and instead set the following: > me.subject = gettext('Import Disk'), Done. ^ permalink raw reply [flat|nested] 8+ messages in thread
* [pve-devel] [PATCH qemu-server 3/3] Move importdisk from qm to API 2020-07-07 10:04 [pve-devel] [PATCH widget-toolkit 0/1] Add GUI for importdisk Dominic Jäger 2020-07-07 10:04 ` [pve-devel] [PATCH widget-toolkit 1/3] EditWindow: Change url to 'importdisk' for import Dominic Jäger 2020-07-07 10:04 ` [pve-devel] [PATCH manager 2/3] Hardware View: Add GUI for importdisk Dominic Jäger @ 2020-07-07 10:04 ` Dominic Jäger 2020-07-29 13:00 ` Aaron Lauterer 2 siblings, 1 reply; 8+ messages in thread From: Dominic Jäger @ 2020-07-07 10:04 UTC (permalink / raw) To: pve-devel Additionally, add parameters that enable directly (avoiding the intermediate step as unused disk) attaching the disk to a bus/device with all known disk options. Required to create a GUI for importdisk. Signed-off-by: Dominic Jäger <d.jaeger@proxmox.com> --- PVE/API2/Qemu.pm | 113 ++++++++++++++++++++++++++++++++++- PVE/CLI/qm.pm | 57 +----------------- PVE/QemuServer/Drive.pm | 14 +++++ PVE/QemuServer/ImportDisk.pm | 2 +- 4 files changed, 127 insertions(+), 59 deletions(-) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index b33359d..6efbbe3 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -24,6 +24,7 @@ use PVE::QemuServer; use PVE::QemuServer::Drive; use PVE::QemuServer::CPUConfig; use PVE::QemuServer::Monitor qw(mon_cmd); +use PVE::QemuServer::ImportDisk qw(do_import); use PVE::QemuMigrate; use PVE::RPCEnvironment; use PVE::AccessControl; @@ -45,8 +46,6 @@ BEGIN { } } -use Data::Dumper; # fixme: remove - use base qw(PVE::RESTHandler); my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal."; @@ -4252,4 +4251,114 @@ __PACKAGE__->register_method({ return PVE::QemuServer::Cloudinit::dump_cloudinit_config($conf, $param->{vmid}, $param->{type}); }}); +__PACKAGE__->register_method ({ + name => 'importdisk', + path => '{vmid}/importdisk', + method => 'POST', + protected => 1, # for worker upid file + proxyto => 'node', + description => "Import an external disk image into a VM. " + ."The image format has to be supported by qemu-img.", + permissions => { + check => [ 'and', + [ 'perm', '/storage/{storage}', ['Datastore.AllocateTemplate']], + [ 'perm', '/storage/{storage}', ['Datastore.AllocateSpace']], + [ 'perm', '/vms/{vmid}', ['VM.Allocate']], + ], + }, + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + vmid => get_standard_option('pve-vmid', + {completion => \&PVE::QemuServer::complete_vmid}), + source => { + description => 'Path of the disk image that should be imported', + type => 'string', + }, + device => { + type => 'string', + description => "What device the imported image should be " + ."(e.g. 'ide0', 'scsi2'). Will add the image as unused disk " + ."if omitted.", + enum => [PVE::QemuServer::Drive::valid_drive_names()], + optional => 1, + }, + device_options => { + type => 'string', + format => 'drive_options', + description => "What options to set for the device " + ."(e.g. 'discard=on,backup=0')", + optional => 1, + }, + storage => get_standard_option('pve-storage-id', { + description => 'Which storage to use for the imported image.', + completion => \&PVE::QemuServer::complete_storage, + }), + format => { + type => 'string', + description => 'Target format.', + enum => [ 'raw', 'qcow2', 'vmdk' ], + optional => 1, + }, + digest => get_standard_option('pve-config-digest'), + }, + }, + returns => { type => 'string'}, + code => sub { + my ($params) = @_; + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my $vmid = extract_param($params, 'vmid'); + my $source = extract_param($params, 'source'); + my $digest = extract_param($params, 'digest'); + my $device_options = extract_param($params, 'device_options'); + my $device = extract_param($params, 'device'); + my $storage = extract_param($params, 'storage'); + my $vm_conf = PVE::QemuConfig->load_config($vmid); + + die "Could not import because source parameter is an empty string\n" + if ($source eq ""); + die "Could not import because source '$source' is not an absolute path\n" + if $source !~ m!/!; + die "Could not import because source '$source' does not exist" + if !-e $source; + die "VM $vmid config checksum missmatch (file change by other user?)\n" + if $digest && $digest ne $vm_conf->{digest}; + die "Could not import because device $device is already in use in " + ."VM $vmid. Choose a different device!\n" + if $device && $vm_conf->{$device}; + + my $worker = sub { + my $message = $device ? "to $device on" : 'as unused disk to'; + print "Importing disk '$source' $message VM $vmid ...\n"; + my ($unused_disk, $volid) = eval { + PVE::QemuServer::ImportDisk::do_import( + $source, $vmid, $storage, + {format => extract_param($params, 'format')}, + ) + }; + die "Importing disk failed: $@\n" if $@; + + if ($device) { + eval { + $update_vm_api->({ + vmid => $vmid, + $device => "$volid,$device_options", + }, 1); + }; + # Importing large disks takes a lot of time + # => don't remove disk automatically on option update error + die "Copying disk to $unused_disk succeeded but attaching it " + ."as $device and setting its options to $device_options " + ."failed: $@.\n Adjust them manually.\n" if $@; + } + print "Successfully imported disk '$source' as " + .($device ? $device : $unused_disk) . ": $volid'\n"; + }; + my $upid = $rpcenv->fork_worker('importdisk', $vmid, $authuser, $worker); + return $upid; + }}); + 1; diff --git a/PVE/CLI/qm.pm b/PVE/CLI/qm.pm index 282fa86..4a304ce 100755 --- a/PVE/CLI/qm.pm +++ b/PVE/CLI/qm.pm @@ -445,61 +445,6 @@ __PACKAGE__->register_method ({ return undef; }}); -__PACKAGE__->register_method ({ - name => 'importdisk', - path => 'importdisk', - method => 'POST', - description => "Import an external disk image as an unused disk in a VM. The - image format has to be supported by qemu-img(1).", - parameters => { - additionalProperties => 0, - properties => { - vmid => get_standard_option('pve-vmid', {completion => \&PVE::QemuServer::complete_vmid}), - source => { - description => 'Path to the disk image to import', - type => 'string', - optional => 0, - }, - storage => get_standard_option('pve-storage-id', { - description => 'Target storage ID', - completion => \&PVE::QemuServer::complete_storage, - optional => 0, - }), - format => { - type => 'string', - description => 'Target format', - enum => [ 'raw', 'qcow2', 'vmdk' ], - optional => 1, - }, - }, - }, - returns => { type => 'null'}, - code => sub { - my ($param) = @_; - - my $vmid = extract_param($param, 'vmid'); - my $source = extract_param($param, 'source'); - my $storeid = extract_param($param, 'storage'); - my $format = extract_param($param, 'format'); - - my $vm_conf = PVE::QemuConfig->load_config($vmid); - PVE::QemuConfig->check_lock($vm_conf); - die "$source: non-existent or non-regular file\n" if (! -f $source); - - my $storecfg = PVE::Storage::config(); - PVE::Storage::storage_check_enabled($storecfg, $storeid); - - my $target_storage_config = PVE::Storage::storage_config($storecfg, $storeid); - die "storage $storeid does not support vm images\n" - if !$target_storage_config->{content}->{images}; - - print "importing disk '$source' to VM $vmid ...\n"; - my ($drive_id, $volid) = PVE::QemuServer::ImportDisk::do_import($source, $vmid, $storeid, { format => $format }); - print "Successfully imported disk as '$drive_id:$volid'\n"; - - return undef; - }}); - __PACKAGE__->register_method ({ name => 'terminal', path => 'terminal', @@ -984,7 +929,7 @@ our $cmddef = { terminal => [ __PACKAGE__, 'terminal', ['vmid']], - importdisk => [ __PACKAGE__, 'importdisk', ['vmid', 'source', 'storage']], + importdisk => [ "PVE::API2::Qemu", 'importdisk', ['vmid', 'source', 'storage'], { node => $nodename }], importovf => [ __PACKAGE__, 'importovf', ['vmid', 'manifest', 'storage']], diff --git a/PVE/QemuServer/Drive.pm b/PVE/QemuServer/Drive.pm index 91c33f8..87727ea 100644 --- a/PVE/QemuServer/Drive.pm +++ b/PVE/QemuServer/Drive.pm @@ -201,6 +201,7 @@ my %wwn_fmt = ( }, ); + my $add_throttle_desc = sub { my ($key, $type, $what, $unit, $longunit, $minimum) = @_; my $d = { @@ -308,6 +309,19 @@ my $alldrive_fmt = { %wwn_fmt, }; +my %optional_file_drivdesc_base = %drivedesc_base; +$optional_file_drivdesc_base{file}{optional} = 1; # TODO verify +my $drive_options_fmt = { + %optional_file_drivdesc_base, # first bad thing here is the missing file stuff + %iothread_fmt, + %model_fmt, + %queues_fmt, + %scsiblock_fmt, + %ssd_fmt, + %wwn_fmt, +}; +PVE::JSONSchema::register_format('drive_options', $drive_options_fmt); + my $efidisk_fmt = { volume => { alias => 'file' }, file => { diff --git a/PVE/QemuServer/ImportDisk.pm b/PVE/QemuServer/ImportDisk.pm index 51ad52e..d89cd4d 100755 --- a/PVE/QemuServer/ImportDisk.pm +++ b/PVE/QemuServer/ImportDisk.pm @@ -38,7 +38,7 @@ sub do_import { } if ($drive_name) { - # should never happen as setting $drive_name is not exposed to public interface + # Exposed in importdisk API only die "cowardly refusing to overwrite existing entry: $drive_name\n" if $vm_conf->{$drive_name}; my $modified = {}; # record what $option we modify -- 2.20.1 ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [pve-devel] [PATCH qemu-server 3/3] Move importdisk from qm to API 2020-07-07 10:04 ` [pve-devel] [PATCH qemu-server 3/3] Move importdisk from qm to API Dominic Jäger @ 2020-07-29 13:00 ` Aaron Lauterer 2020-07-30 10:38 ` Dominic Jäger 0 siblings, 1 reply; 8+ messages in thread From: Aaron Lauterer @ 2020-07-29 13:00 UTC (permalink / raw) To: Proxmox VE development discussion, Dominic Jäger Some small nits inline, mainly regarding the wording of descriptions and messages. One thing I noticed is with multi line strings. The concat `.` should probably be at the end of the previous line if you split it up but I would prefer to have long lines instead of it being split. Long lines make it easier to search for a certain (log) message in the code with grep. On 7/7/20 12:04 PM, Dominic Jäger wrote: > Additionally, add parameters that enable directly (avoiding the intermediate > step as unused disk) attaching the disk to a bus/device with all known disk > options. > > Required to create a GUI for importdisk. > > Signed-off-by: Dominic Jäger <d.jaeger@proxmox.com> > --- > PVE/API2/Qemu.pm | 113 ++++++++++++++++++++++++++++++++++- > PVE/CLI/qm.pm | 57 +----------------- > PVE/QemuServer/Drive.pm | 14 +++++ > PVE/QemuServer/ImportDisk.pm | 2 +- > 4 files changed, 127 insertions(+), 59 deletions(-) > > diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm > index b33359d..6efbbe3 100644 > --- a/PVE/API2/Qemu.pm > +++ b/PVE/API2/Qemu.pm > @@ -24,6 +24,7 @@ use PVE::QemuServer; > use PVE::QemuServer::Drive; > use PVE::QemuServer::CPUConfig; > use PVE::QemuServer::Monitor qw(mon_cmd); > +use PVE::QemuServer::ImportDisk qw(do_import); > use PVE::QemuMigrate; > use PVE::RPCEnvironment; > use PVE::AccessControl; > @@ -45,8 +46,6 @@ BEGIN { > } > } > > -use Data::Dumper; # fixme: remove > - > use base qw(PVE::RESTHandler); > > my $opt_force_description = "Force physical removal. Without this, we simple remove the disk from the config file and create an additional configuration entry called 'unused[n]', which contains the volume ID. Unlink of unused[n] always cause physical removal."; > @@ -4252,4 +4251,114 @@ __PACKAGE__->register_method({ > return PVE::QemuServer::Cloudinit::dump_cloudinit_config($conf, $param->{vmid}, $param->{type}); > }}); > > +__PACKAGE__->register_method ({ > + name => 'importdisk', > + path => '{vmid}/importdisk', > + method => 'POST', > + protected => 1, # for worker upid file > + proxyto => 'node', > + description => "Import an external disk image into a VM. " > + ."The image format has to be supported by qemu-img.", > + permissions => { > + check => [ 'and', > + [ 'perm', '/storage/{storage}', ['Datastore.AllocateTemplate']], > + [ 'perm', '/storage/{storage}', ['Datastore.AllocateSpace']], > + [ 'perm', '/vms/{vmid}', ['VM.Allocate']], > + ], > + }, > + parameters => { > + additionalProperties => 0, > + properties => { > + node => get_standard_option('pve-node'), > + vmid => get_standard_option('pve-vmid', > + {completion => \&PVE::QemuServer::complete_vmid}), > + source => { > + description => 'Path of the disk image that should be imported', What about: "Path to the disk image to import". > + type => 'string', > + }, > + device => { > + type => 'string', > + description => "What device the imported image should be " "Bus/Device type of the new disk" > + ."(e.g. 'ide0', 'scsi2'). Will add the image as unused disk " > + ."if omitted.", > + enum => [PVE::QemuServer::Drive::valid_drive_names()], > + optional => 1, > + }, > + device_options => { > + type => 'string', > + format => 'drive_options', > + description => "What options to set for the device " "Options to set for the new disk" > + ."(e.g. 'discard=on,backup=0')", > + optional => 1, > + }, > + storage => get_standard_option('pve-storage-id', { > + description => 'Which storage to use for the imported image.', "The storage to which the image will be imported to." > + completion => \&PVE::QemuServer::complete_storage, > + }), > + format => { > + type => 'string', > + description => 'Target format.', > + enum => [ 'raw', 'qcow2', 'vmdk' ], > + optional => 1, > + }, > + digest => get_standard_option('pve-config-digest'), > + }, > + }, > + returns => { type => 'string'}, > + code => sub { > + my ($params) = @_; > + my $rpcenv = PVE::RPCEnvironment::get(); > + my $authuser = $rpcenv->get_user(); > + > + my $vmid = extract_param($params, 'vmid'); > + my $source = extract_param($params, 'source'); > + my $digest = extract_param($params, 'digest'); > + my $device_options = extract_param($params, 'device_options'); > + my $device = extract_param($params, 'device'); > + my $storage = extract_param($params, 'storage'); > + my $vm_conf = PVE::QemuConfig->load_config($vmid); > + > + die "Could not import because source parameter is an empty string\n" > + if ($source eq ""); > + die "Could not import because source '$source' is not an absolute path\n" > + if $source !~ m!/!; > + die "Could not import because source '$source' does not exist" > + if !-e $source; > + die "VM $vmid config checksum missmatch (file change by other user?)\n" > + if $digest && $digest ne $vm_conf->{digest}; > + die "Could not import because device $device is already in use in " > + ."VM $vmid. Choose a different device!\n" > + if $device && $vm_conf->{$device}; > + > + my $worker = sub { > + my $message = $device ? "to $device on" : 'as unused disk to'; wouldn't it make more sense to phrase it "to $device for" and 'as unused disk for'? It will result in something like "importing disk 'myfile.qcow' as unused disk for VM XXX" > + print "Importing disk '$source' $message VM $vmid ...\n"; > + my ($unused_disk, $volid) = eval { > + PVE::QemuServer::ImportDisk::do_import( > + $source, $vmid, $storage, > + {format => extract_param($params, 'format')}, > + ) > + }; > + die "Importing disk failed: $@\n" if $@; > + > + if ($device) { > + eval { > + $update_vm_api->({ > + vmid => $vmid, > + $device => "$volid,$device_options", > + }, 1); > + }; > + # Importing large disks takes a lot of time > + # => don't remove disk automatically on option update error > + die "Copying disk to $unused_disk succeeded but attaching it " > + ."as $device and setting its options to $device_options " > + ."failed: $@.\n Adjust them manually.\n" if $@; > + } > + print "Successfully imported disk '$source' as " > + .($device ? $device : $unused_disk) . ": $volid'\n"; > + }; > + my $upid = $rpcenv->fork_worker('importdisk', $vmid, $authuser, $worker); > + return $upid; > + }}); > + > 1; > diff --git a/PVE/CLI/qm.pm b/PVE/CLI/qm.pm > index 282fa86..4a304ce 100755 > --- a/PVE/CLI/qm.pm > +++ b/PVE/CLI/qm.pm > @@ -445,61 +445,6 @@ __PACKAGE__->register_method ({ > return undef; > }}); > > -__PACKAGE__->register_method ({ > - name => 'importdisk', > - path => 'importdisk', > - method => 'POST', > - description => "Import an external disk image as an unused disk in a VM. The > - image format has to be supported by qemu-img(1).", > - parameters => { > - additionalProperties => 0, > - properties => { > - vmid => get_standard_option('pve-vmid', {completion => \&PVE::QemuServer::complete_vmid}), > - source => { > - description => 'Path to the disk image to import', > - type => 'string', > - optional => 0, > - }, > - storage => get_standard_option('pve-storage-id', { > - description => 'Target storage ID', > - completion => \&PVE::QemuServer::complete_storage, > - optional => 0, > - }), > - format => { > - type => 'string', > - description => 'Target format', > - enum => [ 'raw', 'qcow2', 'vmdk' ], > - optional => 1, > - }, > - }, > - }, > - returns => { type => 'null'}, > - code => sub { > - my ($param) = @_; > - > - my $vmid = extract_param($param, 'vmid'); > - my $source = extract_param($param, 'source'); > - my $storeid = extract_param($param, 'storage'); > - my $format = extract_param($param, 'format'); > - > - my $vm_conf = PVE::QemuConfig->load_config($vmid); > - PVE::QemuConfig->check_lock($vm_conf); > - die "$source: non-existent or non-regular file\n" if (! -f $source); > - > - my $storecfg = PVE::Storage::config(); > - PVE::Storage::storage_check_enabled($storecfg, $storeid); > - > - my $target_storage_config = PVE::Storage::storage_config($storecfg, $storeid); > - die "storage $storeid does not support vm images\n" > - if !$target_storage_config->{content}->{images}; > - > - print "importing disk '$source' to VM $vmid ...\n"; > - my ($drive_id, $volid) = PVE::QemuServer::ImportDisk::do_import($source, $vmid, $storeid, { format => $format }); > - print "Successfully imported disk as '$drive_id:$volid'\n"; > - > - return undef; > - }}); > - > __PACKAGE__->register_method ({ > name => 'terminal', > path => 'terminal', > @@ -984,7 +929,7 @@ our $cmddef = { > > terminal => [ __PACKAGE__, 'terminal', ['vmid']], > > - importdisk => [ __PACKAGE__, 'importdisk', ['vmid', 'source', 'storage']], > + importdisk => [ "PVE::API2::Qemu", 'importdisk', ['vmid', 'source', 'storage'], { node => $nodename }], > > importovf => [ __PACKAGE__, 'importovf', ['vmid', 'manifest', 'storage']], > > diff --git a/PVE/QemuServer/Drive.pm b/PVE/QemuServer/Drive.pm > index 91c33f8..87727ea 100644 > --- a/PVE/QemuServer/Drive.pm > +++ b/PVE/QemuServer/Drive.pm > @@ -201,6 +201,7 @@ my %wwn_fmt = ( > }, > ); > > + > my $add_throttle_desc = sub { > my ($key, $type, $what, $unit, $longunit, $minimum) = @_; > my $d = { > @@ -308,6 +309,19 @@ my $alldrive_fmt = { > %wwn_fmt, > }; > > +my %optional_file_drivdesc_base = %drivedesc_base; s/drivdesc/drivedesc/ or maybe even to drive_desc > +$optional_file_drivdesc_base{file}{optional} = 1; # TODO verify > +my $drive_options_fmt = { > + %optional_file_drivdesc_base, # first bad thing here is the missing file stuff > + %iothread_fmt, > + %model_fmt, > + %queues_fmt, > + %scsiblock_fmt, > + %ssd_fmt, > + %wwn_fmt, > +}; > +PVE::JSONSchema::register_format('drive_options', $drive_options_fmt); > + > my $efidisk_fmt = { > volume => { alias => 'file' }, > file => { > diff --git a/PVE/QemuServer/ImportDisk.pm b/PVE/QemuServer/ImportDisk.pm > index 51ad52e..d89cd4d 100755 > --- a/PVE/QemuServer/ImportDisk.pm > +++ b/PVE/QemuServer/ImportDisk.pm > @@ -38,7 +38,7 @@ sub do_import { > } > > if ($drive_name) { > - # should never happen as setting $drive_name is not exposed to public interface > + # Exposed in importdisk API only > die "cowardly refusing to overwrite existing entry: $drive_name\n" if $vm_conf->{$drive_name}; > > my $modified = {}; # record what $option we modify > ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [pve-devel] [PATCH qemu-server 3/3] Move importdisk from qm to API 2020-07-29 13:00 ` Aaron Lauterer @ 2020-07-30 10:38 ` Dominic Jäger 0 siblings, 0 replies; 8+ messages in thread From: Dominic Jäger @ 2020-07-30 10:38 UTC (permalink / raw) To: pve-devel On Wed, Jul 29, 2020 at 03:00:41PM +0200, Aaron Lauterer wrote: > Some small nits inline, mainly regarding the wording of descriptions and messages. > > One thing I noticed is with multi line strings. > The concat `.` should probably be at the end of the previous line if you split it up I intially placed it in front because the Perl style guide has && in front in a multi-line example. However, most of this file has dots in the end and the guide forbids mixing only, so I changed it. > but I would prefer to have long lines instead of it being split. Long lines > make it easier to search for a certain (log) message in the code with grep. Fabian G. just told me that 80 character line length limit also holds for comments. > > + properties => { > > + node => get_standard_option('pve-node'), > > + vmid => get_standard_option('pve-vmid', > > + {completion => \&PVE::QemuServer::complete_vmid}), > > + source => { > > + description => 'Path of the disk image that should be imported', > > What about: "Path to the disk image to import". > > > + type => 'string', > > + }, > > + device => { > > + type => 'string', > > + description => "What device the imported image should be " > > "Bus/Device type of the new disk" > > > + ."(e.g. 'ide0', 'scsi2'). Will add the image as unused disk " > > + ."if omitted.", > > + enum => [PVE::QemuServer::Drive::valid_drive_names()], > > + optional => 1, > > + }, > > + device_options => { > > + type => 'string', > > + format => 'drive_options', > > + description => "What options to set for the device " > > "Options to set for the new disk" > > > + ."(e.g. 'discard=on,backup=0')", > > + optional => 1, > > + }, > > + storage => get_standard_option('pve-storage-id', { > > + description => 'Which storage to use for the imported image.', > > "The storage to which the image will be imported to." Fits the rest of the descriptions better => Changed all of them. Thank you :) > > > + my $worker = sub { > > + my $message = $device ? "to $device on" : 'as unused disk to'; > > wouldn't it make more sense to phrase it "to $device for" and 'as unused disk for'? Didn't include this (yet) because I actually don't think so. The idea was 1) Import sth to sth => "Importing disk 'myfile.qcow2' to VM100." How are we importing it? as unused disk "Importing disk 'myfile.qcow2' as unused disk to VM100." 2) Import sth to sth => "Importing disk 'myfile.qcow2' to scsi0." Where is scsi0? on VM100 "Importing disk 'myfile.qcow2' to scsi0 on VM100." > It will result in something like "importing disk 'myfile.qcow' as unused disk for VM XXX" I think there should be a "to" somewhere. Would for 2) "Importing disk 'myfile.qcow2' to VM100 as scsi0" make sense, maybe? > > > +my %optional_file_drivdesc_base = %drivedesc_base; > > s/drivdesc/drivedesc/ or maybe even to drive_desc > > > +$optional_file_drivdesc_base{file}{optional} = 1; # TODO verify > > +my $drive_options_fmt = { > > + %optional_file_drivdesc_base, # first bad thing here is the missing file stuff > > + %iothread_fmt, > > + %model_fmt, > > + %queues_fmt, > > + %scsiblock_fmt, > > + %ssd_fmt, > > + %wwn_fmt, > > +}; > > +PVE::JSONSchema::register_format('drive_options', $drive_options_fmt); > > + Thank you, changed it to drivedesc :) It's drivedesc in the rest of the file, without underscore. ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2020-07-30 10:38 UTC | newest] Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2020-07-07 10:04 [pve-devel] [PATCH widget-toolkit 0/1] Add GUI for importdisk Dominic Jäger 2020-07-07 10:04 ` [pve-devel] [PATCH widget-toolkit 1/3] EditWindow: Change url to 'importdisk' for import Dominic Jäger 2020-07-07 10:04 ` [pve-devel] [PATCH manager 2/3] Hardware View: Add GUI for importdisk Dominic Jäger 2020-07-29 13:25 ` Aaron Lauterer 2020-07-30 10:20 ` Dominic Jäger 2020-07-07 10:04 ` [pve-devel] [PATCH qemu-server 3/3] Move importdisk from qm to API Dominic Jäger 2020-07-29 13:00 ` Aaron Lauterer 2020-07-30 10:38 ` Dominic Jäger
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox