use warnings;
use Cwd 'abs_path';
use Net::SSLeay;
+use UUID;
use PVE::Cluster qw (cfs_read_file cfs_write_file);;
use PVE::SafeSyslog;
use PVE::AccessControl;
use PVE::INotify;
use PVE::Network;
+use PVE::API2::Firewall::VM;
use Data::Dumper; # fixme: remove
} elsif ($opt eq 'args' || $opt eq 'lock') {
die "only root can set '$opt' config\n";
} elsif ($opt eq 'cpu' || $opt eq 'kvm' || $opt eq 'acpi' || $opt eq 'machine' ||
- $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet') {
+ $opt eq 'vga' || $opt eq 'watchdog' || $opt eq 'tablet' || $opt eq 'smbios1') {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
} elsif ($opt =~ m/^net\d+$/) {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
$conf->{bootdisk} = $firstdisk;
}
+ # auto generate uuid if user did not specify smbios1 option
+ if (!$conf->{smbios1}) {
+ my ($uuid, $uuid_str);
+ UUID::generate($uuid);
+ UUID::unparse($uuid, $uuid_str);
+ $conf->{smbios1} = "uuid=$uuid_str";
+ }
+
PVE::QemuServer::update_config_nolock($vmid, $conf);
};
{ subdir => 'snapshot' },
{ subdir => 'spiceproxy' },
{ subdir => 'sendkey' },
+ { subdir => 'firewall' },
];
return $res;
}});
+__PACKAGE__->register_method ({
+ subclass => "PVE::API2::Firewall::VM",
+ path => '{vmid}/firewall',
+});
+
__PACKAGE__->register_method({
name => 'rrd',
path => '{vmid}/rrd',
PVE::Network::tap_rate_limit($iface, $newnet->{rate});
}
- if(($newnet->{bridge} ne $oldnet->{bridge}) || ($newnet->{tag} ne $oldnet->{tag})){
- eval{PVE::Network::tap_unplug($iface, $oldnet->{bridge}, $oldnet->{tag});};
- PVE::Network::tap_plug($iface, $newnet->{bridge}, $newnet->{tag});
+ if(($newnet->{bridge} ne $oldnet->{bridge}) || ($newnet->{tag} ne $oldnet->{tag}) || ($newnet->{firewall} ne $oldnet->{firewall})){
+ PVE::Network::tap_unplug($iface);
+ PVE::Network::tap_plug($iface, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
}
}else{
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
+ websocket => {
+ optional => 1,
+ type => 'boolean',
+ description => "starts websockify instead of vncproxy",
+ },
},
},
returns => {
my $vmid = $param->{vmid};
my $node = $param->{node};
+ my $websocket = $param->{websocket};
my $conf = PVE::QemuServer::load_config($vmid, $node); # check if VM exists
if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
$remip = PVE::Cluster::remote_node_ip($node);
- # NOTE: kvm VNC traffic is already TLS encrypted
+ # NOTE: kvm VNC traffic is already TLS encrypted or is known unsecure
$remcmd = ['/usr/bin/ssh', '-T', '-o', 'BatchMode=yes', $remip];
}
if ($conf->{vga} && ($conf->{vga} =~ m/^serial\d+$/)) {
+ die "Websocket mode is not supported in vga serial mode!" if $websocket;
+
my $termcmd = [ '/usr/sbin/qm', 'terminal', $vmid, '-iface', $conf->{vga} ];
#my $termcmd = "/usr/bin/qm terminal -iface $conf->{vga}";
$cmd = ['/usr/bin/vncterm', '-rfbport', $port,
'-perm', 'Sys.Console', '-c', @$remcmd, @$termcmd];
} else {
+ $ENV{LC_PVE_TICKET} = $ticket if $websocket; # set ticket with "qm vncproxy"
+
my $qmcmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid];
my $qmstr = join(' ', @$qmcmd);
};
}});
+__PACKAGE__->register_method({
+ name => 'vncwebsocket',
+ path => '{vmid}/vncwebsocket',
+ method => 'GET',
+ permissions => {
+ description => "You also need to pass a valid ticket (vncticket).",
+ check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
+ },
+ description => "Opens a weksocket for VNC traffic.",
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ vmid => get_standard_option('pve-vmid'),
+ vncticket => {
+ description => "Ticket from previous call to vncproxy.",
+ type => 'string',
+ maxLength => 512,
+ },
+ port => {
+ description => "Port number returned by previous vncproxy call.",
+ type => 'integer',
+ minimum => 5900,
+ maximum => 5999,
+ },
+ },
+ },
+ returns => {
+ type => "object",
+ properties => {
+ port => { type => 'string' },
+ },
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $rpcenv = PVE::RPCEnvironment::get();
+
+ my $authuser = $rpcenv->get_user();
+
+ my $vmid = $param->{vmid};
+ my $node = $param->{node};
+
+ my $authpath = "/vms/$vmid";
+
+ PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
+
+ my $conf = PVE::QemuServer::load_config($vmid, $node); # VM exists ?
+
+ # Note: VNC ports are acessible from outside, so we do not gain any
+ # security if we verify that $param->{port} belongs to VM $vmid. This
+ # check is done by verifying the VNC ticket (inside VNC protocol).
+
+ my $port = $param->{port};
+
+ return { port => $port };
+ }});
+
__PACKAGE__->register_method({
name => 'spiceproxy',
path => '{vmid}/spiceproxy',
my $node = $param->{node};
my $proxy = $param->{proxy};
- my $conf = PVE::QemuServer::load_config($vmid, $node);
- my $title = "VM $vmid - $conf->{'name'}",
+ my $conf = PVE::QemuServer::load_config($vmid, $node);
+ my $title = "VM $vmid - $conf->{'name'}",
my $port = PVE::QemuServer::spice_port($vmid);
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid'),
skiplock => get_standard_option('skiplock'),
- migratedfrom => get_standard_option('pve-node',{ optional => 1 }),
+ migratedfrom => get_standard_option('pve-node', { optional => 1 }),
timeout => {
description => "Wait maximal timeout seconds.",
type => 'integer',
my $storecfg = PVE::Storage::config();
- if (&$vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
+ if (&$vm_is_ha_managed($vmid) && ($rpcenv->{type} ne 'ha') && !defined($migratedfrom)) {
my $hacmd = sub {
my $upid = shift;
description => "Add the new VM to the specified pool.",
},
snapname => get_standard_option('pve-snapshot-name', {
- requires => 'full',
optional => 1,
}),
storage => get_standard_option('pve-storage-id', {
if (PVE::QemuServer::drive_is_cdrom($drive)) {
$newconf->{$opt} = $value; # simply copy configuration
} else {
- if ($param->{full} || !PVE::Storage::volume_is_base($storecfg, $drive->{file})) {
+ if ($param->{full}) {
die "Full clone feature is not available"
if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
$drive->{full} = 1;
+ } else {
+ # not full means clone instead of copy
+ die "Linked clone feature is not available"
+ if !PVE::Storage::volume_has_feature($storecfg, 'clone', $drive->{file}, $snapname, $running);
}
$drives->{$opt} = $drive;
push @$vollist, $drive->{file};
}
}
+ # auto generate a new uuid
+ my ($uuid, $uuid_str);
+ UUID::generate($uuid);
+ UUID::unparse($uuid, $uuid_str);
+ my $smbios1 = PVE::QemuServer::parse_smbios1($newconf->{smbios1} || '');
+ $smbios1->{uuid} = $uuid_str;
+ $newconf->{smbios1} = PVE::QemuServer::print_smbios1($smbios1);
+
delete $newconf->{template};
if ($param->{name}) {
}
if ($param->{delete}) {
- eval { PVE::Storage::vdisk_free($storecfg, $old_volid); };
- warn $@ if $@;
+ my $used_paths = PVE::QemuServer::get_used_paths($vmid, $storecfg, $conf, 1, 1);
+ my $path = PVE::Storage::path($storecfg, $old_volid);
+ if ($used_paths->{$path}){
+ warn "volume $old_volid have snapshots. Can't delete it\n";
+ PVE::QemuServer::add_unused_volume($conf, $old_volid);
+ PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+ } else {
+ eval { PVE::Storage::vdisk_free($storecfg, $old_volid); };
+ warn $@ if $@;
+ }
}
};
die "you can't resize a cdrom\n" if PVE::QemuServer::drive_is_cdrom($drive);
- die "you can't online resize a virtio windows bootdisk\n"
- if PVE::QemuServer::check_running($vmid) && $conf->{bootdisk} eq $disk && $conf->{ostype} =~ m/^w/ && $disk =~ m/^virtio/;
-
my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
$rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);