[pve-devel] qemu-server : cloning + storage migration patches
Here my last patches for template/vm cloning + storage migration patches changelog : Rebase on last git. ___ pve-devel mailing list pve-devel@pve.proxmox.com http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH 1/5] add qemu_img_convert
also work with snapshot as source for qcow2,rbd,sheepdog. Signed-off-by: Alexandre Derumier aderum...@odiso.com --- PVE/QemuServer.pm | 51 +++ 1 file changed, 51 insertions(+) diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index fb672eb..b55a68e 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -4478,4 +4478,55 @@ sub is_template { return 2 if $baseimagecount $totalvolumecount; #semi-template } +sub qemu_img_convert { +my ($src_volid, $dst_volid, $snapname) = @_; + +my $storecfg = PVE::Storage::config(); +my ($src_storeid, $src_volname) = PVE::Storage::parse_volume_id($src_volid, 1); +my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid, 1); + +if ($src_storeid $dst_storeid) { + my $src_scfg = PVE::Storage::storage_config($storecfg, $src_storeid); + my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid); + + my $src_format = qemu_img_format($src_scfg, $src_volname); + my $dst_format = qemu_img_format($dst_scfg, $dst_volname); + + my $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname); + my $dst_path = PVE::Storage::path($storecfg, $dst_volid); + + my $cmd = []; + push @$cmd, '/usr/bin/qemu-img', 'convert', '-t', 'writeback', '-p', '-C'; + push @$cmd, '-s', $snapname if($snapname $src_format eq qcow2); + push @$cmd, '-f', $src_format, '-O', $dst_format, $src_path, $dst_path; + + my $parser = sub { + my $line = shift; + print $line.\n; + }; + + eval { run_command($cmd, timeout = undef, errfunc = sub {}, outfunc = $parser); }; + my $err = $@; + die copy failed: $err if $err; +} +} + +sub qemu_img_format { +my ($scfg, $volname) = @_; + +if ($scfg-{path} $volname =~ m/\.(raw|qcow2|qed|vmdk)$/){ + return $1; +} +elsif ($scfg-{type} eq 'nexenta' || $scfg-{type} eq 'iscsidirect'){ + return iscsi; +} +elsif ($scfg-{type} eq 'lvm' || $scfg-{type} eq 'iscsi'){ + return host_device; +} +#sheepdog,rbd,or other qemu block driver +else{ + return $scfg-{type}; +} +} + 1; -- 1.7.10.4 ___ pve-devel mailing list pve-devel@pve.proxmox.com http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH 2/5] add qemu_drive_mirror
Signed-off-by: Alexandre Derumier aderum...@odiso.com --- PVE/QemuServer.pm | 66 + 1 file changed, 66 insertions(+) diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index b55a68e..99761a4 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -4529,4 +4529,70 @@ sub qemu_img_format { } } +sub qemu_drive_mirror { +my ($vmid, $drive, $dst_volid, $vmiddst, $maxwait) = @_; + +my $count = 1; +my $old_len = 0; +my $frozen = undef; + +my $storecfg = PVE::Storage::config(); +my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid, 1); + +if ($dst_storeid) { + my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid); + + my $format = undef; +if ($dst_volname =~ m/\.(raw|qcow2)$/){ + $format = $1; + } + + my $dst_path = PVE::Storage::path($storecfg, $dst_volid); + + if($format){ + PVE::QemuServer::vm_mon_cmd($vmid, drive-mirror, device = drive-$drive, mode = existing, sync = full, target = $dst_path, format = $format); + }else{ + PVE::QemuServer::vm_mon_cmd($vmid, drive-mirror, device = drive-$drive, mode = existing, sync = full, target = $dst_path); + } + eval{ + while (1) { + my $stats = PVE::QemuServer::vm_mon_cmd($vmid, query-block-jobs); + my $stat = @$stats[0]; + die error job is not mirroring if $stat-{type} ne mirror; + print copy .$stat-{offset}. bytes of .$stat-{len}. bytes\n; + last if ($stat-{len} == $stat-{offset}); + if ($old_len == $stat-{offset}) { + if ($maxwait $count $maxwait) { + # if writes to disk occurs the disk needs to be freezed + # to be able to complete the migration + vm_suspend($vmid,1); + $count = 0; + $frozen = 1; + }else { + $count++ unless $frozen; + } + } + elsif ($frozen) { + vm_resume($vmid,1); + $count = 0; + } + $old_len = $stat-{offset}; + sleep 1; + } + }; + if ($@) { + PVE::QemuServer::vm_mon_cmd($vmid, block-job-cancel, device = drive-$drive); + die mirroring error: $@; + } + + if($vmiddst != $vmid){ + #if we clone a disk for a new target vm, we don't switch the disk + PVE::QemuServer::vm_mon_cmd($vmid, block-job-cancel, device = drive-$drive); + }else{ + #if source and destination are on the same guest + PVE::QemuServer::vm_mon_cmd($vmid, block-job-complete, device = drive-$drive); + } +} +} + 1; -- 1.7.10.4 ___ pve-devel mailing list pve-devel@pve.proxmox.com http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH 4/5] extend qm create to create clones
linked clone (works on template without snapname, works on vm with snapname only) - qm create vmid --clonefrom vmidsrc [--snapname snap] [--clonemode clone] copy clone : dest storage = source storage -- qm create vmid --clonefrom vmidsrc [--snapname snap] --clonemode copy copy clone : dest storage != source storage -- qm create vmid --clonefrom vmidsrc [--snapname snap] --clonemode copy --virtio0 storeid:[fmt] (--virtio0 local:raw --virtio1 rbdstorage: --virtio2:nfsstorage:qcow2) others config params can be add --- qm create vmid --clonefrom vmidsrc [--snapname snap] [--clonemode clone] --memory 2048 --name newvmname Signed-off-by: Alexandre Derumier aderum...@odiso.com --- PVE/API2/Qemu.pm | 102 +- 1 file changed, 94 insertions(+), 8 deletions(-) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 10437ba..909452a 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -353,6 +353,21 @@ __PACKAGE__-register_method({ type = 'string', format = 'pve-poolid', description = Add the VM to the specified pool., }, +clonefrom = get_standard_option('pve-vmid', { +description = Template Vmid., +optional = 1, +}), + clonemode = { + description = clone|copy, + type = 'string', + enum = [ 'clone', 'copy' ], + optional = 1, + }, +snapname = get_standard_option('pve-snapshot-name', { +description = Template Snapshot Name., +optional = 1, +}), + }), }, returns = { @@ -379,6 +394,12 @@ __PACKAGE__-register_method({ my $pool = extract_param($param, 'pool'); + my $clonefrom = extract_param($param, 'clonefrom'); + + my $clonemode = extract_param($param, 'clonemode'); + + my $snapname = extract_param($param, 'snapname'); + my $filename = PVE::QemuServer::config_file($vmid); my $storecfg = PVE::Storage::config(); @@ -395,17 +416,17 @@ __PACKAGE__-register_method({ if (!$archive) { $resolve_cdrom_alias($param); - $check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage); + $check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param, $storage) if !$clonefrom; $check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]); - foreach my $opt (keys %$param) { if (PVE::QemuServer::valid_drivename($opt)) { my $drive = PVE::QemuServer::parse_drive($opt, $param-{$opt}); raise_param_exc({ $opt = unable to parse drive options }) if !$drive; - - PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive); - $param-{$opt} = PVE::QemuServer::print_drive($vmid, $drive); + if(!$clonefrom){ + PVE::QemuServer::cleanup_drive_path($opt, $storecfg, $drive); + $param-{$opt} = PVE::QemuServer::print_drive($vmid, $drive); + } } } @@ -468,13 +489,74 @@ __PACKAGE__-register_method({ my $realcmd = sub { my $vollist = []; + my $conf = undef; + my $running = undef; + my $parentconf = undef; + my $oldconf = undef; - my $conf = $param; + if($clonefrom){ + my $parentparam = {}; - eval { + $running = PVE::QemuServer::check_running($clonefrom); + + $clonemode = 'clone' if !$clonemode; + + $parentconf = PVE::QemuServer::load_config($clonefrom); + $oldconf = PVE::QemuServer::load_config($clonefrom); #fixme, how to copy the parentconf hashref ? + + die you can't clone, the source vm is locked if $parentconf-{lock}; - $vollist = $create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $param, $storage); + if($snapname){ + die snapshot don't exist if !$parentconf-{snapshots}-{$snapname}; + die clonemode $clonemode feature is not available if !PVE::QemuServer::has_feature($clonemode, $parentconf, $storecfg, $snapname); + $parentparam = $parentconf-{snapshots}-{$snapname}; + delete $parentparam-{snaptime}; + + }else{ + + die clonemode $clonemode feature is not available if
[pve-devel] [PATCH 5/6] qemu right click menu : add vm convert to template
Signed-off-by: Alexandre Derumier aderum...@odiso.com --- www/manager/qemu/CmdMenu.js | 20 1 file changed, 20 insertions(+) diff --git a/www/manager/qemu/CmdMenu.js b/www/manager/qemu/CmdMenu.js index f80922b..3dedc43 100644 --- a/www/manager/qemu/CmdMenu.js +++ b/www/manager/qemu/CmdMenu.js @@ -90,6 +90,26 @@ Ext.define('PVE.qemu.CmdMenu', { } }, { + text: gettext('Convert To Template'), + icon: '/pve2/images/forward.png', + handler: function() { + var msg = Ext.String.format(gettext(Do you really want convert VM {0} to template (You'll can use the VM anymore)?), vmid); + Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) { + if (btn !== 'yes') { + return; + } + + PVE.Utils.API2Request({ +url: '/nodes/' + nodename + '/qemu/' + vmid + '/template', +method: 'POST', +failure: function(response, opts) { + Ext.Msg.alert('Error', response.htmlStatus); +} + }); + }); + } + }, + { text: gettext('Console'), icon: '/pve2/images/display.png', handler: function() { -- 1.7.10.4 ___ pve-devel mailing list pve-devel@pve.proxmox.com http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH 3/6] add clone vm option in qemu vm right click menu
fixme: add a new icon Signed-off-by: Alexandre Derumier aderum...@odiso.com --- www/manager/qemu/CmdMenu.js | 12 1 file changed, 12 insertions(+) diff --git a/www/manager/qemu/CmdMenu.js b/www/manager/qemu/CmdMenu.js index c61d1c0..f80922b 100644 --- a/www/manager/qemu/CmdMenu.js +++ b/www/manager/qemu/CmdMenu.js @@ -78,6 +78,18 @@ Ext.define('PVE.qemu.CmdMenu', { } }, { + text: gettext('Clone To VM'), + icon: '/pve2/images/forward.png', + handler: function() { + var win = Ext.create('PVE.window.Clone', { + snapname: 'current', + nodename: nodename, + vmid: vmid, + }); + win.show(); + } + }, + { text: gettext('Console'), icon: '/pve2/images/display.png', handler: function() { -- 1.7.10.4 ___ pve-devel mailing list pve-devel@pve.proxmox.com http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH 1/6] add clone panel form
Signed-off-by: Alexandre Derumier aderum...@odiso.com --- www/manager/Makefile |1 + www/manager/qemu/Clone.js | 268 + 2 files changed, 269 insertions(+) create mode 100644 www/manager/qemu/Clone.js diff --git a/www/manager/Makefile b/www/manager/Makefile index a4feb40..9e84ef1 100644 --- a/www/manager/Makefile +++ b/www/manager/Makefile @@ -102,6 +102,7 @@ JSSRC= \ qemu/ScsiHwEdit.js \ qemu/Options.js \ qemu/Snapshot.js\ + qemu/Clone.js \ qemu/SnapshotTree.js\ qemu/Config.js \ qemu/CreateWizard.js\ diff --git a/www/manager/qemu/Clone.js b/www/manager/qemu/Clone.js new file mode 100644 index 000..70b3a99 --- /dev/null +++ b/www/manager/qemu/Clone.js @@ -0,0 +1,268 @@ +Ext.define('PVE.window.Clone', { +extend: 'Ext.window.Window', + +resizable: false, + + +create_clone: function(snapname, name, newvmid, clonemode, storage, format, diskarray) { + var me = this; + +params = { name: name, clonefrom: me.vmid, vmid: newvmid, clonemode: clonemode }; + +if (snapname snapname !== 'current') { +params.snapname = snapname; +} + + if (clonemode === 'copy' storage){ + + Ext.Array.each(diskarray, function(disk) { + var myformat = format ? format : ''; + params[disk] = storage + ':' + myformat; + }); + } + + + + PVE.Utils.API2Request({ + params: params, + url: '/nodes/' + me.nodename + '/qemu', + waitMsgTarget: me, + method: 'POST', + failure: function(response, opts) { + Ext.Msg.alert('Error', response.htmlStatus); + }, + success: function(response, options) { + me.close(); + } + }); + +}, + +compute_sel1: function(clonefeature, istemplate, snapname) { +var me = this; +var list = []; +list.push(['copy', 'Copy Clone']); + + if((clonefeature istemplate === 1 snapname === 'current') || (clonefeature !istemplate snapname !== 'current')){ + list.push(['clone', 'Linked Clone']); + } +me.kv1.store.loadData(list); + + if((clonefeature istemplate === 1 snapname === 'current') || (clonefeature !istemplate snapname !== 'current')){ + me.kv1.setValue('clone'); + }else{ + me.kv1.setValue('copy'); + } +}, + +initComponent : function() { + var me = this; + + var diskarray = []; + + if (!me.nodename) { + throw no node name specified; + } + + if (!me.vmid) { + throw no VM ID specified; + } + + me.snapshotsel = Ext.create('PVE.form.SnapshotSelector', { + name: 'snapname', + fieldLabel: 'Snapshot', +nodename: me.nodename, +vmid: me.vmid, +disabled: false, +hidden: me.istemplate ? true : false, + allowBlank: false, + value : me.snapname, + listeners: { + change: function(f, value) { + + var clonefeature; + var snapname = value; + //check if linked clone feature is available + var params = { feature: 'clone' }; + if (value !== 'current') { + params.snapname = snapname; + } + + PVE.Utils.API2Request({ + waitMsgTarget: me, + url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/feature', + params: params, + method: 'GET', + success: function(response, options) { + var res = response.result.data; + + if (res === 1) { + clonefeature = 1; + } + me.compute_sel1(clonefeature, me.istemplate, snapname); + } + }); + } + } + }); + + var items = []; + + items.push(me.snapshotsel); + + items.push( + { +xtype: 'pveVMIDSelector', +name: 'newvmid', +value: '', +loadNextFreeVMID: true, +validateExists: false +}, + { + xtype: 'textfield', + name: 'name', +
[pve-devel] [PATCH 6/6] add hdmove panel
Signed-off-by: Alexandre Derumier aderum...@odiso.com --- www/manager/Makefile |1 + www/manager/qemu/HDMove.js | 139 ++ www/manager/qemu/HardwareView.js | 40 ++- 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 www/manager/qemu/HDMove.js diff --git a/www/manager/Makefile b/www/manager/Makefile index b2763da..48bdf12 100644 --- a/www/manager/Makefile +++ b/www/manager/Makefile @@ -97,6 +97,7 @@ JSSRC= \ qemu/CDEdit.js \ qemu/HDEdit.js \ qemu/HDResize.js\ + qemu/HDMove.js \ qemu/DisplayEdit.js \ qemu/KeyboardEdit.js\ qemu/HardwareView.js\ diff --git a/www/manager/qemu/HDMove.js b/www/manager/qemu/HDMove.js new file mode 100644 index 000..837467a --- /dev/null +++ b/www/manager/qemu/HDMove.js @@ -0,0 +1,139 @@ +Ext.define('PVE.window.HDMove', { +extend: 'Ext.window.Window', + +resizable: false, + + +move_disk: function(disk, storage, format) { + var me = this; + +params = { disk: disk, storage: storage }; + +if (format) { +params.format = format; +} + + PVE.Utils.API2Request({ + params: params, + url: '/nodes/' + me.nodename + '/qemu/' + me.vmid + '/move', + waitMsgTarget: me, + method: 'PUT', + failure: function(response, opts) { + Ext.Msg.alert('Error', response.htmlStatus); + }, + success: function(response, options) { + me.close(); + } + }); + +}, + +initComponent : function() { + var me = this; + + var diskarray = []; + + if (!me.nodename) { + throw no node name specified; + } + + if (!me.vmid) { + throw no VM ID specified; + } + +var items = [ +{ +xtype: 'displayfield', +name: 'disk', +value: me.disk, +fieldLabel: 'Disk', +vtype: 'StorageId', +allowBlank: false +}, + +]; + +me.hdstoragesel = Ext.create('PVE.form.StorageSelector', { +name: 'hdstorage', +nodename: me.nodename, +fieldLabel: 'Target Storage', +storageContent: 'images', +autoSelect: me.insideWizard, +allowBlank: true, +disabled: false, +hidden: false, +listeners: { +change: function(f, value) { +var rec = f.store.getById(value); + if (rec.data.type === 'iscsi') { +me.formatsel.setValue('raw'); +me.formatsel.setDisabled(true); +} else if (rec.data.type === 'lvm' || + rec.data.type === 'rbd' || + rec.data.type === 'sheepdog' || + rec.data.type === 'nexenta' +) { +me.formatsel.setValue('raw'); +me.formatsel.setDisabled(true); +} else { +me.formatsel.setDisabled(false); +} + +} +} + + }); + + me.formatsel = Ext.create('PVE.form.DiskFormatSelector', { + name: 'diskformat', + fieldLabel: gettext('Format'), + value: 'raw', +disabled: true, +hidden: false, + allowBlank: false + }); + + + items.push(me.hdstoragesel); + items.push(me.formatsel); + + me.formPanel = Ext.create('Ext.form.Panel', { + bodyPadding: 10, + border: false, + fieldDefaults: { + labelWidth: 100, + anchor: '100%' + }, + items: items + }); + + var form = me.formPanel.getForm(); + + var submitBtn; + + me.title = Move disk; + submitBtn = Ext.create('Ext.Button', { + text: gettext('Move'), + handler: function() { + if (form.isValid()) { + var values = form.getValues(); + me.move_disk(me.disk, values.hdstorage, values.diskformat); + } + } + }); + + Ext.apply(me, { + modal: true, + width: 350, + border: false, + layout: 'fit', + buttons: [ submitBtn ], + items: [ me.formPanel ] + }); + + + me.callParent(); + + +}
[pve-devel] pve-manager : cloning migration patches
changelog : rebase on last git ___ pve-devel mailing list pve-devel@pve.proxmox.com http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
[pve-devel] [PATCH 2/6] add snapshot selector
Signed-off-by: Alexandre Derumier aderum...@odiso.com --- www/manager/Makefile |1 + www/manager/form/SnapshotSelector.js | 64 ++ 2 files changed, 65 insertions(+) create mode 100644 www/manager/form/SnapshotSelector.js diff --git a/www/manager/Makefile b/www/manager/Makefile index 9e84ef1..7da2234 100644 --- a/www/manager/Makefile +++ b/www/manager/Makefile @@ -45,6 +45,7 @@ JSSRC= \ form/LanguageSelector.js\ form/DisplaySelector.js \ form/CacheTypeSelector.js \ + form/SnapshotSelector.js\ form/ContentTypeSelector.js \ form/DayOfWeekSelector.js \ form/BackupModeSelector.js \ diff --git a/www/manager/form/SnapshotSelector.js b/www/manager/form/SnapshotSelector.js new file mode 100644 index 000..15eb65a --- /dev/null +++ b/www/manager/form/SnapshotSelector.js @@ -0,0 +1,64 @@ +Ext.define('PVE.form.SnapshotSelector', { +extend: 'PVE.form.ComboGrid', +alias: ['widget.PVE.form.SnapshotSelector'], + +loadStore: function(nodename, vmid) { + var me = this; + + if (!nodename) { + return; + } + + me.nodename = nodename; + +if (!vmid) { + return +} + + me.vmid = vmid; + + me.store.setProxy({ + type: 'pve', + url: '/api2/json/nodes/' + me.nodename + '/qemu/' + me.vmid +'/snapshot' + }); + + me.store.load(); +}, + +initComponent: function() { + var me = this; + +if (!me.nodename) { +throw no node name specified; +} + +if (!me.vmid) { +throw no VM ID specified; +} + + var store = Ext.create('Ext.data.Store', { + fields: [ 'name'], + filterOnLoad: true, + }); + + Ext.apply(me, { + store: store, + valueField: 'name', + displayField: 'name', +listConfig: { + columns: [ + { + header: 'Snapshot', + dataIndex: 'name', + hideable: false, + flex: 1 + }, + ] + } + }); + +me.callParent(); + + me.loadStore(me.nodename, me.vmid); +} +}); -- 1.7.10.4 ___ pve-devel mailing list pve-devel@pve.proxmox.com http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
Re: [pve-devel] qemu-server : cloning + storage migration patches
Feel free to adapt/change patches. I'm using them in production since 1 month without problem. (I think my qm create code should be cleaned) - Mail original - De: Dietmar Maurer diet...@proxmox.com À: Alexandre Derumier aderum...@odiso.com, pve-devel@pve.proxmox.com Envoyé: Mardi 2 Avril 2013 12:27:57 Objet: RE: [pve-devel] qemu-server : cloning + storage migration patches Thanks. This is on top of the task list now. Here my last patches for template/vm cloning + storage migration patches changelog : Rebase on last git. ___ pve-devel mailing list pve-devel@pve.proxmox.com http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel