'vmstatestorage' => 1,
};
+my $cloudinitoptions = {
+ cipassword => 1,
+ citype => 1,
+ ciuser => 1,
+ nameserver => 1,
+ searchdomain => 1,
+ sshkeys => 1,
+};
+
my $check_vm_modify_config_perm = sub {
my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.PowerMgmt']);
} elsif ($diskoptions->{$opt}) {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']);
- } elsif ($opt =~ m/^(?:net|ipconfig)\d+$/) {
+ } elsif ($cloudinitoptions->{$opt} || ($opt =~ m/^(?:net|ipconfig)\d+$/)) {
$rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']);
} else {
# catches usb\d+, hostpci\d+, args, lock, etc.
type => 'array',
items => {
type => "object",
- properties => {},
+ properties => $PVE::QemuServer::vmstatus_return_properties,
},
links => [ { rel => 'child', href => "{vmid}" } ],
},
next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
my $data = $vmstatus->{$vmid};
- $data->{vmid} = int($vmid);
push @$res, $data;
}
type => 'string', format => 'pve-poolid',
description => "Add the VM to the specified pool.",
},
+ bwlimit => {
+ description => "Override i/o bandwidth limit (in KiB/s).",
+ optional => 1,
+ type => 'integer',
+ minimum => '0',
+ },
+ start => {
+ optional => 1,
+ type => 'boolean',
+ default => 0,
+ description => "Start VM after it was created successfully.",
+ },
}),
},
returns => {
my $vmid = extract_param($param, 'vmid');
my $archive = extract_param($param, 'archive');
+ my $is_restore = !!$archive;
my $storage = extract_param($param, 'storage');
my $pool = extract_param($param, 'pool');
+ my $bwlimit = extract_param($param, 'bwlimit');
+
+ my $start_after_create = extract_param($param, 'start');
+
my $filename = PVE::QemuConfig->config_file($vmid);
my $storecfg = PVE::Storage::config();
}
}
- my $restorefn = sub {
- my $vmlist = PVE::Cluster::get_vmlist();
- if ($vmlist->{ids}->{$vmid}) {
- my $current_node = $vmlist->{ids}->{$vmid}->{node};
- if ($current_node eq $node) {
- my $conf = PVE::QemuConfig->load_config($vmid);
-
- PVE::QemuConfig->check_protection($conf, "unable to restore VM $vmid");
+ my $emsg = $is_restore ? "unable to restore VM $vmid -" : "unable to create VM $vmid -";
- die "unable to restore vm $vmid - config file already exists\n"
- if !$force;
+ eval { PVE::QemuConfig->create_and_lock_config($vmid, $force) };
+ die "$emsg $@" if $@;
- die "unable to restore vm $vmid - vm is running\n"
- if PVE::QemuServer::check_running($vmid);
+ my $restorefn = sub {
+ my $conf = PVE::QemuConfig->load_config($vmid);
- die "unable to restore vm $vmid - vm is a template\n"
- if PVE::QemuConfig->is_template($conf);
+ PVE::QemuConfig->check_protection($conf, $emsg);
- } else {
- die "unable to restore vm $vmid - already existing on cluster node '$current_node'\n";
- }
- }
+ die "$emsg vm is running\n" if PVE::QemuServer::check_running($vmid);
+ die "$emsg vm is a template\n" if PVE::QemuConfig->is_template($conf);
my $realcmd = sub {
PVE::QemuServer::restore_archive($archive, $vmid, $authuser, {
storage => $storage,
pool => $pool,
- unique => $unique });
+ unique => $unique,
+ bwlimit => $bwlimit, });
PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
+
+ if ($start_after_create) {
+ eval { PVE::API2::Qemu->vm_start({ vmid => $vmid, node => $node }) };
+ warn $@ if $@;
+ }
};
# ensure no old replication state are exists
PVE::ReplicationState::delete_guest_states($vmid);
- return $rpcenv->fork_worker('qmrestore', $vmid, $authuser, $realcmd);
+ return PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd);
};
my $createfn = sub {
-
- # test after locking
- PVE::Cluster::check_vmid_unused($vmid);
-
# ensure no old replication state are exists
PVE::ReplicationState::delete_guest_states($vmid);
eval { PVE::Storage::vdisk_free($storecfg, $volid); };
warn $@ if $@;
}
- die "create failed - $err";
+ die "$emsg $err";
}
PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
};
- return $rpcenv->fork_worker('qmcreate', $vmid, $authuser, $realcmd);
+ PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd);
+
+ if ($start_after_create) {
+ print "Execute autostart\n";
+ eval { PVE::API2::Qemu->vm_start({vmid => $vmid, node => $node}) };
+ warn $@ if $@;
+ }
};
- return PVE::QemuConfig->lock_config_full($vmid, 1, $archive ? $restorefn : $createfn);
+ my ($code, $worker_name);
+ if ($is_restore) {
+ $worker_name = 'qmrestore';
+ $code = sub {
+ eval { $restorefn->() };
+ if (my $err = $@) {
+ eval { PVE::QemuConfig->remove_lock($vmid, 'create') };
+ warn $@ if $@;
+ die $err;
+ }
+ };
+ } else {
+ $worker_name = 'qmcreate';
+ $code = sub {
+ eval { $createfn->() };
+ if (my $err = $@) {
+ eval {
+ my $conffile = PVE::QemuConfig->config_file($vmid);
+ unlink($conffile)
+ or die "failed to remove config file: $@\n";
+ };
+ warn $@ if $@;
+ die $err;
+ }
+ };
+ }
+
+ return $rpcenv->fork_worker($worker_name, $vmid, $authuser, $code);
}});
__PACKAGE__->register_method({
},
},
returns => {
+ description => "The current VM configuration.",
type => "object",
- properties => {
+ properties => PVE::QemuServer::json_config_properties({
digest => {
type => 'string',
description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.',
}
- },
+ }),
},
code => sub {
my ($param) = @_;
delete $conf->{pending};
+ # hide cloudinit password
+ if ($conf->{cipassword}) {
+ $conf->{cipassword} = '**********';
+ }
+
return $conf;
}});
$item->{value} = $conf->{$opt} if defined($conf->{$opt});
$item->{pending} = $conf->{pending}->{$opt} if defined($conf->{pending}->{$opt});
$item->{delete} = ($pending_delete_hash->{$opt} ? 2 : 1) if exists $pending_delete_hash->{$opt};
+
+ # hide cloudinit password
+ if ($opt eq 'cipassword') {
+ $item->{value} = '**********' if defined($item->{value});
+ # the trailing space so that the pending string is different
+ $item->{pending} = '********** ' if defined($item->{pending});
+ }
push @$res, $item;
}
next if defined($conf->{$opt});
my $item = { key => $opt };
$item->{pending} = $conf->{pending}->{$opt};
+
+ # hide cloudinit password
+ if ($opt eq 'cipassword') {
+ $item->{pending} = '**********' if defined($item->{pending});
+ }
push @$res, $item;
}
vmid => get_standard_option('pve-vmid'),
},
},
- returns => { type => 'object' },
+ returns => {
+ type => 'object',
+ properties => {
+ %$PVE::QemuServer::vmstatus_return_properties,
+ ha => {
+ description => "HA manager service status.",
+ type => 'object',
+ },
+ spice => {
+ description => "Qemu VGA configuration supports spice.",
+ type => 'boolean',
+ optional => 1,
+ },
+ agent => {
+ description => "Qemu GuestAgent enabled in config.",
+ type => 'boolean',
+ optional => 1,
+ },
+ },
+ },
code => sub {
my ($param) = @_;
$status->{ha} = PVE::HA::Config::get_service_status("vm:$param->{vmid}");
$status->{spice} = 1 if PVE::QemuServer::vga_conf_has_spice($conf->{vga});
-
- $status->{agent} = 1 if $conf->{agent};
+ $status->{agent} = 1 if (PVE::QemuServer::parse_guest_agent($conf)->{enabled});
return $status;
}});
properties => {
node => get_standard_option('pve-node'),
vmid => get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
- newid => get_standard_option('pve-vmid', { description => 'VMID for the clone.' }),
+ newid => get_standard_option('pve-vmid', {
+ completion => \&PVE::Cluster::complete_next_vmid,
+ description => 'VMID for the clone.' }),
name => {
optional => 1,
type => 'string', format => 'dns-name',
}),
storage => get_standard_option('pve-storage-id', {
description => "Target storage for full clone.",
- requires => 'full',
optional => 1,
}),
'format' => {
- description => "Target format for file storage.",
- requires => 'full',
+ description => "Target format for file storage. Only valid for full clone.",
type => 'string',
optional => 1,
enum => [ 'raw', 'qcow2', 'vmdk'],
full => {
optional => 1,
type => 'boolean',
- description => "Create a full copy of all disk. This is always done when " .
+ description => "Create a full copy of all disks. This is always done when " .
"you clone a normal VM. For VM templates, we try to create a linked clone by default.",
- default => 0,
},
target => get_standard_option('pve-node', {
description => "Target node. Only allowed if the original VM is on shared storage.",
die "snapshot '$snapname' does not exist\n"
if $snapname && !defined( $conf->{snapshots}->{$snapname});
+ my $full = extract_param($param, 'full');
+ if (!defined($full)) {
+ $full = !PVE::QemuConfig->is_template($conf);
+ }
+
+ die "parameter 'storage' not allowed for linked clones\n"
+ if defined($storage) && !$full;
+
+ die "parameter 'format' not allowed for linked clones\n"
+ if defined($format) && !$full;
+
my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf;
my $sharedvm = &$check_storage_access_clone($rpcenv, $authuser, $storecfg, $oldconf, $storage);
if (PVE::QemuServer::drive_is_cdrom($drive, 1)) {
$newconf->{$opt} = $value; # simply copy configuration
} else {
- if ($param->{full} || PVE::QemuServer::drive_is_cloudinit($drive)) {
+ if ($full || PVE::QemuServer::drive_is_cloudinit($drive)) {
die "Full clone feature is not supported for drive '$opt'\n"
if !PVE::Storage::volume_has_feature($storecfg, 'copy', $drive->{file}, $snapname, $running);
$fullclone->{$opt} = 1;
}
delete $newconf->{lock};
+
+ # do not write pending changes
+ if ($newconf->{pending}) {
+ warn "found pending changes, discarding for clone\n";
+ delete $newconf->{pending};
+ }
+
PVE::QemuConfig->write_config($newid, $newconf);
if ($target) {
type => 'array',
items => {
type => "object",
- properties => {},
+ properties => {
+ name => {
+ description => "Snapshot identifier. Value 'current' identifies the current VM.",
+ type => 'string',
+ },
+ vmstate => {
+ description => "Snapshot includes RAM.",
+ type => 'boolean',
+ optional => 1,
+ },
+ description => {
+ description => "Snapshot description.",
+ type => 'string',
+ },
+ snaptime => {
+ description => "Snapshot creation time",
+ type => 'integer',
+ renderer => 'timestamp',
+ optional => 1,
+ },
+ parent => {
+ description => "Parent snapshot identifier.",
+ type => 'string',
+ optional => 1,
+ },
+ },
},
links => [ { rel => 'child', href => "{name}" } ],
},
}
my $running = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
- my $current = { name => 'current', digest => $conf->{digest}, running => $running };
+ my $current = {
+ name => 'current',
+ digest => $conf->{digest},
+ running => $running,
+ description => "You are here!",
+ };
$current->{parent} = $conf->{parent} if $conf->{parent};
push @$res, $current;