]> git.proxmox.com Git - pve-container.git/blobdiff - src/PVE/LXC.pm
get_primary_ips: take dhcp/manual settings into account
[pve-container.git] / src / PVE / LXC.pm
index fa87039b5882fe4ece68de88837cc447d038ce7f..b8ac53601e6673a988d382c1d6a674d8325f2dbe 100644 (file)
@@ -55,13 +55,13 @@ my $valid_lxc_keys = {
     'lxc.utsname' => 1,
 
     'lxc.id_map' => 1,
-    
+
     'lxc.cgroup.memory.limit_in_bytes' => \&parse_lxc_size,
     'lxc.cgroup.memory.memsw.limit_in_bytes' => \&parse_lxc_size,
     'lxc.cgroup.cpu.cfs_period_us' => '\d+',
     'lxc.cgroup.cpu.cfs_quota_us' => '\d+',
     'lxc.cgroup.cpu.shares' => '\d+',
-    
+
     # mount related
     'lxc.mount' => 1,
     'lxc.mount.entry' => 1,
@@ -71,7 +71,7 @@ my $valid_lxc_keys = {
     'lxc.tty' => '\d+',
     'lxc.pts' => 1,
     'lxc.haltsignal' => 1,
-    'lxc.rebootsignal' => 1,   
+    'lxc.rebootsignal' => 1,
     'lxc.stopsignal' => 1,
     'lxc.init_cmd' => 1,
     'lxc.console' => 1,
@@ -103,7 +103,7 @@ my $valid_lxc_keys = {
     'lxc.hook.start' => 1,
     'lxc.hook.post-stop' => 1,
     'lxc.hook.clone' => 1,
-    
+
     # pve related keys
     'pve.nameserver' => sub {
        my ($name, $value) = @_;
@@ -125,6 +125,14 @@ my $valid_lxc_keys = {
        PVE::Storage::parse_volume_id($value);
        return $value;
     },
+
+     #pve snapshot
+    'pve.lock' => 1,
+    'pve.snaptime' => 1,
+    'pve.snapcomment' => 1,
+    'pve.parent' => 1,
+    'pve.snapstate' => 1,
+    'pve.snapname' => 1,
 };
 
 my $valid_lxc_network_keys = {
@@ -160,65 +168,90 @@ sub write_lxc_config {
 
     return $raw if !$data;
 
-    my $done_hash = { digest => 1};
-
     my $dump_entry = sub {
-       my ($k) = @_;
-       my $value = $data->{$k};
+       my ($k, $value, $done_hash, $snapshot) = @_;
        return if !defined($value);
        return if $done_hash->{$k};
        $done_hash->{$k} = 1;
        if (ref($value)) {
-           die "got unexpected reference for '$k'" 
+           die "got unexpected reference for '$k'"
                if !$lxc_array_configs->{$k};
            foreach my $v (@$value) {
+               $raw .= 'snap.' if $snapshot;
                $raw .= "$k = $v\n";
            }
        } else {
+           $raw .= 'snap.' if $snapshot;
            $raw .= "$k = $value\n";
        }
     };
 
-    # Note: Order is important! Include defaults first, so that we
-    # can overwrite them later.
-    &$dump_entry('lxc.include');
-    
-    foreach my $k (sort keys %$data) {
-       next if $k !~ m/^lxc\./;
-       &$dump_entry($k);
-    }
+    my $config_writer = sub {
+       my ($elem, $snapshot) = @_;
 
-    foreach my $k (sort keys %$data) {
-       next if $k !~ m/^pve\./;
-       &$dump_entry($k);
-    }
+       my $done_hash = { digest => 1};
 
-    my $network_count = 0;
-    foreach my $k (sort keys %$data) {
-       next if $k !~ m/^net\d+$/;
-       $done_hash->{$k} = 1;
-       my $net = $data->{$k};
-       $network_count++;
-       $raw .= "lxc.network.type = $net->{type}\n";
-       foreach my $subkey (sort keys %$net) {
-           next if $subkey eq 'type';
-           if ($valid_lxc_network_keys->{$subkey}) {
-               $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
-           } elsif ($valid_pve_network_keys->{$subkey}) {
-               $raw .= "pve.network.$subkey = $net->{$subkey}\n";
-           } else {
-               die "found invalid network key '$subkey'";
+       if (defined(my $value = $elem->{'pve.snapname'})) {
+            &$dump_entry('pve.snapname', $value, $done_hash, $snapshot);
+       }
+
+       # Note: Order is important! Include defaults first, so that we
+       # can overwrite them later.
+       &$dump_entry('lxc.include', $elem->{'lxc.include'}, $done_hash, $snapshot);
+
+       foreach my $k (sort keys %$elem) {
+           next if $k !~ m/^lxc\./;
+           &$dump_entry($k, $elem->{$k}, $done_hash, $snapshot);
+       }
+       foreach my $k (sort keys %$elem) {
+           next if $k !~ m/^pve\./;
+           &$dump_entry($k, $elem->{$k}, $done_hash, $snapshot);
+       }
+       my $network_count = 0;
+
+       foreach my $k (sort keys %$elem) {
+           next if $k !~ m/^net\d+$/;
+           $done_hash->{$k} = 1;
+
+           my $net = $elem->{$k};
+           $network_count++;
+           $raw .= 'snap.' if $snapshot;
+           $raw .= "lxc.network.type = $net->{type}\n";
+           foreach my $subkey (sort keys %$net) {
+               next if $subkey eq 'type';
+               if ($valid_lxc_network_keys->{$subkey}) {
+                   $raw .= 'snap.' if $snapshot;
+                   $raw .= "lxc.network.$subkey = $net->{$subkey}\n";
+               } elsif ($valid_pve_network_keys->{$subkey}) {
+                   $raw .= 'snap.' if $snapshot;
+                   $raw .= "pve.network.$subkey = $net->{$subkey}\n";
+               } else {
+                   die "found invalid network key '$subkey'";
+               }
            }
        }
-    }
+       if (!$network_count) {
+           $raw .= 'snap.' if $snapshot;
+           $raw .= "lxc.network.type = empty\n";
+       }
+       foreach my $k (sort keys %$elem) {
+           next if $k eq 'snapshots';
+           next if $done_hash->{$k};
+           die "found un-written value \"$k\" in config - implement this!";
+       }
 
-    if (!$network_count) {
-       $raw .= "lxc.network.type = empty\n";
-    }
+    };
 
-    foreach my $k (sort keys %$data) {
-       next if $done_hash->{$k};
-       die "found un-written value in config - implement this!";
+    &$config_writer($data);
+
+    if ($data->{snapshots}) {
+       my @tmp = sort { $data->{snapshots}->{$b}{'pve.snaptime'} <=>
+                             $data->{snapshots}->{$a}{'pve.snaptime'} }
+                       keys %{$data->{snapshots}};
+       foreach my $snapname (@tmp) {
+           $raw .= "\n";
+           &$config_writer($data->{snapshots}->{$snapname}, 1);
+       }
     }
 
     return $raw;
@@ -232,7 +265,7 @@ sub parse_lxc_option {
     die "invalid key '$name'\n" if !defined($parser);
 
     if ($parser eq '1') {
-       return $value;          
+       return $value;
     } elsif (ref($parser)) {
        my $res = &$parser($name, $value);
        return $res if defined($res);
@@ -240,7 +273,7 @@ sub parse_lxc_option {
        # assume regex
        return $value if $value =~ m/^$parser$/;
     }
-    
+
     die "unable to parse value '$value' for option '$name'\n";
 }
 
@@ -258,11 +291,14 @@ sub parse_lxc_config {
 
     my $vmid = $1;
 
+     
     my $network_counter = 0;
     my $network_list = [];
     my $host_ifnames = {};
+    my $snapname;
+    my $network;
 
-     my $find_next_hostif_name = sub {
+    my $find_next_hostif_name = sub {
        for (my $i = 0; $i < 10; $i++) {
            my $name = "veth${vmid}.$i";
            if (!$host_ifnames->{$name}) {
@@ -289,16 +325,47 @@ sub parse_lxc_config {
        }
     };
 
-    my $network;
+    my $finalize_section = sub {
+       &$push_network($network); # flush
+       
+       foreach my $net (@{$network_list}) {
+           next if $net->{type} eq 'empty'; # skip
+           $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
+           $net->{hwaddr} =  PVE::Tools::random_ether_addr() if !$net->{hwaddr};
+           die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
+           
+           if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
+               if ($snapname) {
+                   $data->{snapshots}->{$snapname}->{"net$1"} = $net;
+               } else {
+                   $data->{"net$1"} = $net;
+               }
+           }
+       }
 
-    while ($raw && $raw =~ s/^(.*?)(\n|$)//) {
+       # reset helper vars
+       $network_counter = 0;
+       $network_list = [];
+       $host_ifnames = {};
+       $network = undef;
+    };
+    
+    while ($raw && $raw =~ s/^(.*)?(\n|$)//) {
        my $line = $1;
+       next if $line =~ m/^\s*$/; # skip empty lines
+       next if $line =~ m/^#/; # skip comments
 
-       next if $line =~ m/^\#/;
-       next if $line =~ m/^\s*$/;
+       # snap.pve.snapname starts new sections
+       if ($line =~ m/^(snap\.)?pve\.snapname\s*=\s*(\w*)\s*$/) {
+           my $value = $2;
+           
+           &$finalize_section();
 
-       if ($line =~ m/^lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
-           my ($subkey, $value) = ($1, $2);
+           $snapname = $value;
+           $data->{snapshots}->{$snapname}->{'pve.snapname'} = $snapname;
+           
+       } elsif ($line =~ m/^(snap\.)?lxc\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
+           my ($subkey, $value) = ($2, $3);
            if ($subkey eq 'type') {
                &$push_network($network);
                $network = { type => $value };
@@ -307,51 +374,38 @@ sub parse_lxc_config {
            } else {
                die "unable to parse config line: $line\n";
            }
-           next;
-       }
-       if ($line =~ m/^pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
-           my ($subkey, $value) = ($1, $2);
+       } elsif ($line =~ m/^(snap\.)?pve\.network\.(\S+)\s*=\s*(\S+)\s*$/) {
+           my ($subkey, $value) = ($2, $3);
            if ($valid_pve_network_keys->{$subkey}) {
                $network->{$subkey} = $value;
            } else {
                die "unable to parse config line: $line\n";
            }
-           next;
-       }
-       if ($line =~ m/^(pve.comment)\s*=\s*(\S.*)\s*$/) {
-           my ($name, $value) = ($1, $2);
-           $data->{$name} = $value;
-           next;
-       }
-       if ($line =~ m/^((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
-           my ($name, $value) = ($1, $2);
-
+       } elsif ($line =~ m/^(snap\.)?((?:pve|lxc)\.\S+)\s*=\s*(\S.*)\s*$/) {
+           my ($name, $value) = ($2, $3);
+           
            if ($lxc_array_configs->{$name}) {
                $data->{$name} = [] if !defined($data->{$name});
-               push @{$data->{$name}},  parse_lxc_option($name, $value);
+               if ($snapname) {
+                   push @{$data->{snapshots}->{$snapname}->{$name}},  parse_lxc_option($name, $value);
+               } else {
+                   push @{$data->{$name}},  parse_lxc_option($name, $value);
+               }
            } else {
-               die "multiple definitions for $name\n" if defined($data->{$name});
-               $data->{$name} = parse_lxc_option($name, $value);
+               if ($snapname) {
+                   die "multiple definitions for $name\n" if defined($data->{snapshots}->{$snapname}->{$name});
+                   $data->{snapshots}->{$snapname}->{$name} = parse_lxc_option($name, $value);
+               } else {
+                   die "multiple definitions for $name\n" if defined($data->{$name});
+                   $data->{$name} = parse_lxc_option($name, $value);
+               }
            }
-           
-           next;
+       } else {
+           die "unable to parse config line: $line\n";
        }
-
-       die "unable to parse config line: $line\n";
     }
 
-    &$push_network($network);
-
-    foreach my $net (@{$network_list}) {
-       next if $net->{type} eq 'empty'; # skip
-       $net->{'veth.pair'} = &$find_next_hostif_name() if !$net->{'veth.pair'};
-       $net->{hwaddr} =  PVE::Tools::random_ether_addr() if !$net->{hwaddr};
-       die "unsupported network type '$net->{type}'\n" if $net->{type} ne 'veth';
-
-       if ($net->{'veth.pair'} =~ m/^veth\d+.(\d+)$/) {
-           $data->{"net$1"} = $net;
-       }
-    }
+    &$finalize_section();
 
     return $data;
 }
@@ -446,7 +500,7 @@ my $lockdir = "/run/lock/lxc";
 
 sub lock_filename {
     my ($vmid) = @_;
+
     return "$lockdir/pve-config-{$vmid}.lock";
 }
 
@@ -483,7 +537,7 @@ sub lock_aquire {
            }
 
            $lock_handles->{$$}->{$filename}->{refcount}++;
-           
+
            print STDERR " OK\n";
        }
     };
@@ -492,7 +546,7 @@ sub lock_aquire {
     my $err = $@;
     if ($err) {
        die "can't lock file '$filename' - $err";
-    } 
+    }
 }
 
 sub lock_release {
@@ -626,12 +680,12 @@ sub json_config_properties {
 # container status helpers
 
 sub list_active_containers {
-    
+
     my $filename = "/proc/net/unix";
 
     # similar test is used by lcxcontainers.c: list_active_containers
     my $res = {};
-    
+
     my $fh = IO::File->new ($filename, "r");
     return $res if !$fh;
 
@@ -645,7 +699,7 @@ sub list_active_containers {
     }
 
     close($fh);
-    
+
     return $res;
 }
 
@@ -656,7 +710,7 @@ sub check_running {
     my $active_hash = list_active_containers();
 
     return 1 if defined($active_hash->{$vmid});
-    
+
     return undef;
 }
 
@@ -664,7 +718,7 @@ sub get_container_disk_usage {
     my ($vmid) = @_;
 
     my $cmd = ['lxc-attach', '-n', $vmid, '--', 'df',  '-P', '-B', '1', '/'];
-    
+
     my $res = {
        total => 0,
        used => 0,
@@ -694,20 +748,20 @@ sub vmstatus {
     my $list = $opt_vmid ? { $opt_vmid => { type => 'lxc' }} : config_list();
 
     my $active_hash = list_active_containers();
-    
+
     foreach my $vmid (keys %$list) {
        my $d = $list->{$vmid};
 
        my $running = defined($active_hash->{$vmid});
-       
+
        $d->{status} = $running ? 'running' : 'stopped';
 
        my $cfspath = cfs_config_path($vmid);
        my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
-       
+
        $d->{name} = $conf->{'lxc.utsname'} || "CT$vmid";
        $d->{name} =~ s/[\s]//g;
-           
+
        $d->{cpus} = 0;
 
        my $cfs_period_us = $conf->{'lxc.cgroup.cpu.cfs_period_us'};
@@ -716,11 +770,11 @@ sub vmstatus {
        if ($cfs_period_us && $cfs_quota_us) {
            $d->{cpus} = int($cfs_quota_us/$cfs_period_us);
        }
-       
+
        $d->{disk} = 0;
        $d->{maxdisk} = defined($conf->{'pve.disksize'}) ?
            int($conf->{'pve.disksize'}*1024*1024)*1024 : 1024*1024*1024*1024*1024;
-       
+
        if (my $private = $conf->{'lxc.rootfs'}) {
            if ($private =~ m!^/!) {
                my $res = PVE::Tools::df($private, 2);
@@ -731,10 +785,10 @@ sub vmstatus {
                    my $res = get_container_disk_usage($vmid);
                    $d->{disk} = $res->{used};
                    $d->{maxdisk} = $res->{total};
-               }               
+               }
            }
        }
-       
+
        $d->{mem} = 0;
        $d->{swap} = 0;
        $d->{maxmem} = ($conf->{'lxc.cgroup.memory.limit_in_bytes'}||0) +
@@ -749,7 +803,7 @@ sub vmstatus {
        $d->{diskread} = 0;
        $d->{diskwrite} = 0;
     }
-    
+
     foreach my $vmid (keys %$list) {
        my $d = $list->{$vmid};
        next if $d->{status} ne 'running';
@@ -760,14 +814,15 @@ sub vmstatus {
        $d->{swap} = read_cgroup_value('memory', $vmid, 'memory.memsw.usage_in_bytes') - $d->{mem};
 
        my $blkio_bytes = read_cgroup_value('blkio', $vmid, 'blkio.throttle.io_service_bytes', 1);
-       my @bytes = split /\n/, $blkio_bytes;
+       my @bytes = split(/\n/, $blkio_bytes);
        foreach my $byte (@bytes) {
-           my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/;
-           $d->{diskread} = $2 if $key eq 'Read';
-           $d->{diskwrite} = $2 if $key eq 'Write';
+           if (my ($key, $value) = $byte =~ /(Read|Write)\s+(\d+)/) {
+               $d->{diskread} = $2 if $key eq 'Read';
+               $d->{diskwrite} = $2 if $key eq 'Write';
+           }
        }
     }
-    
+
     return $list;
 }
 
@@ -775,11 +830,11 @@ sub vmstatus {
 sub print_lxc_network {
     my $net = shift;
 
-    die "no network bridge defined\n" if !$net->{bridge};
+    die "no network name defined\n" if !$net->{name};
 
-    my $res = "bridge=$net->{bridge}";
+    my $res = "name=$net->{name}";
 
-    foreach my $k (qw(hwaddr mtu name ip gw ip6 gw6 firewall tag)) {
+    foreach my $k (qw(hwaddr mtu bridge ip gw ip6 gw6 firewall tag)) {
        next if !defined($net->{$k});
        $res .= ",$k=$net->{$k}";
     }
@@ -804,7 +859,7 @@ sub parse_lxc_network {
 
     $res->{type} = 'veth';
     $res->{hwaddr} = PVE::Tools::random_ether_addr() if !$res->{hwaddr};
-   
+
     return $res;
 }
 
@@ -839,19 +894,34 @@ sub find_lxc_console_pids {
        my @args = split(/\0/, $cmdline);
 
        # serach for lxc-console -n <vmid>
-       return if scalar(@args) != 3; 
+       return if scalar(@args) != 3;
        return if $args[1] ne '-n';
        return if $args[2] !~ m/^\d+$/;
        return if $args[0] !~ m|^(/usr/bin/)?lxc-console$|;
-       
+
        my $vmid = $args[2];
-       
+
        push @{$res->{$vmid}}, $pid;
     });
 
     return $res;
 }
 
+sub find_lxc_pid {
+    my ($vmid) = @_;
+
+    my $pid = undef;
+    my $parser = sub {
+        my $line = shift;
+        $pid = $1 if $line =~ m/^PID:\s+(\d+)$/;
+    };
+    PVE::Tools::run_command(['lxc-info', '-n', $vmid], outfunc => $parser);
+
+    die "unable to get PID for CT $vmid (not running?)\n" if !$pid;
+
+    return $pid;
+}
+
 my $ipv4_reverse_mask = [
     '0.0.0.0',
     '128.0.0.0',
@@ -887,8 +957,8 @@ my $ipv4_reverse_mask = [
     '255.255.255.254',
     '255.255.255.255',
 ];
-# Note: we cannot use Net:IP, because that only allows strict 
+
+# Note: we cannot use Net:IP, because that only allows strict
 # CIDR networks
 sub parse_ipv4_cidr {
     my ($cidr, $noerr) = @_;
@@ -896,12 +966,18 @@ sub parse_ipv4_cidr {
     if ($cidr =~ m!^($IPV4RE)(?:/(\d+))$! && ($2 > 7) &&  ($2 < 32)) {
        return { address => $1, netmask => $ipv4_reverse_mask->[$2] };
     }
-    
+
     return undef if $noerr;
-    
+
     die "unable to parse ipv4 address/mask\n";
 }
 
+sub check_lock {
+    my ($conf) = @_;
+
+    die "VM is locked ($conf->{'pve.lock'})\n" if $conf->{'pve.lock'};
+}
+
 sub lxc_conf_to_pve {
     my ($vmid, $lxc_conf) = @_;
 
@@ -937,7 +1013,7 @@ sub lxc_conf_to_pve {
        } elsif ($k eq 'cpulimit') {
            my $cfs_period_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_period_us'};
            my $cfs_quota_us = $lxc_conf->{'lxc.cgroup.cpu.cfs_quota_us'};
-           
+
            if ($cfs_period_us && $cfs_quota_us) {
                $conf->{$k} = $cfs_quota_us/$cfs_period_us;
            } else {
@@ -954,7 +1030,19 @@ sub lxc_conf_to_pve {
            $conf->{$k} = print_lxc_network($net);
        }
     }
-  
+
+    if (my $parent = $lxc_conf->{'pve.parent'}) {
+           $conf->{parent} = $lxc_conf->{'pve.parent'};
+    }
+
+    if (my $parent = $lxc_conf->{'pve.snapcomment'}) {
+       $conf->{description} = $lxc_conf->{'pve.snapcomment'};
+    }
+
+    if (my $parent = $lxc_conf->{'pve.snaptime'}) {
+       $conf->{snaptime} = $lxc_conf->{'pve.snaptime'};
+    }
+
     return $conf;
 }
 
@@ -988,6 +1076,12 @@ sub update_lxc_config {
 
     my @nohotplug;
 
+    my $rootdir;
+    if ($running) {
+       my $pid = find_lxc_pid($vmid);
+       $rootdir = "/proc/$pid/root";
+    }
+
     if (defined($delete)) {
        foreach my $opt (@$delete) {
            if ($opt eq 'hostname' || $opt eq 'memory') {
@@ -1060,7 +1154,7 @@ sub update_lxc_config {
                write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
            }
        } elsif ($opt eq 'cpuunits') {
-           $conf->{'lxc.cgroup.cpu.shares'} = $value;      
+           $conf->{'lxc.cgroup.cpu.shares'} = $value;
            write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
        } elsif ($opt eq 'description') {
            $conf->{'pve.comment'} = PVE::Tools::encode_text($value);
@@ -1071,13 +1165,14 @@ sub update_lxc_config {
        } elsif ($opt =~ m/^net(\d+)$/) {
            my $netid = $1;
            my $net = PVE::LXC::parse_lxc_network($value);
-           my $oldnet = $conf->{$opt} ? $conf->{$opt} : undef;
            $net->{'veth.pair'} = "veth${vmid}.$netid";
-           $conf->{$opt} = $net;
-           next if !$running;
-           update_net($vmid, $net, $oldnet, $netid);
+           if (!$running) {
+               $conf->{$opt} = $net;
+           } else {
+               update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
+           }
        } else {
-           die "implement me"
+           die "implement me: $opt";
        }
        PVE::LXC::write_config($vmid, $conf) if $running;
     }
@@ -1091,15 +1186,27 @@ sub get_primary_ips {
     my ($conf) = @_;
 
     # return data from net0
-    
+
     my $net = $conf->{net0};
     return undef if !$net;
 
     my $ipv4 = $net->{ip};
-    $ipv4 =~ s!/\d+$!! if $ipv4;
-    my $ipv6 = $net->{ip};
-    $ipv6 =~ s!/\d+$!! if $ipv6;
-    
+    if ($ipv4) {
+       if ($ipv4 =~ /^(dhcp|manual)$/) {
+           $ipv4 = undef
+       } else {
+           $ipv4 =~ s!/\d+$!!;
+       }
+    }
+    my $ipv6 = $net->{ip6};
+    if ($ipv6) {
+       if ($ipv6 =~ /^(dhcp|manual)$/) {
+           $ipv6 = undef;
+       } else {
+           $ipv6 =~ s!/\d+$!!;
+       }
+    }
+
     return ($ipv4, $ipv6);
 }
 
@@ -1143,49 +1250,401 @@ my $safe_string_ne = sub {
 };
 
 sub update_net {
-    my ($vmid, $newnet, $oldnet, $netid) = @_;
+    my ($vmid, $conf, $opt, $newnet, $netid, $rootdir) = @_;
 
     my $veth = $newnet->{'veth.pair'};
-    my $vethpeer = $veth."p";
+    my $vethpeer = $veth . "p";
     my $eth = $newnet->{name};
 
-    if ($oldnet) {
-       if (&$safe_string_ne($oldnet->{hwaddr}, $newnet->{hwaddr}) ||
-           &$safe_string_ne($oldnet->{name}, $newnet->{name})) { 
+    if ($conf->{$opt}) {
+       if (&$safe_string_ne($conf->{$opt}->{hwaddr}, $newnet->{hwaddr}) ||
+           &$safe_string_ne($conf->{$opt}->{name}, $newnet->{name})) {
 
             PVE::Network::veth_delete($veth);
-           hotplug_net($vmid, $newnet);
+           delete $conf->{$opt};
+           PVE::LXC::write_config($vmid, $conf);
+
+           hotplug_net($vmid, $conf, $opt, $newnet);
 
-       } elsif (&$safe_string_ne($oldnet->{bridge}, $newnet->{bridge}) ||
-                &$safe_num_ne($oldnet->{tag}, $newnet->{tag}) ||
-                &$safe_num_ne($oldnet->{firewall}, $newnet->{firewall})) {
+       } elsif (&$safe_string_ne($conf->{$opt}->{bridge}, $newnet->{bridge}) ||
+                &$safe_num_ne($conf->{$opt}->{tag}, $newnet->{tag}) ||
+                &$safe_num_ne($conf->{$opt}->{firewall}, $newnet->{firewall})) {
+
+               if ($conf->{$opt}->{bridge}){
+                   PVE::Network::tap_unplug($veth);
+                   delete $conf->{$opt}->{bridge};
+                   delete $conf->{$opt}->{tag};
+                   delete $conf->{$opt}->{firewall};
+                   PVE::LXC::write_config($vmid, $conf);
+               }
 
-                PVE::Network::tap_unplug($veth);
                 PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
+               $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge};
+               $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag};
+               $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall};
+               PVE::LXC::write_config($vmid, $conf);
        }
     } else {
-       hotplug_net($vmid, $newnet);
+       hotplug_net($vmid, $conf, $opt, $newnet);
     }
 
+    update_ipconfig($vmid, $conf, $opt, $eth, $newnet, $rootdir);
 }
 
 sub hotplug_net {
-    my ($vmid, $newnet) = @_;
+    my ($vmid, $conf, $opt, $newnet) = @_;
 
     my $veth = $newnet->{'veth.pair'};
-    my $vethpeer = $veth."p";
+    my $vethpeer = $veth . "p";
     my $eth = $newnet->{name};
 
     PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr});
     PVE::Network::tap_plug($veth, $newnet->{bridge}, $newnet->{tag}, $newnet->{firewall});
 
-    #attach peer in container
+    # attach peer in container
     my $cmd = ['lxc-device', '-n', $vmid, 'add', $vethpeer, "$eth" ];
     PVE::Tools::run_command($cmd);
 
-    #link up peer in container
+    # link up peer in container
     $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', 'link', 'set', $eth ,'up'  ];
     PVE::Tools::run_command($cmd);
+
+    $conf->{$opt}->{type} = 'veth';
+    $conf->{$opt}->{bridge} = $newnet->{bridge} if $newnet->{bridge};
+    $conf->{$opt}->{tag} = $newnet->{tag} if $newnet->{tag};
+    $conf->{$opt}->{firewall} = $newnet->{firewall} if $newnet->{firewall};
+    $conf->{$opt}->{hwaddr} = $newnet->{hwaddr} if $newnet->{hwaddr};
+    $conf->{$opt}->{name} = $newnet->{name} if $newnet->{name};
+    $conf->{$opt}->{'veth.pair'} = $newnet->{'veth.pair'} if $newnet->{'veth.pair'};
+
+    delete $conf->{$opt}->{ip} if $conf->{$opt}->{ip};
+    delete $conf->{$opt}->{ip6} if $conf->{$opt}->{ip6};
+    delete $conf->{$opt}->{gw} if $conf->{$opt}->{gw};
+    delete $conf->{$opt}->{gw6} if $conf->{$opt}->{gw6};
+
+    PVE::LXC::write_config($vmid, $conf);
+}
+
+sub update_ipconfig {
+    my ($vmid, $conf, $opt, $eth, $newnet, $rootdir) = @_;
+
+    my $lxc_setup = PVE::LXCSetup->new($conf, $rootdir);
+
+    my $optdata = $conf->{$opt};
+    my $deleted = [];
+    my $added = [];
+    my $netcmd = sub {
+       my $cmd = ['lxc-attach', '-n', $vmid, '-s', 'NETWORK', '--', '/sbin/ip', @_];
+       PVE::Tools::run_command($cmd);
+    };
+
+    my $change_ip_config = sub {
+       my ($ipversion) = @_;
+
+       my $family_opt = "-$ipversion";
+       my $suffix = $ipversion == 4 ? '' : $ipversion;
+       my $gw= "gw$suffix";
+       my $ip= "ip$suffix";
+
+       my $change_ip = &$safe_string_ne($optdata->{$ip}, $newnet->{$ip});
+       my $change_gw = &$safe_string_ne($optdata->{$gw}, $newnet->{$gw});
+
+       return if !$change_ip && !$change_gw;
+
+       # step 1: add new IP, if this fails we cancel
+       if ($change_ip && $newnet->{$ip}) {
+           eval { &$netcmd($family_opt, 'addr', 'add', $newnet->{$ip}, 'dev', $eth); };
+           if (my $err = $@) {
+               warn $err;
+               return;
+           }
+       }
+
+       # step 2: replace gateway
+       #   If this fails we delete the added IP and cancel.
+       #   If it succeeds we save the config and delete the old IP, ignoring
+       #   errors. The config is then saved.
+       # Note: 'ip route replace' can add
+       if ($change_gw) {
+           if ($newnet->{$gw}) {
+               eval { &$netcmd($family_opt, 'route', 'replace', 'default', 'via', $newnet->{$gw}); };
+               if (my $err = $@) {
+                   warn $err;
+                   # the route was not replaced, the old IP is still available
+                   # rollback (delete new IP) and cancel
+                   if ($change_ip) {
+                       eval { &$netcmd($family_opt, 'addr', 'del', $newnet->{$ip}, 'dev', $eth); };
+                       warn $@ if $@; # no need to die here
+                   }
+                   return;
+               }
+           } else {
+               eval { &$netcmd($family_opt, 'route', 'del', 'default'); };
+               # if the route was not deleted, the guest might have deleted it manually
+               # warn and continue
+               warn $@ if $@;
+           }
+       }
+
+       # from this point on we safe the configuration
+       # step 3: delete old IP ignoring errors
+       if ($change_ip && $optdata->{$ip}) {
+           eval { &$netcmd($family_opt, 'addr', 'del', $optdata->{$ip}, 'dev', $eth); };
+           warn $@ if $@; # no need to die here
+       }
+
+       foreach my $property ($ip, $gw) {
+           if ($newnet->{$property}) {
+               $optdata->{$property} = $newnet->{$property};
+           } else {
+               delete $optdata->{$property};
+           }
+       }
+       PVE::LXC::write_config($vmid, $conf);
+       $lxc_setup->setup_network($conf);
+    };
+
+    &$change_ip_config(4);
+    &$change_ip_config(6);
+
+}
+
+# Internal snapshots
+
+# NOTE: Snapshot create/delete involves several non-atomic
+# action, and can take a long time.
+# So we try to avoid locking the file and use 'lock' variable
+# inside the config file instead.
+
+my $snapshot_copy_config = sub {
+    my ($source, $dest) = @_;
+
+    foreach my $k (keys %$source) {
+       next if $k eq 'snapshots';
+       next if $k eq 'pve.snapstate';
+       next if $k eq 'pve.snaptime';
+       next if $k eq 'pve.lock';
+       next if $k eq 'digest';
+       next if $k eq 'pve.comment';
+
+       $dest->{$k} = $source->{$k};
+    }
+};
+
+my $snapshot_prepare = sub {
+    my ($vmid, $snapname, $comment) = @_;
+
+    my $snap;
+
+    my $updatefn =  sub {
+
+       my $conf = load_config($vmid);
+
+       check_lock($conf);
+
+       $conf->{'pve.lock'} = 'snapshot';
+
+       die "snapshot name '$snapname' already used\n"
+           if defined($conf->{snapshots}->{$snapname});
+
+       my $storecfg = PVE::Storage::config();
+       die "snapshot feature is not available\n" if !has_feature('snapshot', $conf, $storecfg);
+
+       $snap = $conf->{snapshots}->{$snapname} = {};
+
+       &$snapshot_copy_config($conf, $snap);
+
+       $snap->{'pve.snapstate'} = "prepare";
+       $snap->{'pve.snaptime'} = time();
+       $snap->{'pve.snapname'} = $snapname;
+       $snap->{'pve.snapcomment'} = $comment if $comment;
+       $conf->{snapshots}->{$snapname} = $snap;
+
+       PVE::LXC::write_config($vmid, $conf);
+    };
+
+    lock_container($vmid, 10, $updatefn);
+
+    return $snap;
+};
+
+my $snapshot_commit = sub {
+    my ($vmid, $snapname) = @_;
+
+    my $updatefn = sub {
+
+       my $conf = load_config($vmid);
+
+       die "missing snapshot lock\n"
+           if !($conf->{'pve.lock'} && $conf->{'pve.lock'} eq 'snapshot');
+
+       die "snapshot '$snapname' does not exist\n" 
+           if !defined($conf->{snapshots}->{$snapname});
+
+       die "wrong snapshot state\n"
+           if !($conf->{snapshots}->{$snapname}->{'pve.snapstate'} && $conf->{snapshots}->{$snapname}->{'pve.snapstate'} eq "prepare");
+
+       delete $conf->{snapshots}->{$snapname}->{'pve.snapstate'};
+       delete $conf->{'pve.lock'};
+       $conf->{'pve.parent'} = $snapname;
+
+       PVE::LXC::write_config($vmid, $conf);
+
+    };
+
+    lock_container($vmid, 10 ,$updatefn);
+};
+
+sub has_feature {
+    my ($feature, $conf, $storecfg, $snapname) = @_;
+    #Fixme add other drives if necessary.
+    my $err;
+    my $volid = $conf->{'pve.volid'};
+    $err = 1 if !PVE::Storage::volume_has_feature($storecfg, $feature, $volid, $snapname);
+
+    return $err ? 0 : 1;
+}
+
+sub snapshot_create {
+    my ($vmid, $snapname, $comment) = @_;
+
+    my $snap = &$snapshot_prepare($vmid, $snapname, $comment);
+
+    my $config = load_config($vmid);
+
+    my $cmd = "/usr/bin/lxc-freeze -n $vmid";
+    my $running = check_running($vmid);
+    eval {
+       if ($running) {
+           PVE::Tools::run_command($cmd);
+       };
+
+       my $storecfg = PVE::Storage::config();
+       my $volid = $config->{'pve.volid'};
+
+       $cmd = "/usr/bin/lxc-unfreeze -n $vmid";
+       if ($running) {
+           PVE::Tools::run_command($cmd);
+       };
+
+       PVE::Storage::volume_snapshot($storecfg, $volid, $snapname);
+       &$snapshot_commit($vmid, $snapname);
+    };
+    if(my $err = $@) {
+       snapshot_delete($vmid, $snapname, 1);
+       die "$err\n";
+    }
+}
+
+sub snapshot_delete {
+    my ($vmid, $snapname, $force) = @_;
+
+    my $snap;
+
+    my $conf;
+
+    my $updatefn =  sub {
+
+       $conf = load_config($vmid);
+
+       $snap = $conf->{snapshots}->{$snapname};
+
+       check_lock($conf);
+
+       die "snapshot '$snapname' does not exist\n" if !defined($snap);
+
+       $snap->{'pve.snapstate'} = 'delete';
+
+       PVE::LXC::write_config($vmid, $conf);
+    };
+
+    lock_container($vmid, 10, $updatefn);
+
+    my $storecfg = PVE::Storage::config();
+
+    my $del_snap =  sub {
+
+       check_lock($conf);
+
+       if ($conf->{'pve.parent'} eq $snapname) {
+           if ($conf->{snapshots}->{$snapname}->{'pve.snapname'}) {
+               $conf->{'pve.parent'} = $conf->{snapshots}->{$snapname}->{'pve.parent'};
+           } else {
+               delete $conf->{'pve.parent'};
+           }
+       }
+
+       delete $conf->{snapshots}->{$snapname};
+
+       PVE::LXC::write_config($vmid, $conf);
+    };
+
+    my $volid = $conf->{snapshots}->{$snapname}->{'pve.volid'};
+
+    eval {
+       PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snapname);
+    };
+    my $err = $@;
+
+    if(!$err || ($err && $force)) {
+       lock_container($vmid, 10, $del_snap);
+       if ($err) {
+           die "Can't delete snapshot: $vmid $snapname $err\n";
+       }
+    }
+}
+
+sub snapshot_rollback {
+    my ($vmid, $snapname) = @_;
+
+    my $storecfg = PVE::Storage::config();
+
+    my $conf = load_config($vmid);
+
+    my $snap = $conf->{snapshots}->{$snapname};
+
+    die "snapshot '$snapname' does not exist\n" if !defined($snap);
+
+    PVE::Storage::volume_rollback_is_possible($storecfg, $snap->{'pve.volid'},
+                                             $snapname);
+
+    my $updatefn = sub {
+
+       die "unable to rollback to incomplete snapshot (snapstate = $snap->{snapstate})\n" if $snap->{snapstate};
+
+       check_lock($conf);
+
+       system("lxc-stop -n $vmid --kill") if check_running($vmid);
+
+       die "unable to rollback vm $vmid: vm is running\n"
+           if check_running($vmid);
+
+       $conf->{'pve.lock'} = 'rollback';
+
+       my $forcemachine;
+
+       # copy snapshot config to current config
+
+       my $tmp_conf = $conf;
+       &$snapshot_copy_config($tmp_conf->{snapshots}->{$snapname}, $conf);
+       $conf->{snapshots} = $tmp_conf->{snapshots};
+       delete $conf->{'pve.snaptime'};
+       delete $conf->{'pve.snapname'};
+       $conf->{'pve.parent'} = $snapname;
+
+       PVE::LXC::write_config($vmid, $conf);
+    };
+
+    my $unlockfn = sub {
+       delete $conf->{'pve.lock'};
+       PVE::LXC::write_config($vmid, $conf);
+    };
+
+    lock_container($vmid, 10, $updatefn);
+
+    PVE::Storage::volume_snapshot_rollback($storecfg, $conf->{'pve.volid'}, $snapname);
+
+    lock_container($vmid, 5, $unlockfn);
 }
 
 1;