[pve-devel] [PATCH] Add CT suspend/resume to PVE API

Daniel Hunsaker danhunsaker at gmail.com
Fri Oct 3 05:03:02 CEST 2014


From: Dan Hunsaker <danhunsaker at gmail.com>

As discussed in a previous thread, following is a patch to support container
suspend (via vzctl chkpnt) and resume (via vzctl restore).

- Added /nodes/{node}/openvz/{vmid}/status/suspend to API
- Added /nodes/{node}/openvz/{vmid}/status/resume to API
- Adapted vm_suspend/vm_resume from PVE/QemuServer.pm into PVE/OpenVZ.pm
  - Removed locking since vzctl already does this for us, and the locks
    conflict with each other (container already locked)
  - Changed monitor commands to run_command(vzctl) calls
  - Refuse to suspend if CT is offline
  - Refuse to resume if CT is online
  - vzctl does these checks as well, but it doesn't really hurt to have them

This was great, but there were artifacts in the web UI - specifically, the
task descriptions were unformatted.  So, I moved over to www/manager/Utils.js:

- Added descriptions for vzsuspend and vzresume tasks in web UI

And while I was working with the web UI anyway:

- Added suspend/resume options to CmdMenu for both OpenVZ and QEMU guests
  - Confirm suspend before proceeding
  - No confirm on resume, since it's a startup action
- Fixed OpenVZ CmdMenu shutdown and stop confirmation prompts to refer to CTs

I considered adding these options to the toolbar, but there are enough options
there already that it can get crowded quick in smaller browser windows (such
as the ones I tend to use, for screen real estate purposes), so I opted
against that.

REVISION: Between the original version of this patch and the present, mobile
support was added, so I went into www/mobile/(OpenVZ|QEMU)Summary.js and added
the suspend and resume options there as well.  No confirmation this time, since
stop and shutdown don't bother with it either in the mobile interface.

I also did a cursory search for other places where suspend/resume commands
might be useful, and added them to bin/pvectl.  If I've missed any other spots,
I'll gladly add the commands to them, as well.

Signed-off-by: Dan Hunsaker <danhunsaker at gmail.com>
---
 PVE/API2/OpenVZ.pm            | 308 +++++++++++++++++++++++++++---------------
 PVE/OpenVZ.pm                 |  92 ++++++++-----
 bin/pvectl                    |  16 ++-
 www/manager/Utils.js          |  80 +++++------
 www/manager/openvz/CmdMenu.js |  28 +++-
 www/manager/qemu/CmdMenu.js   |  26 +++-
 www/mobile/OpenVzSummary.js   |  30 ++--
 www/mobile/QemuSummary.js     |  34 +++--
 8 files changed, 401 insertions(+), 213 deletions(-)

diff --git a/PVE/API2/OpenVZ.pm b/PVE/API2/OpenVZ.pm
index 184ebdf..5d8c0c6 100644
--- a/PVE/API2/OpenVZ.pm
+++ b/PVE/API2/OpenVZ.pm
@@ -71,7 +71,7 @@ my $get_container_storage = sub {
 
 my $check_ct_modify_config_perm = sub {
     my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
-    
+
     return 1 if $authuser ne 'root at pam';
 
     foreach my $opt (@$key_list) {
@@ -82,7 +82,7 @@ my $check_ct_modify_config_perm = sub {
 	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
 	} elsif ($opt eq 'memory' || $opt eq 'swap') {
 	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']);
-	} elsif ($opt eq 'netif' || $opt eq 'ip_address' || $opt eq 'nameserver' || 
+	} elsif ($opt eq 'netif' || $opt eq 'ip_address' || $opt eq 'nameserver' ||
 		 $opt eq 'searchdomain' || $opt eq 'hostname') {
 	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
 	} else {
@@ -94,8 +94,8 @@ my $check_ct_modify_config_perm = sub {
 };
 
 __PACKAGE__->register_method({
-    name => 'vmlist', 
-    path => '', 
+    name => 'vmlist',
+    path => '',
     method => 'GET',
     description => "OpenVZ container index (per node).",
     permissions => {
@@ -136,7 +136,7 @@ __PACKAGE__->register_method({
 	}
 
 	return $res;
-  
+
     }});
 
 my $restore_openvz = sub {
@@ -153,10 +153,10 @@ my $restore_openvz = sub {
 
     die "unable to create CT $vmid - container already exists\n"
 	if !$force && -f $conffile;
- 
+
     die "unable to create CT $vmid - directory '$private' already exists\n"
 	if !$force && -d $private;
-   
+
     die "unable to create CT $vmid - directory '$root' already exists\n"
 	if !$force && -d $root;
 
@@ -168,14 +168,14 @@ my $restore_openvz = sub {
 
 	    my $oldprivate = PVE::OpenVZ::get_privatedir($conf, $vmid);
 	    rmtree $oldprivate if -d $oldprivate;
-	   
+
 	    my $oldroot = $conf->{ve_root} ? $conf->{ve_root}->{value} : $root;
 	    rmtree $oldroot if -d $oldroot;
 	};
 
 	mkpath $private || die "unable to create private dir '$private'";
 	mkpath $root || die "unable to create root dir '$root'";
-	
+
 	my $cmd = ['tar', 'xpf', $archive, '--totals', '--sparse', '-C', $private];
 
 	if ($archive eq '-') {
@@ -197,7 +197,7 @@ my $restore_openvz = sub {
 	    $conf =~ s/host_ifname=veth[0-9]+\./host_ifname=veth${vmid}\./g;
 
 	    PVE::Tools::file_set_contents($conffile, $conf);
-		
+
 	    foreach my $s (PVE::OpenVZ::SCRIPT_EXT) {
 		my $tfn = "$cfgdir/${vmid}.$s";
 		my $sfn = "$private/etc/vzdump/vps.$s";
@@ -228,8 +228,8 @@ my $restore_openvz = sub {
 
 # create_vm is also used by vzrestore
 __PACKAGE__->register_method({
-    name => 'create_vm', 
-    path => '', 
+    name => 'create_vm',
+    path => '',
     method => 'POST',
     description => "Create or restore a container.",
     permissions => {
@@ -247,11 +247,11 @@ __PACKAGE__->register_method({
 	    vmid => get_standard_option('pve-vmid'),
 	    ostemplate => {
 		description => "The OS template or backup file.",
-		type => 'string', 
+		type => 'string',
 		maxLength => 255,
 	    },
-	    password => { 
-		optional => 1, 
+	    password => {
+		optional => 1,
 		type => 'string',
 		description => "Sets root password inside container.",
 	    },
@@ -261,23 +261,23 @@ __PACKAGE__->register_method({
 		optional => 1,
 	    }),
 	    force => {
-		optional => 1, 
+		optional => 1,
 		type => 'boolean',
 		description => "Allow to overwrite existing container.",
 	    },
 	    restore => {
-		optional => 1, 
+		optional => 1,
 		type => 'boolean',
 		description => "Mark this as restore task.",
 	    },
-	    pool => { 
+	    pool => {
 		optional => 1,
 		type => 'string', format => 'pve-poolid',
 		description => "Add the VM to the specified pool.",
 	    },
 	}),
     },
-    returns => { 
+    returns => {
 	type => 'string',
     },
     code => sub {
@@ -296,7 +296,7 @@ __PACKAGE__->register_method({
 	my $storage = extract_param($param, 'storage') || 'local';
 
 	my $pool = extract_param($param, 'pool');
-	
+
 	my $storage_cfg = cfs_read_file("storage.cfg");
 
 	my $scfg = PVE::Storage::storage_check_node($storage_cfg, $storage, $node);
@@ -311,7 +311,7 @@ __PACKAGE__->register_method({
 	if (defined($pool)) {
 	    $rpcenv->check_pool_exist($pool);
 	    $rpcenv->check_perm_modify($authuser, "/pool/$pool");
-	} 
+	}
 
 	$rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
 
@@ -337,9 +337,9 @@ __PACKAGE__->register_method({
 	my $archive;
 
 	if ($ostemplate eq '-') {
-	    die "pipe requires cli environment\n" 
-		if $rpcenv->{type} ne 'cli'; 
-	    die "pipe can only be used with restore tasks\n" 
+	    die "pipe requires cli environment\n"
+		if $rpcenv->{type} ne 'cli';
+	    die "pipe can only be used with restore tasks\n"
 		if !$param->{restore};
 	    $archive = '-';
 	} else {
@@ -347,9 +347,9 @@ __PACKAGE__->register_method({
 	    $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $ostemplate);
 	}
 
-	if (!defined($param->{searchdomain}) && 
+	if (!defined($param->{searchdomain}) &&
 	    !defined($param->{nameserver})) {
-	
+
 	    my $resolv = PVE::INotify::read_file('resolvconf');
 
 	    $param->{searchdomain} = $resolv->{search} if $resolv->{search};
@@ -371,7 +371,7 @@ __PACKAGE__->register_method({
 
 	my $check_vmid_usage = sub {
 	    if ($param->{force}) {
-		die "cant overwrite mounted container\n" 
+		die "cant overwrite mounted container\n"
 		    if PVE::OpenVZ::check_mounted($conf, $vmid);
 	    } else {
 		die "CT $vmid already exists\n" if -f $basecfg_fn;
@@ -392,7 +392,7 @@ __PACKAGE__->register_method({
 		&$restore_openvz($private, $archive, $vmid, $param->{force});
 
 		# is this really needed?
-		my $cmd = ['vzctl', '--skiplock', '--quiet', 'set', $vmid, 
+		my $cmd = ['vzctl', '--skiplock', '--quiet', 'set', $vmid,
 			   '--applyconfig_map', 'name', '--save'];
 		run_command($cmd);
 
@@ -411,10 +411,10 @@ __PACKAGE__->register_method({
 		my $cmd = ['vzctl', '--skiplock', 'create', $vmid,
 			   '--ostemplate', $archive, '--private', $private];
 		run_command($cmd);
-		
-		# hack: vzctl '--userpasswd' starts the CT, but we want 
+
+		# hack: vzctl '--userpasswd' starts the CT, but we want
 		# to avoid that for create
-		PVE::OpenVZ::set_rootpasswd($private, $password) 
+		PVE::OpenVZ::set_rootpasswd($private, $password)
 		    if defined($password);
 	    }
 
@@ -425,21 +425,21 @@ __PACKAGE__->register_method({
 
 	&$check_vmid_usage(); # first check before locking
 
-	return $rpcenv->fork_worker($param->{restore} ? 'vzrestore' : 'vzcreate', 
+	return $rpcenv->fork_worker($param->{restore} ? 'vzrestore' : 'vzcreate',
 				    $vmid, $authuser, $realcmd);
     }});
 
 my $vm_config_perm_list = [
-	    'VM.Config.Disk', 
-	    'VM.Config.CPU', 
-	    'VM.Config.Memory', 
-	    'VM.Config.Network', 
+	    'VM.Config.Disk',
+	    'VM.Config.CPU',
+	    'VM.Config.Memory',
+	    'VM.Config.Network',
 	    'VM.Config.Options',
     ];
 
 __PACKAGE__->register_method({
-    name => 'update_vm', 
-    path => '{vmid}/config', 
+    name => 'update_vm',
+    path => '{vmid}/config',
     method => 'PUT',
     protected => 1,
     proxyto => 'node',
@@ -457,7 +457,7 @@ __PACKAGE__->register_method({
 		    type => 'string',
 		    description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
 		    maxLength => 40,
-		    optional => 1,		    
+		    optional => 1,
 		}
 	    }),
     },
@@ -482,7 +482,7 @@ __PACKAGE__->register_method({
 	my $code = sub {
 
 	    my $conf = PVE::OpenVZ::load_config($vmid);
-	    die "checksum missmatch (file change by other user?)\n" 
+	    die "checksum missmatch (file change by other user?)\n"
 		if $digest && $digest ne $conf->{digest};
 
 	    my $changes = PVE::OpenVZ::update_ovz_config($vmid, $conf, $param);
@@ -492,7 +492,7 @@ __PACKAGE__->register_method({
 	    my $cmd = ['vzctl', '--skiplock', 'set', $vmid, @$changes, '--save'];
 
 	    PVE::Cluster::log_msg('info', $authuser, "update CT $vmid: " . join(' ', @$changes));
- 
+
 	    run_command($cmd);
 	};
 
@@ -502,13 +502,13 @@ __PACKAGE__->register_method({
     }});
 
 __PACKAGE__->register_method ({
-    subclass => "PVE::API2::Firewall::CT",  
+    subclass => "PVE::API2::Firewall::CT",
     path => '{vmid}/firewall',
 });
 
 __PACKAGE__->register_method({
     name => 'vmdiridx',
-    path => '{vmid}', 
+    path => '{vmid}',
     method => 'GET',
     proxyto => 'node',
     description => "Directory index",
@@ -549,13 +549,13 @@ __PACKAGE__->register_method({
 	    { subdir => 'rrddata' },
 	    { subdir => 'firewall' },
 	    ];
-	
+
 	return $res;
     }});
 
 __PACKAGE__->register_method({
-    name => 'rrd', 
-    path => '{vmid}/rrd', 
+    name => 'rrd',
+    path => '{vmid}/rrd',
     method => 'GET',
     protected => 1, # fixme: can we avoid that?
     permissions => {
@@ -594,14 +594,14 @@ __PACKAGE__->register_method({
 	my ($param) = @_;
 
 	return PVE::Cluster::create_rrd_graph(
-	    "pve2-vm/$param->{vmid}", $param->{timeframe}, 
+	    "pve2-vm/$param->{vmid}", $param->{timeframe},
 	    $param->{ds}, $param->{cf});
-					      
+
     }});
 
 __PACKAGE__->register_method({
-    name => 'rrddata', 
-    path => '{vmid}/rrddata', 
+    name => 'rrddata',
+    path => '{vmid}/rrddata',
     method => 'GET',
     protected => 1, # fixme: can we avoid that?
     permissions => {
@@ -641,8 +641,8 @@ __PACKAGE__->register_method({
     }});
 
 __PACKAGE__->register_method({
-    name => 'initlog', 
-    path => '{vmid}/initlog', 
+    name => 'initlog',
+    path => '{vmid}/initlog',
     method => 'GET',
     protected => 1,
     proxyto => 'node',
@@ -669,7 +669,7 @@ __PACKAGE__->register_method({
     },
     returns => {
 	type => 'array',
-	items => { 
+	items => {
 	    type => "object",
 	    properties => {
 		n => {
@@ -700,13 +700,13 @@ __PACKAGE__->register_method({
 	my ($count, $lines) = PVE::Tools::dump_logfile($logfn, $param->{start}, $param->{limit});
 
 	$rpcenv->set_result_attrib('total', $count);
-	    
-	return $lines; 
+
+	return $lines;
     }});
 
 __PACKAGE__->register_method({
-    name => 'vm_config', 
-    path => '{vmid}/config', 
+    name => 'vm_config',
+    path => '{vmid}/config',
     method => 'GET',
     proxyto => 'node',
     description => "Get container configuration.",
@@ -720,7 +720,7 @@ __PACKAGE__->register_method({
 	    vmid => get_standard_option('pve-vmid'),
 	},
     },
-    returns => { 
+    returns => {
 	type => "object",
 	properties => {
 	    digest => {
@@ -775,8 +775,8 @@ __PACKAGE__->register_method({
     }});
 
 __PACKAGE__->register_method({
-    name => 'destroy_vm', 
-    path => '{vmid}', 
+    name => 'destroy_vm',
+    path => '{vmid}',
     method => 'DELETE',
     protected => 1,
     proxyto => 'node',
@@ -791,7 +791,7 @@ __PACKAGE__->register_method({
 	    vmid => get_standard_option('pve-vmid'),
 	},
     },
-    returns => { 
+    returns => {
 	type => 'string',
     },
     code => sub {
@@ -820,8 +820,8 @@ __PACKAGE__->register_method({
 my $sslcert;
 
 __PACKAGE__->register_method ({
-    name => 'vncproxy', 
-    path => '{vmid}/vncproxy', 
+    name => 'vncproxy',
+    path => '{vmid}/vncproxy',
     method => 'POST',
     protected => 1,
     permissions => {
@@ -840,7 +840,7 @@ __PACKAGE__->register_method ({
 	    },
 	},
     },
-    returns => { 
+    returns => {
     	additionalProperties => 0,
 	properties => {
 	    user => { type => 'string' },
@@ -870,19 +870,19 @@ __PACKAGE__->register_method ({
 	my $port = PVE::Tools::next_vnc_port();
 
 	my $remip;
-	
+
 	if ($node ne PVE::INotify::nodename()) {
 	    $remip = PVE::Cluster::remote_node_ip($node);
 	}
 
 	# NOTE: vncterm VNC traffic is already TLS encrypted,
 	# so we select the fastest chipher here (or 'none'?)
-	my $remcmd = $remip ? 
+	my $remcmd = $remip ?
 	    ['/usr/bin/ssh', '-t', $remip] : [];
 
-	my $shcmd = [ '/usr/bin/dtach', '-A', 
-		      "/var/run/dtach/vzctlconsole$vmid", 
-		      '-r', 'winch', '-z', 
+	my $shcmd = [ '/usr/bin/dtach', '-A',
+		      "/var/run/dtach/vzctlconsole$vmid",
+		      '-r', 'winch', '-z',
 		      '/usr/sbin/vzctl', 'console', $vmid ];
 
 	my $realcmd = sub {
@@ -890,14 +890,14 @@ __PACKAGE__->register_method ({
 
 	    syslog ('info', "starting openvz vnc proxy $upid\n");
 
-	    my $timeout = 10; 
+	    my $timeout = 10;
 
 	    my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
-		       '-timeout', $timeout, '-authpath', $authpath, 
+		       '-timeout', $timeout, '-authpath', $authpath,
 		       '-perm', 'VM.Console'];
 
 	    if ($param->{websocket}) {
-		$ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm 
+		$ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
 		push @$cmd, '-notls', '-listen', 'localhost';
 	    }
 
@@ -915,9 +915,9 @@ __PACKAGE__->register_method ({
 	return {
 	    user => $authuser,
 	    ticket => $ticket,
-	    port => $port, 
-	    upid => $upid, 
-	    cert => $sslcert, 
+	    port => $port,
+	    upid => $upid,
+	    cert => $sslcert,
 	};
     }});
 
@@ -925,7 +925,7 @@ __PACKAGE__->register_method({
     name => 'vncwebsocket',
     path => '{vmid}/vncwebsocket',
     method => 'GET',
-    permissions => { 
+    permissions => {
 	description => "You also need to pass a valid ticket (vncticket).",
 	check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
     },
@@ -966,13 +966,13 @@ __PACKAGE__->register_method({
 	PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
 
 	my $port = $param->{port};
-	
+
 	return { port => $port };
     }});
 
 __PACKAGE__->register_method ({
-    name => 'spiceproxy', 
-    path => '{vmid}/spiceproxy', 
+    name => 'spiceproxy',
+    path => '{vmid}/spiceproxy',
     method => 'POST',
     protected => 1,
     proxyto => 'node',
@@ -999,9 +999,9 @@ __PACKAGE__->register_method ({
 	my $authpath = "/vms/$vmid";
 	my $permissions = 'VM.Console';
 
-	my $shcmd = ['/usr/bin/dtach', '-A', 
-		     "/var/run/dtach/vzctlconsole$vmid", 
-		     '-r', 'winch', '-z', 
+	my $shcmd = ['/usr/bin/dtach', '-A',
+		     "/var/run/dtach/vzctlconsole$vmid",
+		     '-r', 'winch', '-z',
 		     '/usr/sbin/vzctl', 'console', $vmid];
 
 	my $title = "CT $vmid";
@@ -1011,7 +1011,7 @@ __PACKAGE__->register_method ({
 
 __PACKAGE__->register_method({
     name => 'vmcmdidx',
-    path => '{vmid}/status', 
+    path => '{vmid}/status',
     method => 'GET',
     proxyto => 'node',
     description => "Directory index",
@@ -1047,7 +1047,7 @@ __PACKAGE__->register_method({
 	    { subdir => 'start' },
 	    { subdir => 'stop' },
 	    ];
-	
+
 	return $res;
     }});
 
@@ -1057,12 +1057,12 @@ my $vm_is_ha_managed = sub {
     my $cc = PVE::Cluster::cfs_read_file('cluster.conf');
     if (PVE::Cluster::cluster_conf_lookup_pvevm($cc, 0, $vmid, 1)) {
 	return 1;
-    } 
+    }
     return 0;
 };
 
 __PACKAGE__->register_method({
-    name => 'vm_status', 
+    name => 'vm_status',
     path => '{vmid}/status/current',
     method => 'GET',
     proxyto => 'node',
@@ -1094,7 +1094,7 @@ __PACKAGE__->register_method({
     }});
 
 __PACKAGE__->register_method({
-    name => 'vm_user_beancounters', 
+    name => 'vm_user_beancounters',
     path => '{vmid}/status/ubc',
     method => 'GET',
     proxyto => 'node',
@@ -1138,7 +1138,7 @@ __PACKAGE__->register_method({
     }});
 
 __PACKAGE__->register_method({
-    name => 'vm_start', 
+    name => 'vm_start',
     path => '{vmid}/status/start',
     method => 'POST',
     protected => 1,
@@ -1154,7 +1154,7 @@ __PACKAGE__->register_method({
 	    vmid => get_standard_option('pve-vmid'),
 	},
     },
-    returns => { 
+    returns => {
 	type => 'string',
     },
     code => sub {
@@ -1202,13 +1202,13 @@ __PACKAGE__->register_method({
 		}
 
 		my $vzconf = PVE::OpenVZ::read_global_vz_config();
-		
+
 		# make sure mount point is there (see bug #276)
 		my $root = PVE::OpenVZ::get_rootdir($veconf, $vmid);
 		mkpath $root || die "unable to create root dir '$root'";
 
 		my $cmd = ['vzctl', 'start', $vmid];
-	    
+
 		run_command($cmd);
 
 		return;
@@ -1219,7 +1219,7 @@ __PACKAGE__->register_method({
     }});
 
 __PACKAGE__->register_method({
-    name => 'vm_stop', 
+    name => 'vm_stop',
     path => '{vmid}/status/stop',
     method => 'POST',
     protected => 1,
@@ -1235,7 +1235,7 @@ __PACKAGE__->register_method({
 	    vmid => get_standard_option('pve-vmid'),
 	},
     },
-    returns => { 
+    returns => {
 	type => 'string',
     },
     code => sub {
@@ -1278,7 +1278,7 @@ __PACKAGE__->register_method({
 
 		my $cmd = ['vzctl', 'stop', $vmid, '--fast'];
 		run_command($cmd);
-	    
+
 		return;
 	    };
 
@@ -1287,7 +1287,7 @@ __PACKAGE__->register_method({
     }});
 
 __PACKAGE__->register_method({
-    name => 'vm_mount', 
+    name => 'vm_mount',
     path => '{vmid}/status/mount',
     method => 'POST',
     protected => 1,
@@ -1303,7 +1303,7 @@ __PACKAGE__->register_method({
 	    vmid => get_standard_option('pve-vmid'),
 	},
     },
-    returns => { 
+    returns => {
 	type => 'string',
     },
     code => sub {
@@ -1325,7 +1325,7 @@ __PACKAGE__->register_method({
 	    syslog('info', "mount CT $vmid: $upid\n");
 
 	    my $cmd = ['vzctl', 'mount', $vmid];
-	    
+
 	    run_command($cmd);
 
 	    return;
@@ -1335,7 +1335,7 @@ __PACKAGE__->register_method({
     }});
 
 __PACKAGE__->register_method({
-    name => 'vm_umount', 
+    name => 'vm_umount',
     path => '{vmid}/status/umount',
     method => 'POST',
     protected => 1,
@@ -1351,7 +1351,7 @@ __PACKAGE__->register_method({
 	    vmid => get_standard_option('pve-vmid'),
 	},
     },
-    returns => { 
+    returns => {
 	type => 'string',
     },
     code => sub {
@@ -1373,7 +1373,7 @@ __PACKAGE__->register_method({
 	    syslog('info', "umount CT $vmid: $upid\n");
 
 	    my $cmd = ['vzctl', 'umount', $vmid];
-	    
+
 	    run_command($cmd);
 
 	    return;
@@ -1383,7 +1383,7 @@ __PACKAGE__->register_method({
     }});
 
 __PACKAGE__->register_method({
-    name => 'vm_shutdown', 
+    name => 'vm_shutdown',
     path => '{vmid}/status/shutdown',
     method => 'POST',
     protected => 1,
@@ -1412,7 +1412,7 @@ __PACKAGE__->register_method({
 	    }
 	},
     },
-    returns => { 
+    returns => {
 	type => 'string',
     },
     code => sub {
@@ -1449,7 +1449,7 @@ __PACKAGE__->register_method({
 
 	    push @$cmd, '--fast';
 	    run_command($cmd);
-	    
+
 	    return;
 	};
 
@@ -1459,7 +1459,103 @@ __PACKAGE__->register_method({
     }});
 
 __PACKAGE__->register_method({
-    name => 'migrate_vm', 
+       name => 'vm_suspend',
+       path => '{vmid}/status/suspend',
+       method => 'POST',
+       protected => 1,
+       proxyto => 'node',
+       description => "Suspend the container.",
+       permissions => {
+               check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
+       },
+       parameters => {
+               additionalProperties => 0,
+               properties => {
+                       node => get_standard_option('pve-node'),
+                       vmid => get_standard_option('pve-vmid'),
+               },
+       },
+       returns => {
+               type => 'string',
+       },
+       code => sub {
+               my ($param) = @_;
+
+               my $rpcenv = PVE::RPCEnvironment::get();
+
+               my $authuser = $rpcenv->get_user();
+
+               my $node = extract_param($param, 'node');
+
+               my $vmid = extract_param($param, 'vmid');
+
+               die "CT $vmid not running\n" if !PVE::OpenVZ::check_running($vmid);
+
+               my $realcmd = sub {
+                       my $upid = shift;
+
+                       syslog('info', "suspend CT $vmid: $upid\n");
+
+                       PVE::OpenVZ::vm_suspend($vmid);
+
+                       return;
+               };
+
+               my $upid = $rpcenv->fork_worker('vzsuspend', $vmid, $authuser, $realcmd);
+
+               return $upid;
+       }});
+
+__PACKAGE__->register_method({
+       name => 'vm_resume',
+       path => '{vmid}/status/resume',
+       method => 'POST',
+       protected => 1,
+       proxyto => 'node',
+       description => "Resume the container.",
+       permissions => {
+               check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]],
+       },
+       parameters => {
+               additionalProperties => 0,
+               properties => {
+                       node => get_standard_option('pve-node'),
+                       vmid => get_standard_option('pve-vmid'),
+               },
+       },
+       returns => {
+               type => 'string',
+       },
+       code => sub {
+               my ($param) = @_;
+
+               my $rpcenv = PVE::RPCEnvironment::get();
+
+               my $authuser = $rpcenv->get_user();
+
+               my $node = extract_param($param, 'node');
+
+               my $vmid = extract_param($param, 'vmid');
+
+               die "CT $vmid already running\n" if PVE::OpenVZ::check_running($vmid);
+
+               my $realcmd = sub {
+                       my $upid = shift;
+
+                       syslog('info', "resume CT $vmid: $upid\n");
+
+                       PVE::OpenVZ::vm_resume($vmid);
+
+                       return;
+               };
+
+               my $upid = $rpcenv->fork_worker('vzresume', $vmid, $authuser, $realcmd);
+
+               return $upid;
+       }});
+
+__PACKAGE__->register_method({
+    name => 'migrate_vm',
     path => '{vmid}/migrate',
     method => 'POST',
     protected => 1,
@@ -1481,7 +1577,7 @@ __PACKAGE__->register_method({
 	    },
 	},
     },
-    returns => { 
+    returns => {
 	type => 'string',
 	description => "the task ID.",
     },
@@ -1510,7 +1606,7 @@ __PACKAGE__->register_method({
 
 	# try to detect errors early
 	if (PVE::OpenVZ::check_running($vmid)) {
-	    die "cant migrate running container without --online\n" 
+	    die "cant migrate running container without --online\n"
 		if !$param->{online};
 	}
 
diff --git a/PVE/OpenVZ.pm b/PVE/OpenVZ.pm
index aa6f502..fcfb0c2 100644
--- a/PVE/OpenVZ.pm
+++ b/PVE/OpenVZ.pm
@@ -6,7 +6,7 @@ use File::stat qw();
 use POSIX qw (LONG_MAX);
 use IO::Dir;
 use IO::File;
-use PVE::Tools qw(extract_param $IPV6RE $IPV4RE);
+use PVE::Tools qw(run_command extract_param $IPV6RE $IPV4RE);
 use PVE::ProcFSTools;
 use PVE::Cluster qw(cfs_register_file cfs_read_file);
 use PVE::SafeSyslog;
@@ -182,7 +182,7 @@ sub read_container_blkio_stat {
 
     my $filename = "/proc/vz/beancounter/$vmid/blkio.io_service_bytes";
     if (my $fh = IO::File->new ($filename, "r")) {
-       
+
 	while (defined (my $line = <$fh>)) {
 	    if ($line =~ m/^\S+\s+Read\s+(\d+)$/) {
 		$read += $1;
@@ -223,7 +223,7 @@ sub vmstatus {
 
 	    $d->{mem} = 0;
 	    $d->{swap} = 0;
-	    
+
 	    ($d->{maxmem}, $d->{maxswap}) = ovz_config_extract_mem_swap($conf);
 
 	    $d->{nproc} = 0;
@@ -332,7 +332,7 @@ sub vmstatus {
 	my $d = $list->{$vmid};
 	next if !$d || !$d->{status} || $d->{status} ne 'running';
 	($d->{netin}, $d->{netout}) = read_container_network_usage($vmid);
-	($d->{diskread}, $d->{diskwrite}) = read_container_blkio_stat($vmid); 
+	($d->{diskread}, $d->{diskwrite}) = read_container_blkio_stat($vmid);
     }
 
     return $list;
@@ -449,7 +449,7 @@ sub read_global_vz_config {
 	lockdir => '/var/lib/vz/lock',
 	disk_quota => 1,
     };
-    
+
     my $filename = "/etc/vz/vz.conf";
 
     return $res if ! -f $filename;
@@ -485,7 +485,7 @@ sub read_global_vz_config {
 	$dir =~ s/^\"(.*)\"/$1/;
 	$res->{lockdir} = $dir;
     }
-    if ($data =~ m/^\s*DISK_QUOTA=(no|false|off|0)$/m) { 
+    if ($data =~ m/^\s*DISK_QUOTA=(no|false|off|0)$/m) {
 	$res->{disk_quota} = 0;
     }
 
@@ -523,7 +523,7 @@ sub parse_netif {
 		    $d->{$1} = parse_boolean('mac_filter', $2);
 		} else {
 		    $d->{$1} = $2;
-		}		
+		}
 	    }
 	}
 	if ($d->{ifname}) {
@@ -631,7 +631,7 @@ sub parse_res_bytes_bytes {
 
     my @a = split(/:/, $text);
     $a[1] = $a[0] if !defined($a[1]);
-    
+
     my $bar = parse_res_bar_limit($a[0], 1);
     my $lim = parse_res_bar_limit($a[1], 1);
 
@@ -647,7 +647,7 @@ sub parse_res_block_block {
 
     my @a = split(/:/, $text);
     $a[1] = $a[0] if !defined($a[1]);
-    
+
     my $bar = parse_res_bar_limit($a[0], 1024);
     my $lim = parse_res_bar_limit($a[1], 1024);
 
@@ -663,7 +663,7 @@ sub parse_res_pages_pages {
 
     my @a = split(/:/, $text);
     $a[1] = $a[0] if !defined($a[1]);
-    
+
     my $bar = parse_res_bar_limit($a[0], 4096);
     my $lim = parse_res_bar_limit($a[1], 4096);
 
@@ -678,9 +678,9 @@ sub parse_res_pages_unlimited {
     my ($key, $text) = @_;
 
     my @a = split(/:/, $text);
-    
+
     my $bar = parse_res_bar_limit($a[0], 4096);
- 
+
     if (defined($bar)) {
 	return { bar => $bar, lim => $res_unlimited };
     }
@@ -692,9 +692,9 @@ sub parse_res_pages_ignore {
     my ($key, $text) = @_;
 
     my @a = split(/:/, $text);
-    
+
     my $bar = parse_res_bar_limit($a[0], 4096);
- 
+
     if (defined($bar)) {
 	return { bar => $bar };
     }
@@ -707,9 +707,9 @@ sub parse_res_ignore_pages {
 
     my @a = split(/:/, $text);
     $a[1] = $a[0] if !defined($a[1]);
-    
+
     my $lim = parse_res_bar_limit($a[1] , 4096);
- 
+
     if (defined($lim)) {
 	return { bar => 0, lim => $lim };
     }
@@ -740,7 +740,7 @@ my $ovz_ressources = {
     numproc => \&parse_res_num_ignore,
     numtcpsock => \&parse_res_num_ignore,
     numothersock => \&parse_res_num_ignore,
-    numfile => \&parse_res_num_ignore,    
+    numfile => \&parse_res_num_ignore,
     numflock => \&parse_res_num_num,
     numpty => \&parse_res_num_ignore,
     numsiginfo => \&parse_res_num_ignore,
@@ -873,9 +873,9 @@ sub format_res_bar_lim {
     my ($key, $data) = @_;
 
     if (defined($data->{lim}) && ($data->{lim} ne $data->{bar})) {
-	return format_res_value($key, $data->{bar}) . ":" . format_res_value($key, $data->{lim});     
+	return format_res_value($key, $data->{bar}) . ":" . format_res_value($key, $data->{lim});
     } else {
-	return format_res_value($key, $data->{bar}); 
+	return format_res_value($key, $data->{bar});
     }
 }
 
@@ -893,7 +893,7 @@ sub create_config_line {
 	}
     } elsif (defined($data->{bar})) {
 	my $tmp = format_res_bar_lim($key, $data);
-	$text .=  uc($key) . "=\"$tmp\"\n";     
+	$text .=  uc($key) . "=\"$tmp\"\n";
     }
 }
 
@@ -907,8 +907,8 @@ sub ovz_config_extract_mem_swap {
     my $maxpages = ($res_unlimited / 4096);
 
     if ($veconf->{swappages}) {
-	if ($veconf->{physpages} && $veconf->{physpages}->{lim} && 
-	    ($veconf->{physpages}->{lim} < $maxpages)) { 
+	if ($veconf->{physpages} && $veconf->{physpages}->{lim} &&
+	    ($veconf->{physpages}->{lim} < $maxpages)) {
 	    $mem = int(($veconf->{physpages}->{lim} * 4096 + $unit - 1) / $unit);
 	}
 	if ($veconf->{swappages}->{lim} && ($veconf->{swappages}->{lim} < $maxpages)) {
@@ -928,15 +928,15 @@ sub update_ovz_config {
     my ($vmid, $veconf, $param) = @_;
 
     my $changes = [];
-	
+
     # test if barrier or limit changed
     my $push_bl_changes = sub {
 	my ($name, $bar, $lim) = @_;
-	my $old = format_res_bar_lim($name, $veconf->{$name}) 
+	my $old = format_res_bar_lim($name, $veconf->{$name})
 	    if $veconf->{$name} && defined($veconf->{$name}->{bar});
 	my $new = format_res_bar_lim($name, { bar => $bar, lim => $lim });
 	if (!$old || ($old ne $new)) {
-	    $veconf->{$name}->{bar} = $bar; 
+	    $veconf->{$name}->{bar} = $bar;
 	    $veconf->{$name}->{lim} = $lim;
 	    push @$changes, "--$name", $new;
 	}
@@ -977,7 +977,7 @@ sub update_ovz_config {
 	$cpus = $param->{cpus};
     }
 
-    # memory related parameter 
+    # memory related parameter
 
     &$push_bl_changes('vmguarpages', 0, $res_unlimited);
     &$push_bl_changes('oomguarpages', 0, $res_unlimited);
@@ -1061,9 +1061,9 @@ sub update_ovz_config {
     };
 
     &$cond_set_boolean('onboot');
-    
+
     &$cond_set_value('hostname');
- 
+
     &$cond_set_value('searchdomain');
 
     if ($param->{'description'}) {
@@ -1118,7 +1118,7 @@ sub update_ovz_config {
 	    $ifadd .= $newif->{$ifname}->{host_ifname} ? ",$newif->{$ifname}->{host_ifname}" : ',';
 	    $ifadd .= $newif->{$ifname}->{host_mac} ? ",$newif->{$ifname}->{host_mac}" : ',';
 	    $ifadd .= $newif->{$ifname}->{bridge} ? ",$newif->{$ifname}->{bridge}" : '';
-	    
+
 	    # not possible with current vzctl
 	    #$ifadd .= $newif->{$ifname}->{mac_filter} ? ",$newif->{$ifname}->{mac_filter}" : '';
 
@@ -1129,7 +1129,7 @@ sub update_ovz_config {
 	$veconf->{netif}->{value} = $newvalue;
     }
 
-    if (defined($param->{'nameserver'})) { 
+    if (defined($param->{'nameserver'})) {
 	# remove duplicates
 	my $nshash = {};
 	my $newvalue = '';
@@ -1190,8 +1190,8 @@ sub create_lock_manager {
 
     return LockFile::Simple->make(-format => '%f',
 				  -autoclean => 1,
-				  -max => defined($max) ? $max : 60, 
-				  -delay => 1, 
+				  -max => defined($max) ? $max : 60,
+				  -delay => 1,
 				  -stale => 1,
 				  -nfs => 0);
 }
@@ -1220,6 +1220,30 @@ sub lock_container {
     return $res;
 }
 
+sub vm_suspend {
+    my ($vmid) = @_;
+
+    my $cmd = ['vzctl', 'chkpnt', $vmid];
+
+    eval { run_command($cmd); };
+    if (my $err = $@) {
+        syslog("err", "CT $vmid suspend failed - $err");
+        die $err;
+    }
+}
+
+sub vm_resume {
+    my ($vmid) = @_;
+
+    my $cmd = ['vzctl', 'restore', $vmid];
+
+    eval { run_command($cmd); };
+    if (my $err = $@) {
+        syslog("err", "CT $vmid resume failed - $err");
+        die $err;
+    }
+}
+
 sub replacepw {
     my ($file, $epw) = @_;
 
@@ -1238,7 +1262,7 @@ sub replacepw {
 	# copy owner and permissions
 	chmod $st->mode, \*DST;
 	chown $st->uid, $st->gid, \*DST;
-	
+
 	while (defined (my $line = <SRC>)) {
 	    $line =~ s/^root:[^:]*:/root:${epw}:/;
 	    print DST $line;
@@ -1255,7 +1279,7 @@ sub replacepw {
     } else {
 	rename $tmpfile, $file;
 	unlink $tmpfile; # in case rename fails
-    }	
+    }
 }
 
 sub set_rootpasswd {
diff --git a/bin/pvectl b/bin/pvectl
index f8ae3ad..9e9a797 100755
--- a/bin/pvectl
+++ b/bin/pvectl
@@ -28,7 +28,7 @@ my $rpcenv = PVE::RPCEnvironment->init('cli');
 
 $rpcenv->init_request();
 $rpcenv->set_language($ENV{LANG});
-$rpcenv->set_user('root at pam'); 
+$rpcenv->set_user('root at pam');
 
 my $upid_exit = sub {
     my $upid = shift;
@@ -44,15 +44,15 @@ my $cmddef = {
 
 		 exit 0 if (!scalar(@$vmlist));
 
-		 printf "%10s %-20s %-10s %-10s %-12s\n", 
+		 printf "%10s %-20s %-10s %-10s %-12s\n",
 		 qw(VMID NAME STATUS MEM(MB) DISK(GB));
 
 		 foreach my $rec (sort { $a->{vmid} <=> $b->{vmid} } @$vmlist) {
-		     printf "%10s %-20s %-10s %-10s %-12.2f\n", $rec->{vmid}, $rec->{name} || '', 
-		     $rec->{status}, 
-		     ($rec->{maxmem} || 0)/(1024*1024), 
+		     printf "%10s %-20s %-10s %-10s %-12.2f\n", $rec->{vmid}, $rec->{name} || '',
+		     $rec->{status},
+		     ($rec->{maxmem} || 0)/(1024*1024),
 		     ($rec->{maxdisk} || 0)/(1024*1024*1024);
-		 }		 
+		 }
 	      } ],
 
     create => [ 'PVE::API2::OpenVZ', 'create_vm', ['vmid', 'ostemplate'], { node => $nodename }, $upid_exit ],
@@ -60,7 +60,7 @@ my $cmddef = {
 
     set => [ "PVE::API2::OpenVZ", 'update_vm', ['vmid'], { node => $nodename } ],
 
-    config => [ "PVE::API2::OpenVZ", 'vm_config', ['vmid'], 
+    config => [ "PVE::API2::OpenVZ", 'vm_config', ['vmid'],
 		{ node => $nodename }, sub {
 		    my $config = shift;
 		    foreach my $k (sort (keys %$config)) {
@@ -74,6 +74,8 @@ my $cmddef = {
 		}],
 
     start => [ 'PVE::API2::OpenVZ', 'vm_start', ['vmid'], { node => $nodename }, $upid_exit],
+    suspend => [ 'PVE::API2::OpenVZ', 'vm_suspend', ['vmid'], { node => $nodename }, $upid_exit],
+    resume => [ 'PVE::API2::OpenVZ', 'vm_resume', ['vmid'], { node => $nodename }, $upid_exit],
     shutdown => [ 'PVE::API2::OpenVZ', 'vm_shutdown', ['vmid'], { node => $nodename }, $upid_exit],
     stop => [ 'PVE::API2::OpenVZ', 'vm_stop', ['vmid'], { node => $nodename }, $upid_exit],
     mount => [ 'PVE::API2::OpenVZ', 'vm_mount', ['vmid'], { node => $nodename }, $upid_exit],
diff --git a/www/manager/Utils.js b/www/manager/Utils.js
index f95c180..151df32 100644
--- a/www/manager/Utils.js
+++ b/www/manager/Utils.js
@@ -1,13 +1,13 @@
 Ext.ns('PVE');
 
 // avoid errors when running without development tools
-if (!Ext.isDefined(Ext.global.console)) {   
-    var console = { 
-	dir: function() {}, 
-	log: function() {} 
+if (!Ext.isDefined(Ext.global.console)) {
+    var console = {
+	dir: function() {},
+	log: function() {}
     };
 }
-console.log("Starting PVE Manager"); 
+console.log("Starting PVE Manager");
 
 Ext.Ajax.defaultHeaders = {
     'Accept': 'application/json'
@@ -15,7 +15,7 @@ Ext.Ajax.defaultHeaders = {
 
 Ext.Ajax.on('beforerequest', function(conn, options) {
     if (PVE.CSRFPreventionToken) {
-	if (!options.headers) { 
+	if (!options.headers) {
 	    options.headers = {};
 	}
 	options.headers.CSRFPreventionToken = PVE.CSRFPreventionToken;
@@ -48,7 +48,7 @@ Ext.define('PVE.Utils', { statics: {
 
     // this class only contains static functions
 
-    toolkit: undefined, // (extjs|touch), set inside Toolkit.js 
+    toolkit: undefined, // (extjs|touch), set inside Toolkit.js
 
     log_severity_hash: {
 	0: "panic",
@@ -104,7 +104,7 @@ Ext.define('PVE.Utils', { statics: {
     },
 
     render_network_iface_type: function(value) {
-	return PVE.Utils.network_iface_types[value] || 
+	return PVE.Utils.network_iface_types[value] ||
 	    PVE.Utils.unknownText;
     },
 
@@ -131,29 +131,29 @@ Ext.define('PVE.Utils', { statics: {
     kvm_keymaps: {
 	//ar: 'Arabic',
 	da: 'Danish',
-	de: 'German', 
-	'de-ch': 'German (Swiss)', 
-	'en-gb': 'English (UK)', 
+	de: 'German',
+	'de-ch': 'German (Swiss)',
+	'en-gb': 'English (UK)',
 	'en-us': 'English (USA',
 	es: 'Spanish',
 	//et: 'Estonia',
 	fi: 'Finnish',
-	//fo: 'Faroe Islands', 
-	fr: 'French', 
-	'fr-be': 'French (Belgium)', 
+	//fo: 'Faroe Islands',
+	fr: 'French',
+	'fr-be': 'French (Belgium)',
 	'fr-ca': 'French (Canada)',
 	'fr-ch': 'French (Swiss)',
 	//hr: 'Croatia',
 	hu: 'Hungarian',
 	is: 'Icelandic',
-	it: 'Italian', 
+	it: 'Italian',
 	ja: 'Japanese',
 	lt: 'Lithuanian',
 	//lv: 'Latvian',
-	mk: 'Macedonian', 
+	mk: 'Macedonian',
 	nl: 'Dutch',
 	//'nl-be': 'Dutch (Belgium)',
-	no: 'Norwegian', 
+	no: 'Norwegian',
 	pl: 'Polish',
 	pt: 'Portuguese',
 	'pt-br': 'Portuguese (Brazil)',
@@ -259,7 +259,7 @@ Ext.define('PVE.Utils', { statics: {
 	    return PVE.Utils.defaultText;
 	}
 	var text = PVE.Utils.kvm_vga_drivers[value];
-	if (text) { 
+	if (text) {
 	    return text + ' (' + value + ')';
 	}
 	return value;
@@ -304,7 +304,7 @@ Ext.define('PVE.Utils', { statics: {
     // fixme: remove - not needed?
     gridLineHeigh: function() {
 	return 21;
-	
+
 	//if (Ext.isGecko)
 	//return 23;
 	//return 21;
@@ -324,10 +324,10 @@ Ext.define('PVE.Utils', { statics: {
 	    if (verbose && Ext.isObject(result.errors)) {
 		msg += "<br>";
 		Ext.Object.each(result.errors, function(prop, desc) {
-		    msg += "<br><b>" + Ext.htmlEncode(prop) + "</b>: " + 
+		    msg += "<br><b>" + Ext.htmlEncode(prop) + "</b>: " +
 			Ext.htmlEncode(desc);
 		});
-	    }	
+	    }
 	}
 
 	return msg;
@@ -510,6 +510,8 @@ Ext.define('PVE.Utils', { statics: {
 	vzmount: ['CT', gettext('Mount') ],
 	vzumount: ['CT', gettext('Unmount') ],
 	vzshutdown: ['CT', gettext('Shutdown') ],
+	vzsuspend: [ 'CT', gettext('Suspend') ],
+	vzresume: [ 'CT', gettext('Resume') ],
 	hamigrate: [ 'HA', gettext('Migrate') ],
 	hastart: [ 'HA', gettext('Start') ],
 	hastop: [ 'HA', gettext('Stop') ],
@@ -530,7 +532,7 @@ Ext.define('PVE.Utils', { statics: {
 	stopall: [ '', gettext('Stop all VMs and Containers') ]
     },
 
-    format_task_description: function(type, id) {	
+    format_task_description: function(type, id) {
 	var farray = PVE.Utils.task_desc_table[type];
 	if (!farray) {
 	    return type;
@@ -538,7 +540,7 @@ Ext.define('PVE.Utils', { statics: {
 	var prefix = farray[0];
 	var text = farray[1];
 	if (prefix) {
-	    return prefix + ' ' + id + ' - ' + text; 
+	    return prefix + ' ' + id + ' - ' + text;
 	}
 	return text;
     },
@@ -599,7 +601,7 @@ Ext.define('PVE.Utils', { statics: {
 	return "<div class='pve-bar-wrap'>" + text + "<div class='pve-bar-border'>" +
 	    "<div class='pve-bar-inner' style='width:" + per + "%;'></div>" +
 	    "</div></div>";
-	
+
     },
 
     format_cpu_bar: function(per1, per2, text) {
@@ -607,7 +609,7 @@ Ext.define('PVE.Utils', { statics: {
 	return "<div class='pve-bar-border'>" +
 	    "<div class='pve-bar-inner' style='width:" + per1 + "%;'></div>" +
 	    "<div class='pve-bar-inner2' style='width:" + per2 + "%;'></div>" +
-	    "<div class='pve-bar-text'>" + text + "</div>" + 
+	    "<div class='pve-bar-text'>" + text + "</div>" +
 	    "</div>";
     },
 
@@ -619,7 +621,7 @@ Ext.define('PVE.Utils', { statics: {
 
 	return "<div class='pve-largebar-border'>" +
 	    "<div class='pve-largebar-inner' style='width:" + per + "%;'></div>" +
-	    "<div class='pve-largebar-text'>" + text + "</div>" + 
+	    "<div class='pve-largebar-text'>" + text + "</div>" +
 	    "</div>";
     },
 
@@ -641,7 +643,7 @@ Ext.define('PVE.Utils', { statics: {
 
 	if (days) {
 	    var ds = days > 1 ? PVE.Utils.daysText : PVE.Utils.dayText;
-	    return days.toString() + ' ' + ds + ' ' + 
+	    return days.toString() + ' ' + ds + ' ' +
 		hours_str + ':' + mins_str + ':' + ut_str;
 	} else {
 	    return hours_str + ':' + mins_str + ':' + ut_str;
@@ -649,7 +651,7 @@ Ext.define('PVE.Utils', { statics: {
     },
 
     format_duration_short: function(ut) {
-	
+
 	if (ut < 60) {
 	    return ut.toString() + 's';
 	}
@@ -665,7 +667,7 @@ Ext.define('PVE.Utils', { statics: {
 	}
 
 	var days = ut / 86400;
-	return days.toFixed(0) + 'd';	
+	return days.toFixed(0) + 'd';
     },
 
     yesText: gettext('Yes'),
@@ -759,8 +761,8 @@ Ext.define('PVE.Utils', { statics: {
 	if (Ext.isNumber(data.channel) &&
 	    Ext.isNumber(data.id) &&
 	    Ext.isNumber(data.lun)) {
-	    return "CH " + 
-		Ext.String.leftPad(data.channel,2, '0') + 
+	    return "CH " +
+		Ext.String.leftPad(data.channel,2, '0') +
 		" ID " + data.id + " LUN " + data.lun;
 	}
 	return data.volid.replace(/^.*:(.*\/)?/,'');
@@ -781,7 +783,7 @@ Ext.define('PVE.Utils', { statics: {
 	if (!Ext.isNumeric(maxcpu) && (maxcpu >= 1)) {
 	    return '';
 	}
-	
+
 	var per = value * 100;
 
 	return per.toFixed(1) + '% of ' + maxcpu.toString() + (maxcpu > 1 ? 'CPUs' : 'CPU');
@@ -806,7 +808,7 @@ Ext.define('PVE.Utils', { statics: {
 
 	var mem = value;
 	var maxmem = record.data.maxmem;
-	
+
 	if (!record.data.uptime) {
 	    return '';
 	}
@@ -841,7 +843,7 @@ Ext.define('PVE.Utils', { statics: {
 	if (record.data.running) {
 	    metaData.tdCls = cls + "-running";
 	} else if (record.data.template) {
-	    metaData.tdCls = cls + "-template";	    
+	    metaData.tdCls = cls + "-template";
 	} else {
 	    metaData.tdCls = cls;
 	}
@@ -856,7 +858,7 @@ Ext.define('PVE.Utils', { statics: {
 	if (uptime === undefined) {
 	    return '';
 	}
-	
+
 	if (uptime <= 0) {
 	    return '-';
 	}
@@ -868,7 +870,7 @@ Ext.define('PVE.Utils', { statics: {
 	return PVE.Utils.support_level_hash[value] || '-';
     },
 
-    render_upid: function(value, metaData, record) { 
+    render_upid: function(value, metaData, record) {
 	var type = record.data.type;
 	var id = record.data.id;
 
@@ -886,7 +888,7 @@ Ext.define('PVE.Utils', { statics: {
 	    return gettext('Edit') + ': ' + subject;
 	}
     },
- 
+
     openDefaultConsoleWindow: function(allowSpice, vmtype, vmid, nodename, vmname) {
 	var dv = PVE.Utils.defaultViewer(allowSpice);
 	PVE.Utils.openConsoleWindow(dv, vmtype, vmid, nodename, vmname);
@@ -989,13 +991,13 @@ Ext.define('PVE.Utils', { statics: {
 		});
 		var url = 'data:application/x-virt-viewer;charset=UTF-8,' +
 		    encodeURIComponent(raw);
-		    
+
 		downloadWithName(url, "pve-spice.vv");
 	    }
 	});
     },
 
-    // comp.setLoading() is buggy in ExtJS 4.0.7, so we 
+    // comp.setLoading() is buggy in ExtJS 4.0.7, so we
     // use el.mask() instead
     setErrorMask: function(comp, msg) {
 	var el = comp.el;
diff --git a/www/manager/openvz/CmdMenu.js b/www/manager/openvz/CmdMenu.js
index 85589ed..6bb5326 100644
--- a/www/manager/openvz/CmdMenu.js
+++ b/www/manager/openvz/CmdMenu.js
@@ -37,7 +37,7 @@ Ext.define('PVE.openvz.CmdMenu', {
 		    vm_command('start');
 		}
 	    },
-	    { 
+	    {
 		text: gettext('Migrate'),
 		icon: '/pve2/images/forward.png',
 		handler: function() {
@@ -50,10 +50,30 @@ Ext.define('PVE.openvz.CmdMenu', {
 		}
 	    },
 	    {
+		text: gettext('Suspend'),
+		icon: '/pve2/images/forward.png',
+		handler: function() {
+		    var msg = Ext.String.format(gettext("Do you really want to suspend CT {0}?"), vmid);
+		    Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
+		    if (btn !== 'yes') {
+		        return;
+		    }
+		    vm_command('suspend');
+		    });
+		}
+		},
+		{
+		text: gettext('Resume'),
+		icon: '/pve2/images/forward.png',
+		handler: function() {
+		    vm_command('resume');
+		}
+		},
+		{
 		text: gettext('Shutdown'),
 		icon: '/pve2/images/stop.png',
 		handler: function() {
-		    var msg = Ext.String.format(gettext("Do you really want to shutdown VM {0}?"), vmid);
+		    var msg = Ext.String.format(gettext("Do you really want to shutdown CT {0}?"), vmid);
 		    Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
 			if (btn !== 'yes') {
 			    return;
@@ -61,13 +81,13 @@ Ext.define('PVE.openvz.CmdMenu', {
 
 			vm_command('shutdown');
 		    });
-		}			    
+		}
 	    },
 	    {
 		text: gettext('Stop'),
 		icon: '/pve2/images/gtk-stop.png',
 		handler: function() {
-		    var msg = Ext.String.format(gettext("Do you really want to stop VM {0}?"), vmid);
+		    var msg = Ext.String.format(gettext("Do you really want to stop CT {0}?"), vmid);
 		    Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
 			if (btn !== 'yes') {
 			    return;
diff --git a/www/manager/qemu/CmdMenu.js b/www/manager/qemu/CmdMenu.js
index 853f57b..25591e9 100644
--- a/www/manager/qemu/CmdMenu.js
+++ b/www/manager/qemu/CmdMenu.js
@@ -37,7 +37,7 @@ Ext.define('PVE.qemu.CmdMenu', {
 		    vm_command('start');
 		}
 	    },
-	    { 
+	    {
 		text: gettext('Migrate'),
 		icon: '/pve2/images/forward.png',
 		handler: function() {
@@ -50,6 +50,26 @@ Ext.define('PVE.qemu.CmdMenu', {
 		}
 	    },
 	    {
+        text: gettext('Suspend'),
+        icon: '/pve2/images/forward.png',
+        handler: function() {
+            var msg = Ext.String.format(gettext("Do you really want to suspend VM {0}?"), vmid);
+            Ext.Msg.confirm(gettext('Confirm'), msg, function(btn) {
+            if (btn !== 'yes') {
+                return;
+            }
+            vm_command('suspend');
+            });
+        }
+        },
+        {
+        text: gettext('Resume'),
+        icon: '/pve2/images/forward.png',
+        handler: function() {
+            vm_command('resume');
+        }
+        },
+        {
 		text: gettext('Shutdown'),
 		icon: '/pve2/images/stop.png',
 		handler: function() {
@@ -61,7 +81,7 @@ Ext.define('PVE.qemu.CmdMenu', {
 
 			vm_command('shutdown');
 		    });
-		}			    
+		}
 	    },
 	    {
 		text: gettext('Stop'),
@@ -74,7 +94,7 @@ Ext.define('PVE.qemu.CmdMenu', {
 			}
 
 			vm_command("stop");
-		    });		   
+		    });
 		}
 	    },
 	    {
diff --git a/www/mobile/OpenVzSummary.js b/www/mobile/OpenVzSummary.js
index f71fbec..4c27e93 100644
--- a/www/mobile/OpenVzSummary.js
+++ b/www/mobile/OpenVzSummary.js
@@ -31,11 +31,11 @@ Ext.define('PVE.OpenVzSummary', {
 
     config: {
 	items: [
-	    { 
+	    {
 		xtype: 'titlebar',
 		docked: 'top',
 		items: [
-		    { 
+		    {
 			xtype: 'button',
 			align: 'right',
 			iconCls: 'refresh',
@@ -67,7 +67,7 @@ Ext.define('PVE.OpenVzSummary', {
 			    if (!Ext.isDefined(values.mem)) {
 				return '-';
 			    }
-			    return PVE.Utils.format_size(values.mem || 0) + " of " + 
+			    return PVE.Utils.format_size(values.mem || 0) + " of " +
 				PVE.Utils.format_size(values.maxmem);
 			},
 			cpuinfo: function(values) {
@@ -158,31 +158,43 @@ Ext.define('PVE.OpenVzSummary', {
 		    me.vm_command("start", {});
 		}
 	    },
-	    { 
+	    {
+		text: gettext('Suspend'),
+		handler: function() {
+		    me.vm_command("suspend", {});
+		}
+	    },
+	    {
+		text: gettext('Resume'),
+		handler: function() {
+		    me.vm_command("resume", {});
+		}
+	    },
+	    {
 		text: gettext('Shutdown'),
 		handler: function() {
 		    me.vm_command("shutdown", {});
 		}
 	    },
-	    { 
+	    {
 		text: gettext('Stop'),
 		handler: function() {
 		    me.vm_command("stop", {});
 		}
 	    },
-	    { 
+	    {
 		text: gettext('Migrate'),
 		handler: function() {
-		    PVE.Workspace.gotoPage('nodes/' + me.nodename + '/openvz/' + me.vmid + '/migrate'); 
+		    PVE.Workspace.gotoPage('nodes/' + me.nodename + '/openvz/' + me.vmid + '/migrate');
 		}
 	    },
-	    { 
+	    {
 		text: gettext('Console'),
 		handler: function() {
 		    PVE.Utils.openConsoleWindow('html5', 'openvz', me.vmid, me.nodename);
 		}
 	    },
-	    { 
+	    {
 		text: gettext('Spice'),
 		handler: function() {
 		    PVE.Utils.openConsoleWindow('vv', 'openvz', me.vmid, me.nodename);
diff --git a/www/mobile/QemuSummary.js b/www/mobile/QemuSummary.js
index eb33222..b392e1e 100644
--- a/www/mobile/QemuSummary.js
+++ b/www/mobile/QemuSummary.js
@@ -31,11 +31,11 @@ Ext.define('PVE.QemuSummary', {
 
     config: {
 	items: [
-	    { 
+	    {
 		xtype: 'titlebar',
 		docked: 'top',
 		items: [
-		    { 
+		    {
 			xtype: 'button',
 			align: 'right',
 			iconCls: 'refresh',
@@ -67,7 +67,7 @@ Ext.define('PVE.QemuSummary', {
 			    if (!Ext.isDefined(values.mem)) {
 				return '-';
 			    }
-			    return PVE.Utils.format_size(values.mem || 0) + " of " + 
+			    return PVE.Utils.format_size(values.mem || 0) + " of " +
 				PVE.Utils.format_size(values.maxmem);
 			},
 			cpuinfo: function(values) {
@@ -131,8 +131,8 @@ Ext.define('PVE.QemuSummary', {
 	    success: function(response) {
 		var d = response.result.data;
 		var names = ['name', 'memory', 'sockets', 'cores', 'ostype',
-			     'bootdisk', /^net\d+/, 
-			     /^ide\d+/, /^virtio\d+/, /^sata\d+/, 
+			     'bootdisk', /^net\d+/,
+			     /^ide\d+/, /^virtio\d+/, /^sata\d+/,
 			     /^scsi\d+/, /^unused\d+/ ];
 		var kv = PVE.Workspace.obj_to_kv(d, names);
 		vmc.setData(kv);
@@ -161,31 +161,43 @@ Ext.define('PVE.QemuSummary', {
 		    me.vm_command("start", {});
 		}
 	    },
-	    { 
+	    {
+		text: gettext('Suspend'),
+		handler: function() {
+		    me.vm_command("suspend", {});
+		}
+	    },
+	    {
+		text: gettext('Resume'),
+		handler: function() {
+		    me.vm_command("resume", {});
+		}
+	    },
+	    {
 		text: gettext('Shutdown'),
 		handler: function() {
 		    me.vm_command("shutdown", {});
 		}
 	    },
-	    { 
+	    {
 		text: gettext('Stop'),
 		handler: function() {
 		    me.vm_command("stop", {});
 		}
 	    },
-	    { 
+	    {
 		text: gettext('Migrate'),
 		handler: function() {
-		    PVE.Workspace.gotoPage('nodes/' + me.nodename + '/qemu/' + me.vmid + '/migrate'); 
+		    PVE.Workspace.gotoPage('nodes/' + me.nodename + '/qemu/' + me.vmid + '/migrate');
 		}
 	    },
-	    { 
+	    {
 		text: gettext('Console'),
 		handler: function() {
 		    PVE.Utils.openConsoleWindow('html5', 'kvm', me.vmid, me.nodename);
 		}
 	    },
-	    { 
+	    {
 		text: gettext('Spice'),
 		handler: function() {
 		    PVE.Utils.openConsoleWindow('vv', 'kvm', me.vmid, me.nodename);
-- 
1.9.1




More information about the pve-devel mailing list