diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 8b51c043..84f26f0a 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -1717,7 +1717,7 @@ __PACKAGE__->register_method({ my $vmid = $param->{vmid}; my $conf = PVE::QemuConfig->load_config($vmid); - my $ci = $conf->{cloudinit}; + my $ci = $conf->{'special-sections'}->{cloudinit}; $conf->{cipassword} = '**********' if exists $conf->{cipassword}; $ci->{cipassword} = '**********' if exists $ci->{cipassword}; @@ -6042,9 +6042,25 @@ __PACKAGE__->register_method({ # not handled by update_vm_api my $vmgenid = delete $new_conf->{vmgenid}; my $meta = delete $new_conf->{meta}; - my $cloudinit = delete $new_conf->{cloudinit}; # this is informational only + + my $special_sections = delete $new_conf->{'special-sections'} // {}; + + # fleecing state is specific to source side + delete $special_sections->{fleecing}; + $new_conf->{skip_cloud_init} = 1; # re-use image from source side + # TODO PVE 10 - remove backwards-compat handling? + my $cloudinit = delete $new_conf->{cloudinit}; + if ($cloudinit) { + if ($special_sections->{cloudinit}) { + warn "config has duplicate special 'cloudinit' sections - skipping" + ." legacy variant\n"; + } else { + $special_sections->{cloudinit} = $cloudinit; + } + } + $new_conf->{vmid} = $state->{vmid}; $new_conf->{node} = $node; @@ -6066,7 +6082,7 @@ __PACKAGE__->register_method({ $conf->{lock} = 'migrate'; $conf->{vmgenid} = $vmgenid if defined($vmgenid); $conf->{meta} = $meta if defined($meta); - $conf->{cloudinit} = $cloudinit if defined($cloudinit); + $conf->{'special-sections'} = $special_sections; PVE::QemuConfig->write_config($state->{vmid}, $conf); $state->{lock} = 'migrate'; diff --git a/PVE/QemuConfig.pm b/PVE/QemuConfig.pm index b60cc398..2609542c 100644 --- a/PVE/QemuConfig.pm +++ b/PVE/QemuConfig.pm @@ -15,6 +15,7 @@ use PVE::QemuServer::Monitor qw(mon_cmd); use PVE::QemuServer; use PVE::QemuServer::Machine; use PVE::QemuServer::Memory qw(get_current_memory); +use PVE::RESTEnvironment qw(log_warn); use PVE::Storage; use PVE::Tools; use PVE::Format qw(render_bytes render_duration); @@ -543,7 +544,7 @@ sub load_current_config { my ($class, $vmid, $current) = @_; my $conf = $class->SUPER::load_current_config($vmid, $current); - delete $conf->{cloudinit}; + delete $conf->{'special-sections'}; return $conf; } @@ -593,4 +594,85 @@ sub has_cloudinit { return $found; } +# Caller is expected to deal with volumes from an already existing 'fleecing' special section in the +# configuration first. +sub record_fleecing_images { + my ($vmid, $volids) = @_; + + return if scalar($volids->@*) == 0; + + PVE::QemuConfig->lock_config($vmid, sub { + my $conf = PVE::QemuConfig->load_config($vmid); + $conf->{'special-sections'}->{fleecing}->{'fleecing-images'} = join(',', $volids->@*); + PVE::QemuConfig->write_config($vmid, $conf); + }); +} + +# Will also cancel a running backup job inside QEMU. Not doing so can lead to a deadlock when +# attempting to detach the fleecing image. +sub cleanup_fleecing_images { + my ($vmid, $storecfg, $log_func) = @_; + + if (!$log_func) { + $log_func = sub { + my ($level, $line) = @_; + chomp($line); + if ($level eq 'info') { + print "$line\n"; + } else { + log_warn($line); + } + }; + } + + my $volids = []; + my $failed = []; + + # cancel left-over backup job and detach any left-over images from a running VM + if (PVE::QemuServer::Helpers::vm_running_locally($vmid)) { + eval { + if (my $status = mon_cmd($vmid, 'query-backup')) { + if ($status->{status} && $status->{status} eq 'active') { + $log_func->('warn', "left-over backup job still running inside QEMU - canceling now"); + mon_cmd($vmid, 'backup-cancel'); + } + } + }; + $log_func->('warn', "checking/canceling old backup job failed - $@") if $@; + + my $block_info = mon_cmd($vmid, "query-block"); + for my $info ($block_info->@*) { + my $device_id = $info->{device}; + next if $device_id !~ m/-fleecing$/; + + $log_func->('info', "detaching (old) fleecing image for '$device_id'"); + $device_id =~ s/^drive-//; # re-added by qemu_drivedel() + eval { PVE::QemuServer::qemu_drivedel($vmid, $device_id) }; + $log_func->('warn', "error detaching (old) fleecing image '$device_id' - $@") if $@; + } + } + + PVE::QemuConfig->lock_config($vmid, sub { + my $conf = PVE::QemuConfig->load_config($vmid); + my $special = $conf->{'special-sections'}; + if (my $fleecing = $special->{fleecing}) { + $volids = [PVE::Tools::split_list($fleecing->{'fleecing-images'})]; + delete $fleecing->{'fleecing-images'}; + delete $special->{fleecing} if !scalar(keys $fleecing->%*); + PVE::QemuConfig->write_config($vmid, $conf); + } + }); + + for my $volid ($volids->@*) { + $log_func->('info', "removing (old) fleecing image '$volid'"); + eval { PVE::Storage::vdisk_free($storecfg, $volid); }; + if (my $err = $@) { + $log_func->('warn', "error removing fleecing image '$volid' - $err"); + push $failed->@*, $volid; + } + } + + record_fleecing_images($vmid, $failed); +} + 1; diff --git a/PVE/QemuMigrate.pm b/PVE/QemuMigrate.pm index babd81a1..b7bf2aa3 100644 --- a/PVE/QemuMigrate.pm +++ b/PVE/QemuMigrate.pm @@ -178,11 +178,17 @@ sub prepare { my $storecfg = $self->{storecfg} = PVE::Storage::config(); + # updates the configuration, so ordered before saving the configuration in $self + eval { + PVE::QemuConfig::cleanup_fleecing_images( + $vmid, $storecfg, sub { $self->log($_[0], $_[1]); }); + }; + $self->log('warn', "attempt to clean up left-over fleecing images failed - $@") if $@; + # test if VM exists my $conf = $self->{vmconf} = PVE::QemuConfig->load_config($vmid); my $version = PVE::QemuServer::Helpers::get_node_pvecfg_version($self->{node}); - my $cloudinit_config = $conf->{cloudinit}; my $repl_conf = PVE::ReplicationConfig->new(); $self->{replication_jobcfg} = $repl_conf->find_local_replication_job($vmid, $self->{node}); diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index ea453667..a97e77b3 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -1829,7 +1829,7 @@ sub vmconfig_register_unused_drive { if (drive_is_cloudinit($drive)) { eval { PVE::Storage::vdisk_free($storecfg, $drive->{file}) }; warn $@ if $@; - delete $conf->{cloudinit}; + delete $conf->{'special-sections'}->{cloudinit}; } elsif (!drive_is_cdrom($drive)) { my $volid = $drive->{file}; if (vm_is_volid_owner($storecfg, $vmid, $volid)) { @@ -2051,11 +2051,13 @@ sub cloudinit_pending_properties { } sub check_type { - my ($key, $value) = @_; + my ($key, $value, $schema) = @_; - die "unknown setting '$key'\n" if !$confdesc->{$key}; + die "check_type: no schema defined\n" if !$schema; - my $type = $confdesc->{$key}->{type}; + die "unknown setting '$key'\n" if !$schema->{$key}; + + my $type = $schema->{$key}->{type}; if (!defined($value)) { die "got undefined value\n"; @@ -2076,7 +2078,7 @@ sub check_type { return $value if $value =~ m/^(\d+)(\.\d+)?$/; die "type check ('number') failed - got '$value'\n"; } elsif ($type eq 'string') { - if (my $fmt = $confdesc->{$key}->{format}) { + if (my $fmt = $schema->{$key}->{format}) { PVE::JSONSchema::check_format($fmt, $value); return $value; } @@ -2090,6 +2092,9 @@ sub check_type { sub destroy_vm { my ($storecfg, $vmid, $skiplock, $replacement_conf, $purge_unreferenced) = @_; + eval { PVE::QemuConfig::cleanup_fleecing_images($vmid, $storecfg) }; + log_warn("attempt to clean up left-over fleecing images failed - $@") if $@; + my $conf = PVE::QemuConfig->load_config($vmid); if (!$skiplock && !PVE::QemuConfig->has_lock($conf, 'suspended')) { @@ -2163,16 +2168,27 @@ sub destroy_vm { } } +my $fleecing_section_schema = { + 'fleecing-images' => { + type => 'string', + format => 'pve-volume-id-list', + description => "For internal use only. List of fleecing images allocated during backup." + ." If no backup is running, these are left-overs that failed to be removed.", + optional => 1, + }, +}; + sub parse_vm_config { my ($filename, $raw, $strict) = @_; return if !defined($raw); + # note that pending, snapshot and special sections are currently skipped when a backup is taken my $res = { digest => Digest::SHA::sha1_hex($raw), snapshots => {}, - pending => {}, - cloudinit => {}, + pending => undef, + 'special-sections' => {}, }; my $handle_error = sub { @@ -2199,30 +2215,52 @@ sub parse_vm_config { } $descr = undef; }; - my $section = ''; + + my $special_schemas = { + cloudinit => $confdesc, # not actually used right now, see below + fleecing => $fleecing_section_schema, + }; + my $special_sections_re_string = join('|', keys $special_schemas->%*); + my $special_sections_re_1 = qr/($special_sections_re_string)/; + + my $section = { name => '', type => 'main', schema => $confdesc }; my @lines = split(/\n/, $raw); foreach my $line (@lines) { next if $line =~ m/^\s*$/; if ($line =~ m/^\[PENDING\]\s*$/i) { - $section = 'pending'; + $section = { name => 'pending', type => 'pending', schema => $confdesc }; $finish_description->(); - $conf = $res->{$section} = {}; + $handle_error->("vm $vmid - duplicate section: $section->{name}\n") + if defined($res->{$section->{name}}); + $conf = $res->{$section->{name}} = {}; next; - } elsif ($line =~ m/^\[special:cloudinit\]\s*$/i) { - $section = 'cloudinit'; + } elsif ($line =~ m/^\[special:$special_sections_re_1\]\s*$/i) { + $section = { name => $1, type => 'special', schema => $special_schemas->{$1} }; $finish_description->(); - $conf = $res->{$section} = {}; + $handle_error->("vm $vmid - duplicate special section: $section->{name}\n") + if defined($res->{'special-sections'}->{$section->{name}}); + $conf = $res->{'special-sections'}->{$section->{name}} = {}; next; } elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) { - $section = $1; + $section = { name => $1, type => 'snapshot', schema => $confdesc }; $finish_description->(); - $conf = $res->{snapshots}->{$section} = {}; + $handle_error->("vm $vmid - duplicate snapshot section: $section->{name}\n") + if defined($res->{snapshots}->{$section->{name}}); + $conf = $res->{snapshots}->{$section->{name}} = {}; + next; + } elsif ($line =~ m/^\[([^\]]*)\]\s*$/i) { + my $unknown_section = $1; + $section = undef; + $finish_description->(); + $handle_error->("vm $vmid - skipping unknown section: '$unknown_section'\n"); next; } + next if !defined($section); + if ($line =~ m/^\#(.*)$/) { $descr = '' if !defined($descr); $descr .= PVE::Tools::decode_text($1) . "\n"; @@ -2240,7 +2278,7 @@ sub parse_vm_config { $conf->{$key} = $value; } elsif ($line =~ m/^delete:\s*(.*\S)\s*$/) { my $value = $1; - if ($section eq 'pending') { + if ($section->{name} eq 'pending' && $section->{type} eq 'pending') { $conf->{delete} = $value; # we parse this later } else { $handle_error->("vm $vmid - property 'delete' is only allowed in [PENDING]\n"); @@ -2248,17 +2286,17 @@ sub parse_vm_config { } elsif ($line =~ m/^([a-z][a-z_\-]*\d*):\s*(.+?)\s*$/) { my $key = $1; my $value = $2; - if ($section eq 'cloudinit') { + if ($section->{name} eq 'cloudinit' && $section->{type} eq 'special') { # ignore validation only used for informative purpose $conf->{$key} = $value; next; } - eval { $value = check_type($key, $value); }; + eval { $value = check_type($key, $value, $section->{schema}); }; if ($@) { $handle_error->("vm $vmid - unable to parse value of '$key' - $@"); } else { $key = 'ide2' if $key eq 'cdrom'; - my $fmt = $confdesc->{$key}->{format}; + my $fmt = $section->{schema}->{$key}->{format}; if ($fmt && $fmt =~ /^pve-qm-(?:ide|scsi|virtio|sata)$/) { my $v = parse_drive($key, $value); if (my $volid = filename_to_volume_id($vmid, $v->{file}, $v->{media})) { @@ -2280,6 +2318,8 @@ sub parse_vm_config { $finish_description->(); delete $res->{snapstate}; # just to be sure + $res->{pending} = {} if !defined($res->{pending}); + return $res; } @@ -2310,7 +2350,7 @@ sub write_vm_config { foreach my $key (keys %$cref) { next if $key eq 'digest' || $key eq 'description' || $key eq 'snapshots' || - $key eq 'snapstate' || $key eq 'pending' || $key eq 'cloudinit'; + $key eq 'snapstate' || $key eq 'pending' || $key eq 'special-sections'; my $value = $cref->{$key}; if ($key eq 'delete') { die "propertry 'delete' is only allowed in [PENDING]\n" @@ -2318,7 +2358,7 @@ sub write_vm_config { # fixme: check syntax? next; } - eval { $value = check_type($key, $value); }; + eval { $value = check_type($key, $value, $confdesc); }; die "unable to parse value of '$key' - $@" if $@; $cref->{$key} = $value; @@ -2364,7 +2404,7 @@ sub write_vm_config { } foreach my $key (sort keys %$conf) { - next if $key =~ /^(digest|description|pending|cloudinit|snapshots)$/; + next if $key =~ /^(digest|description|pending|snapshots|special-sections)$/; $raw .= "$key: $conf->{$key}\n"; } return $raw; @@ -2377,9 +2417,10 @@ sub write_vm_config { $raw .= &$generate_raw_config($conf->{pending}, 1); } - if (scalar(keys %{$conf->{cloudinit}}) && PVE::QemuConfig->has_cloudinit($conf)){ - $raw .= "\n[special:cloudinit]\n"; - $raw .= &$generate_raw_config($conf->{cloudinit}); + for my $special (sort keys $conf->{'special-sections'}->%*) { + next if $special eq 'cloudinit' && !PVE::QemuConfig->has_cloudinit($conf); + $raw .= "\n[special:$special]\n"; + $raw .= &$generate_raw_config($conf->{'special-sections'}->{$special}); } foreach my $snapname (sort keys %{$conf->{snapshots}}) { @@ -4740,7 +4781,7 @@ sub vmconfig_hotplug_pending { my ($conf, $opt, $old, $new) = @_; return if !$cloudinit_pending_properties->{$opt}; - my $ci = ($conf->{cloudinit} //= {}); + my $ci = ($conf->{'special-sections'}->{cloudinit} //= {}); my $recorded = $ci->{$opt}; my %added = map { $_ => 1 } PVE::Tools::split_list(delete($ci->{added}) // ''); @@ -5131,7 +5172,7 @@ sub vmconfig_apply_pending { if ($generate_cloudinit) { if (PVE::QemuServer::Cloudinit::apply_cloudinit_config($conf, $vmid)) { # After successful generation and if there were changes to be applied, update the - # config to drop the {cloudinit} entry. + # config to drop the 'cloudinit' special section. PVE::QemuConfig->write_config($vmid, $conf); } } @@ -5562,7 +5603,7 @@ sub vm_start_nolock { if (!$migratedfrom) { if (PVE::QemuServer::Cloudinit::apply_cloudinit_config($conf, $vmid)) { # FIXME: apply_cloudinit_config updates $conf in this case, and it would only drop - # $conf->{cloudinit}, so we could just not do this? + # $conf->{'special-sections'}->{cloudinit}, so we could just not do this? # But we do it above, so for now let's be consistent. $conf = PVE::QemuConfig->load_config($vmid); # update/reload } diff --git a/PVE/QemuServer/Cloudinit.pm b/PVE/QemuServer/Cloudinit.pm index f1143aeb..001022e6 100644 --- a/PVE/QemuServer/Cloudinit.pm +++ b/PVE/QemuServer/Cloudinit.pm @@ -657,7 +657,11 @@ my $cloudinit_methods = { sub has_changes { my ($conf) = @_; - return !!$conf->{cloudinit}->%*; + if (my $cloudinit = $conf->{'special-sections'}->{cloudinit}) { + return !!$cloudinit->%*; + } + + return; } sub generate_cloudinit_config { @@ -689,7 +693,7 @@ sub apply_cloudinit_config { my $has_changes = generate_cloudinit_config($conf, $vmid); if ($has_changes) { - delete $conf->{cloudinit}; + delete $conf->{'special-sections'}->{cloudinit}; PVE::QemuConfig->write_config($vmid, $conf); return 1; } diff --git a/PVE/VZDump/QemuServer.pm b/PVE/VZDump/QemuServer.pm index 0930a0f9..4860798e 100644 --- a/PVE/VZDump/QemuServer.pm +++ b/PVE/VZDump/QemuServer.pm @@ -236,20 +236,21 @@ sub assemble { my $found_snapshot; my $found_pending; - my $found_cloudinit; + my $found_special; while (defined (my $line = <$conffd>)) { next if $line =~ m/^\#vzdump\#/; # just to be sure next if $line =~ m/^\#qmdump\#/; # just to be sure if ($line =~ m/^\[(.*)\]\s*$/) { - if ($1 =~ m/PENDING/i) { + if ($1 =~ m/^PENDING$/i) { $found_pending = 1; - } elsif ($1 =~ m/special:cloudinit/) { - $found_cloudinit = 1; + } elsif ($1 =~ m/^special:.*$/) { + $found_special = 1; } else { $found_snapshot = 1; } } - next if $found_snapshot || $found_pending || $found_cloudinit; # skip all snapshots,pending changes and cloudinit config data + # skip all snapshots, pending changes and special sections + next if $found_snapshot || $found_pending || $found_special; if ($line =~ m/^unused\d+:\s*(\S+)\s*/) { $self->loginfo("skip unused drive '$1' (not included into backup)"); @@ -270,6 +271,9 @@ sub assemble { } } + if ($found_special) { + $self->loginfo("special config section found (not included into backup)"); + } if ($found_snapshot) { $self->loginfo("snapshots found (not included into backup)"); } @@ -539,15 +543,25 @@ sub get_and_check_pbs_encryption_config { die "internal error - unhandled case for getting & checking PBS encryption ($keyfile, $master_keyfile)!"; } +# Helper is intended to be called from allocate_fleecing_images() only. Otherwise, fleecing volids +# have already been recorded in the configuration and PVE::QemuConfig::cleanup_fleecing_images() +# should be used instead. my sub cleanup_fleecing_images { - my ($self, $disks) = @_; + my ($self, $vmid, $disks) = @_; + + my $failed = []; for my $di ($disks->@*) { if (my $volid = $di->{'fleece-volid'}) { eval { PVE::Storage::vdisk_free($self->{storecfg}, $volid); }; - $self->log('warn', "error removing fleecing image '$volid' - $@") if $@; + if (my $err = $@) { + $self->log('warn', "error removing fleecing image '$volid' - $err"); + push $failed->@*, $volid; + } } } + + PVE::QemuConfig::record_fleecing_images($vmid, $failed); } my sub allocate_fleecing_images { @@ -555,8 +569,7 @@ my sub allocate_fleecing_images { die "internal error - no fleecing storage specified\n" if !$fleecing_storeid; - # TODO what about potential left-over images from a failed attempt? Just - # auto-remove? While unlikely, could conflict with manually created image from user... + my $fleece_volids = []; eval { my $n = 0; # counter for fleecing image names @@ -582,6 +595,8 @@ my sub allocate_fleecing_images { $di->{'fleece-volid'} = PVE::Storage::vdisk_alloc( $self->{storecfg}, $fleecing_storeid, $vmid, $format, $name, $size); + push $fleece_volids->@*, $di->{'fleece-volid'}; + $n++; } else { die "implement me (type '$di->{type}')"; @@ -589,9 +604,11 @@ my sub allocate_fleecing_images { } }; if (my $err = $@) { - cleanup_fleecing_images($self, $disks); + cleanup_fleecing_images($self, $vmid, $disks); die $err; } + + PVE::QemuConfig::record_fleecing_images($vmid, $fleece_volids); } my sub detach_fleecing_images { @@ -651,6 +668,13 @@ my sub check_and_prepare_fleecing { $use_fleecing = 0; } + # clean up potential left-overs from a previous attempt + eval { + PVE::QemuConfig::cleanup_fleecing_images( + $vmid, $self->{storecfg}, sub { $self->log($_[0], $_[1]); }); + }; + $self->log('warn', "attempt to clean up left-over fleecing images failed - $@") if $@; + if ($use_fleecing) { $self->query_block_node_sizes($vmid, $disks); @@ -1243,7 +1267,11 @@ sub cleanup { } $detach_tpmstate_drive->($task, $vmid); - detach_fleecing_images($task->{disks}, $vmid) if $task->{'use-fleecing'}; + if ($task->{'use-fleecing'}) { + detach_fleecing_images($task->{disks}, $vmid); + PVE::QemuConfig::cleanup_fleecing_images( + $vmid, $self->{storecfg}, sub { $self->log($_[0], $_[1]); }); + } } cleanup_fleecing_images($self, $task->{disks}) if $task->{'use-fleecing'}; diff --git a/test/Makefile b/test/Makefile index 65ed7bc4..f7372e2e 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,6 +1,6 @@ all: test -test: test_snapshot test_cfg_to_cmd test_pci_addr_conflicts test_qemu_img_convert test_migration test_restore_config +test: test_snapshot test_cfg_to_cmd test_pci_addr_conflicts test_qemu_img_convert test_migration test_restore_config test_parse_config test_snapshot: run_snapshot_tests.pl ./run_snapshot_tests.pl @@ -25,6 +25,9 @@ $(MIGRATION_TEST_TARGETS): test_restore_config: run_qemu_restore_config_tests.pl ./run_qemu_restore_config_tests.pl +test_parse_config: run_parse_config_tests.pl + ./run_parse_config_tests.pl + .PHONY: clean clean: - rm -rf MigrationTest/run + rm -rf MigrationTest/run parse-config-output diff --git a/test/parse-config-expected/cloudinit-snapshot.conf b/test/parse-config-expected/cloudinit-snapshot.conf new file mode 100644 index 00000000..bc01f975 --- /dev/null +++ b/test/parse-config-expected/cloudinit-snapshot.conf @@ -0,0 +1,40 @@ +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +parent: cloudinit +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +sockets: 1 +unused0: rbd:vm-120-disk-0 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 + +[special:cloudinit] +ipconfig0: ip=dhcp,ip6=dhcp +name: deb122 + +[cloudinit] +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +ostype: l26 +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +snaptime: 1737549549 +sockets: 1 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 diff --git a/test/parse-config-expected/cloudinit-snapshot.conf.strict.error b/test/parse-config-expected/cloudinit-snapshot.conf.strict.error new file mode 100644 index 00000000..1a77d4a5 --- /dev/null +++ b/test/parse-config-expected/cloudinit-snapshot.conf.strict.error @@ -0,0 +1 @@ +vm 8006 - unable to parse value of 'numa' - type check ('boolean') failed - got 'verify meee~ :)' diff --git a/test/parse-config-expected/duplicate-sections.conf b/test/parse-config-expected/duplicate-sections.conf new file mode 100644 index 00000000..1cb7a88a --- /dev/null +++ b/test/parse-config-expected/duplicate-sections.conf @@ -0,0 +1,43 @@ +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip=dhcp,ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb122 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +parent: foo +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +sockets: 1 +unused0: rbd:vm-120-disk-0 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 + +[PENDING] +vga: qxl + +[special:cloudinit] +name: deb123 + +[foo] +boot: order=scsi0 +cores: 4 +cpu: host +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip=dhcp,ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +snaptime: 1737548747 +sockets: 1 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 diff --git a/test/parse-config-expected/duplicate-sections.conf.strict.error b/test/parse-config-expected/duplicate-sections.conf.strict.error new file mode 100644 index 00000000..f7aabfd7 --- /dev/null +++ b/test/parse-config-expected/duplicate-sections.conf.strict.error @@ -0,0 +1 @@ +vm 8006 - duplicate section: pending diff --git a/test/parse-config-expected/unknown-sections.conf b/test/parse-config-expected/unknown-sections.conf new file mode 100644 index 00000000..6329c33a --- /dev/null +++ b/test/parse-config-expected/unknown-sections.conf @@ -0,0 +1,44 @@ +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +parent: foo +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +sockets: 1 +unused0: rbd:vm-120-disk-0 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 + +[PENDING] +bios: ovmf + +[special:cloudinit] +ipconfig0: ip=dhcp,ip6=dhcp +name: deb122 + +[foo] +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip=dhcp,ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +snaptime: 1737548747 +sockets: 1 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 diff --git a/test/parse-config-expected/unknown-sections.conf.strict.error b/test/parse-config-expected/unknown-sections.conf.strict.error new file mode 100644 index 00000000..7f921a70 --- /dev/null +++ b/test/parse-config-expected/unknown-sections.conf.strict.error @@ -0,0 +1 @@ +vm 8006 - skipping unknown section: 'special:unknown123' diff --git a/test/parse-config-expected/verify-snapshot.conf b/test/parse-config-expected/verify-snapshot.conf new file mode 100644 index 00000000..cd503f86 --- /dev/null +++ b/test/parse-config-expected/verify-snapshot.conf @@ -0,0 +1,36 @@ +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +parent: snap +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +sockets: 1 +unused0: rbd:vm-120-disk-0 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 + +[snap] +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +ostype: l26 +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +snaptime: 1737549549 +sockets: 1 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 diff --git a/test/parse-config-expected/verify-snapshot.conf.strict.error b/test/parse-config-expected/verify-snapshot.conf.strict.error new file mode 100644 index 00000000..1a77d4a5 --- /dev/null +++ b/test/parse-config-expected/verify-snapshot.conf.strict.error @@ -0,0 +1 @@ +vm 8006 - unable to parse value of 'numa' - type check ('boolean') failed - got 'verify meee~ :)' diff --git a/test/parse-config-input/cloudinit-snapshot.conf b/test/parse-config-input/cloudinit-snapshot.conf new file mode 100644 index 00000000..9be05b1c --- /dev/null +++ b/test/parse-config-input/cloudinit-snapshot.conf @@ -0,0 +1,41 @@ +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +parent: cloudinit +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +sockets: 1 +unused0: rbd:vm-120-disk-0 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 + +[special:cloudinit] +ipconfig0: ip=dhcp,ip6=dhcp +name: deb122 + +[cloudinit] +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: verify meee~ :) +ostype: l26 +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +snaptime: 1737549549 +sockets: 1 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 diff --git a/test/parse-config-input/duplicate-sections.conf b/test/parse-config-input/duplicate-sections.conf new file mode 100644 index 00000000..41e90e37 --- /dev/null +++ b/test/parse-config-input/duplicate-sections.conf @@ -0,0 +1,68 @@ +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip=dhcp,ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb122 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +parent: foo +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +sockets: 1 +unused0: rbd:vm-120-disk-0 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 + +[PENDING] +bios: ovmf + +[PENDING] +vga: qxl + +[special:cloudinit] +name: deb12 + +[special:cloudinit] +name: deb123 + +[foo] +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip=dhcp,ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +snaptime: 1737548747 +sockets: 1 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 + +[foo] +boot: order=scsi0 +cores: 4 +cpu: host +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip=dhcp,ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +snaptime: 1737548747 +sockets: 1 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 diff --git a/test/parse-config-input/fleecing-section.conf b/test/parse-config-input/fleecing-section.conf new file mode 100644 index 00000000..ee89dc56 --- /dev/null +++ b/test/parse-config-input/fleecing-section.conf @@ -0,0 +1,20 @@ +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +sockets: 1 +unused0: rbd:vm-120-disk-0 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 + +[special:fleecing] +fleecing-images: zfs:vm-120-fleece-0 diff --git a/test/parse-config-input/locked.conf b/test/parse-config-input/locked.conf new file mode 100644 index 00000000..38b6e36c --- /dev/null +++ b/test/parse-config-input/locked.conf @@ -0,0 +1,16 @@ +# locked +bootdisk: scsi0 +cores: 1 +ide2: none,media=cdrom +lock: backup +memory: 512 +name: apache +net0: virtio=92:38:11:FD:ED:87,bridge=vmbr0,firewall=1 +numa: 0 +ostype: l26 +scsi0: mydir:1422/vm-1422-disk-0.qcow2,size=4G +scsihw: virtio-scsi-pci +smbios1: uuid=ddf91b3f-a597-42be-9a7e-fb6421dcd5cd +sockets: 1 +unused7: mydir:1422/vm-1422-disk-8.qcow2 +vmgenid: 0 diff --git a/test/parse-config-input/plain.conf b/test/parse-config-input/plain.conf new file mode 100644 index 00000000..63449b9e --- /dev/null +++ b/test/parse-config-input/plain.conf @@ -0,0 +1,15 @@ +# plain VM +bootdisk: scsi0 +cores: 1 +ide2: none,media=cdrom +memory: 512 +name: apache +net0: virtio=92:38:11:FD:ED:87,bridge=vmbr0,firewall=1 +numa: 0 +ostype: l26 +scsi0: mydir:142/vm-142-disk-0.qcow2,size=4G +scsihw: virtio-scsi-pci +smbios1: uuid=ddf91b3f-a597-42be-9a7e-fb6421dcd5cd +sockets: 1 +tags: foo bar +vmgenid: 0 diff --git a/test/parse-config-input/regular-vm-efi.conf b/test/parse-config-input/regular-vm-efi.conf new file mode 100644 index 00000000..9d75fff2 --- /dev/null +++ b/test/parse-config-input/regular-vm-efi.conf @@ -0,0 +1,16 @@ +# regular VM with an EFI disk +bios: ovmf +boot: order=scsi0;ide2;net0 +cores: 1 +efidisk0: mydir:139/vm-139-disk-0.qcow2,size=128K +ide2: local:iso/debian-10.6.0-amd64-netinst.iso,media=cdrom +memory: 2048 +name: eficloneclone +net0: virtio=7A:6C:A5:8B:11:93,bridge=vmbr0,firewall=1 +numa: 0 +ostype: l26 +scsi0: rbdkvm:vm-139-disk-1,size=4G +scsihw: virtio-scsi-pci +smbios1: uuid=21a7e7bc-3cd2-4232-a009-a41f4ee992ae +sockets: 1 +vmgenid: 0 diff --git a/test/parse-config-input/sections.conf b/test/parse-config-input/sections.conf new file mode 100644 index 00000000..6329c33a --- /dev/null +++ b/test/parse-config-input/sections.conf @@ -0,0 +1,44 @@ +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +parent: foo +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +sockets: 1 +unused0: rbd:vm-120-disk-0 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 + +[PENDING] +bios: ovmf + +[special:cloudinit] +ipconfig0: ip=dhcp,ip6=dhcp +name: deb122 + +[foo] +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip=dhcp,ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +snaptime: 1737548747 +sockets: 1 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 diff --git a/test/parse-config-input/snapshots.conf b/test/parse-config-input/snapshots.conf new file mode 100644 index 00000000..4f4f8675 --- /dev/null +++ b/test/parse-config-input/snapshots.conf @@ -0,0 +1,189 @@ +boot: order=scsi1;ide2;net0;ide1 +cores: 4 +cpu: x86-64-v2-AES +ide0: dir:111/vm-111-disk-2.qcow2,size=1G +ide1: sani:iso/virtio-win-0.1.266.iso,media=cdrom,size=707456K +ide2: sani:iso/Win2019-evaluation.iso,media=cdrom,size=4985424K +machine: pc-i440fx-9.1 +memory: 4096 +meta: creation-qemu=9.1.2,ctime=1736349024 +name: win-machine-ver +net0: virtio=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1 +net1: e1000=BC:24:11:79:D5:65,bridge=vnet0,firewall=1 +numa: 0 +ostype: win10 +parent: win19_5_2_plus_stuff +scsi0: dir:111/vm-111-disk-1.qcow2,iothread=1,size=1G +scsi1: lvmthinbig:vm-111-disk-0,iothread=1,size=32G +scsihw: virtio-scsi-single +smbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658 +sockets: 1 +unused0: rbd:vm-111-disk-0 +vga: qxl +virtio0: dir:111/vm-111-disk-0.qcow2,iothread=1,size=1G +vmgenid: 713da648-38a6-489e-b0b2-dd9cef419f33 + +[machine_version_5_1] +boot: order=ide0;ide2;net0 +cores: 4 +cpu: x86-64-v2-AES +ide0: lvmthinbig:vm-111-disk-0,size=32G +ide2: sani:iso/Win2016-1616-evaluation.ISO,media=cdrom,size=5198078K +memory: 4096 +meta: creation-qemu=9.1.2,ctime=1736349024 +name: win-machine-ver +net0: e1000=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1 +numa: 0 +ostype: win10 +scsihw: virtio-scsi-single +smbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658 +snaptime: 1736939109 +sockets: 1 +vmgenid: 1f314a76-50a3-4b92-9307-c8c6e313d3ca + +[machine_version_5_1_with_virtio] +boot: order=ide0;ide2;net0;ide1 +cores: 4 +cpu: x86-64-v2-AES +ide0: lvmthinbig:vm-111-disk-0,size=32G +ide1: sani:iso/virtio-win-0.1.266.iso,media=cdrom,size=707456K +ide2: sani:iso/Win2016-1616-evaluation.ISO,media=cdrom,size=5198078K +memory: 4096 +meta: creation-qemu=9.1.2,ctime=1736349024 +name: win-machine-ver +net0: virtio=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1 +numa: 0 +ostype: win10 +parent: machine_version_5_1 +scsi0: dir:111/vm-111-disk-1.qcow2,iothread=1,size=1G +scsihw: virtio-scsi-single +smbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658 +snaptime: 1736940462 +sockets: 1 +virtio0: dir:111/vm-111-disk-0.qcow2,iothread=1,size=1G +vmgenid: 4f602356-cb9c-45ad-a554-d76d95c7c0f8 + +[ovmf_machine_version_5_1] +bios: ovmf +boot: order=ide0;ide2;net0;ide1 +cores: 4 +cpu: x86-64-v2-AES +efidisk0: rbd:vm-111-disk-0,efitype=4m,pre-enrolled-keys=1,size=1M +ide0: lvmthinbig:vm-111-disk-0,size=32G +ide1: sani:iso/virtio-win-0.1.266.iso,media=cdrom,size=707456K +ide2: sani:iso/Win2016-1616-evaluation.ISO,media=cdrom,size=5198078K +machine: pc-q35-5.1 +memory: 4096 +meta: creation-qemu=9.1.2,ctime=1736349024 +name: win-machine-ver +net0: e1000=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1 +numa: 0 +ostype: win10 +parent: machine_version_5_1_with_virtio +scsi0: dir:111/vm-111-disk-1.qcow2,iothread=1,size=1G +scsihw: virtio-scsi-single +smbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658 +snaptime: 1736943308 +sockets: 1 +virtio0: dir:111/vm-111-disk-0.qcow2,iothread=1,size=1G +vmgenid: 4f602356-cb9c-45ad-a554-d76d95c7c0f8 + +[ovmf_machine_version_5_1_virtio] +bios: ovmf +boot: order=ide0;ide2;net0;ide1 +cores: 4 +cpu: x86-64-v2-AES +efidisk0: rbd:vm-111-disk-0,efitype=4m,pre-enrolled-keys=1,size=1M +ide0: lvmthinbig:vm-111-disk-0,size=32G +ide1: sani:iso/virtio-win-0.1.266.iso,media=cdrom,size=707456K +ide2: sani:iso/Win2016-1616-evaluation.ISO,media=cdrom,size=5198078K +machine: pc-q35-5.1 +memory: 4096 +meta: creation-qemu=9.1.2,ctime=1736349024 +name: win-machine-ver +net0: virtio=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1 +numa: 0 +ostype: win10 +parent: ovmf_machine_version_5_1 +scsi0: dir:111/vm-111-disk-1.qcow2,iothread=1,size=1G +scsihw: virtio-scsi-single +smbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658 +snaptime: 1736944525 +sockets: 1 +virtio0: dir:111/vm-111-disk-0.qcow2,iothread=1,size=1G +vmgenid: 00b95468-4f34-4faa-b0af-b214ff5bbcdf + +[static-network] +bios: ovmf +boot: order=ide0;ide2;net0;ide1 +cores: 4 +cpu: x86-64-v2-AES +efidisk0: rbd:vm-111-disk-0,efitype=4m,pre-enrolled-keys=1,size=1M +ide0: lvmthinbig:vm-111-disk-0,size=32G +ide1: sani:iso/virtio-win-0.1.266.iso,media=cdrom,size=707456K +ide2: sani:iso/Win2016-1616-evaluation.ISO,media=cdrom,size=5198078K +machine: pc-q35-5.1 +memory: 4096 +meta: creation-qemu=9.1.2,ctime=1736349024 +name: win-machine-ver +net0: virtio=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1 +numa: 0 +ostype: win10 +parent: ovmf_machine_version_5_1_virtio +scsi0: dir:111/vm-111-disk-1.qcow2,iothread=1,size=1G +scsihw: virtio-scsi-single +smbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658 +snaptime: 1736945713 +sockets: 1 +virtio0: dir:111/vm-111-disk-0.qcow2,iothread=1,size=1G +vmgenid: 5d65fc62-2cb1-4945-9641-631b37c265a5 + +[win19_5_2] +boot: order=scsi1;ide2;net0;ide1 +cores: 4 +cpu: x86-64-v2-AES +ide1: sani:iso/virtio-win-0.1.266.iso,media=cdrom,size=707456K +ide2: sani:iso/Win2019-evaluation.iso,media=cdrom,size=4985424K +machine: pc-i440fx-5.2 +memory: 4096 +meta: creation-qemu=9.1.2,ctime=1736349024 +name: win-machine-ver +net0: virtio=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1 +net1: e1000=BC:24:11:79:D5:65,bridge=vnet0,firewall=1 +numa: 0 +ostype: win10 +parent: machine_version_5_1_with_virtio +scsi0: dir:111/vm-111-disk-1.qcow2,iothread=1,size=1G +scsi1: lvmthinbig:vm-111-disk-0,iothread=1,size=32G +scsihw: virtio-scsi-single +smbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658 +snaptime: 1736950690 +sockets: 1 +virtio0: dir:111/vm-111-disk-0.qcow2,iothread=1,size=1G +vmgenid: f259de06-fa08-4ff7-8ba9-b1233a726ac4 + +[win19_5_2_plus_stuff] +boot: order=scsi1;ide2;net0;ide1 +cores: 4 +cpu: x86-64-v2-AES +ide0: dir:111/vm-111-disk-2.qcow2,size=1G +ide1: sani:iso/virtio-win-0.1.266.iso,media=cdrom,size=707456K +ide2: sani:iso/Win2019-evaluation.iso,media=cdrom,size=4985424K +machine: pc-i440fx-5.2 +memory: 4096 +meta: creation-qemu=9.1.2,ctime=1736349024 +name: win-machine-ver +net0: virtio=BC:24:11:A3:DA:B1,bridge=vnet0,firewall=1 +net1: e1000=BC:24:11:79:D5:65,bridge=vnet0,firewall=1 +numa: 0 +ostype: win10 +parent: win19_5_2 +scsi0: dir:111/vm-111-disk-1.qcow2,iothread=1,size=1G +scsi1: lvmthinbig:vm-111-disk-0,iothread=1,size=32G +scsihw: virtio-scsi-single +smbios1: uuid=2c4a2cda-712b-44ab-8728-51f5e734b658 +snaptime: 1736951300 +sockets: 1 +vga: qxl +virtio0: dir:111/vm-111-disk-0.qcow2,iothread=1,size=1G +vmgenid: 713da648-38a6-489e-b0b2-dd9cef419f33 diff --git a/test/parse-config-input/unknown-sections.conf b/test/parse-config-input/unknown-sections.conf new file mode 100644 index 00000000..0dcd5951 --- /dev/null +++ b/test/parse-config-input/unknown-sections.conf @@ -0,0 +1,57 @@ +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +parent: foo +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +sockets: 1 +unused0: rbd:vm-120-disk-0 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 + +[special:unknown123] +name: foo + +[PENDING] +bios: ovmf + +[special:unknown124] +bios: seabios + +[special:cloudinit] +ipconfig0: ip=dhcp,ip6=dhcp +name: deb122 + +[special:unknown125] +name: bar + +[foo] +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip=dhcp,ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +snaptime: 1737548747 +sockets: 1 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 + +[:3] +name: baz +cat: nya~ diff --git a/test/parse-config-input/verify-snapshot.conf b/test/parse-config-input/verify-snapshot.conf new file mode 100644 index 00000000..5f52272d --- /dev/null +++ b/test/parse-config-input/verify-snapshot.conf @@ -0,0 +1,37 @@ +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: 0 +ostype: l26 +parent: snap +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +sockets: 1 +unused0: rbd:vm-120-disk-0 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 + +[snap] +boot: order=scsi0 +cores: 2 +cpu: x86-64-v2-AES +ide2: lvm:vm-120-cloudinit,media=cdrom +ipconfig0: ip6=dhcp +memory: 4096 +meta: creation-qemu=9.0.2,ctime=1725975013 +name: deb1223 +net0: vmxnet3=BC:24:11:2C:69:EC,bridge=vnet0,firewall=1 +numa: verify meee~ :) +ostype: l26 +scsi0: nfs:120/vm-120-disk-0.qcow2,iothread=1,size=4G +scsihw: virtio-scsi-single +smbios1: uuid=b3247ab1-1fe6-428e-965b-08a1b64a8746 +snaptime: 1737549549 +sockets: 1 +vmgenid: 7079e97c-50e3-4079-afe7-23e67566b946 diff --git a/test/run_parse_config_tests.pl b/test/run_parse_config_tests.pl new file mode 100755 index 00000000..b353ea19 --- /dev/null +++ b/test/run_parse_config_tests.pl @@ -0,0 +1,92 @@ +#!/usr/bin/perl + +# Tests parsing and writing VM configuration files. +# The parsing part is already covered by the config2command test too, but that only focuses on the +# main section, not other section types and does not also test parsing in strict mode. +# +# If no expected file exists, the input is assumed to be equal to the expected output. +# If $file.strict.error (respectively $file.non-strict.error) exists, it is assumed to be the +# expected error when parsing the config in strict (respectively non-strict) mode. + +use strict; +use warnings; + +use lib qw(..); + +use File::Path qw(make_path remove_tree); + +use Test::MockModule; +use Test::More; + +use PVE::QemuServer; +use PVE::Tools; + +my $INPUT_DIR = './parse-config-input'; +my $OUTPUT_DIR = './parse-config-output'; +my $EXPECTED_DIR = './parse-config-expected'; + +# NOTE update when you add/remove tests +plan tests => 2 * 10; + +sub run_tests { + my ($strict) = @_; + + PVE::Tools::dir_glob_foreach('./parse-config-input', '.*\.conf', sub { + my ($file) = @_; + + my $strict_mode = $strict ? 'strict' : 'non-strict'; + + my $expected_err_file = "${EXPECTED_DIR}/${file}.${strict_mode}.error"; + my $expected_err; + $expected_err = PVE::Tools::file_get_contents($expected_err_file) if -f $expected_err_file; + + my $fake_config_fn ="$file/qemu-server/8006.conf"; + my $input_file = "${INPUT_DIR}/${file}"; + my $input = PVE::Tools::file_get_contents($input_file); + my $conf = eval { + PVE::QemuServer::parse_vm_config($fake_config_fn, $input, $strict); + }; + if (my $err = $@) { + if ($expected_err) { + is($err, $expected_err, $file); + } else { + note("got unexpected error '$err'"); + fail($file); + } + return; + } + + if ($expected_err) { + note("expected error for strict mode did not occur: '$expected_err'"); + fail($file); + return; + } + + my $output = eval { PVE::QemuServer::write_vm_config($fake_config_fn, $conf); }; + if (my $err = $@) { + note("got unexpected error '$err'"); + fail($file); + return; + } + + my $output_file = "${OUTPUT_DIR}/${file}"; + PVE::Tools::file_set_contents($output_file, $output); + + my $expected_file = "${EXPECTED_DIR}/${file}"; + $expected_file = $input_file if !-f $expected_file; + + my $cmd = ['diff', '-u', $expected_file, $output_file]; + if (system(@$cmd) == 0) { + pass($file); + } else { + fail($file); + } + }); +} + +make_path(${OUTPUT_DIR}); +run_tests(0); +run_tests(1); +remove_tree(${OUTPUT_DIR}) or die "failed to remove output directory\n"; + +done_testing(); diff --git a/test/run_qemu_restore_config_tests.pl b/test/run_qemu_restore_config_tests.pl index 1e1e8072..1566ddf3 100755 --- a/test/run_qemu_restore_config_tests.pl +++ b/test/run_qemu_restore_config_tests.pl @@ -7,7 +7,6 @@ use lib qw(..); use Test::MockModule; use Test::More; -use Test::MockModule; use File::Basename; @@ -17,18 +16,11 @@ use PVE::Tools qw(dir_glob_foreach file_get_contents); my $INPUT_DIR = './restore-config-input'; my $EXPECTED_DIR = './restore-config-expected'; -my $pve_cluster_module = Test::MockModule->new('PVE::Cluster'); -$pve_cluster_module->mock( - cfs_read_file => sub { - return {}; - }, -); - # NOTE update when you add/remove tests plan tests => 4; -my $cfs_mock = Test::MockModule->new("PVE::Cluster"); -$cfs_mock->mock( +my $pve_cluster_module = Test::MockModule->new("PVE::Cluster"); +$pve_cluster_module->mock( cfs_read_file => sub { my ($file) = @_;