]>
git.proxmox.com Git - pve-manager.git/blob - PVE/OpenVZMigrate.pm
b0c66e642aaeb0e085e225ff5492b1d9355ed409
1 package PVE
::OpenVZMigrate
;
5 use PVE
::AbstractMigrate
;
14 use base
qw(PVE::AbstractMigrate);
16 # fixme: lock VM on target node
19 my ($self, $vmid, $code, @param) = @_;
21 return PVE
::OpenVZ
::lock_container
($vmid, undef, $code, @param);
25 my ($self, $vmid) = @_;
27 my $online = $self->{opts
}->{online
};
29 $self->{storecfg
} = PVE
::Storage
::config
();
30 $self->{vzconf
} = PVE
::OpenVZ
::read_global_vz_config
(),
33 my $conf = $self->{vmconf
} = PVE
::OpenVZ
::load_config
($vmid);
35 my $path = PVE
::OpenVZ
::get_privatedir
($conf, $vmid);
36 my ($vtype, $volid) = PVE
::Storage
::path_to_volume_id
($self->{storecfg
}, $path);
37 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1) if $volid;
39 die "can't determine assigned storage\n" if !$storage;
41 # check if storage is available on both nodes
42 my $scfg = PVE
::Storage
::storage_check_node
($self->{storecfg
}, $storage);
43 PVE
::Storage
::storage_check_node
($self->{storecfg
}, $storage, $self->{node
});
45 # we simply use the backup dir to store temporary dump files
46 # Note: this is on shared storage if the storage is 'shared'
47 $self->{dumpdir
} = PVE
::Storage
::get_backup_dir
($self->{storecfg
}, $storage);
49 PVE
::Storage
::activate_volumes
($self->{storecfg
}, [ $volid ]);
51 $self->{storage
} = $storage;
52 $self->{privatedir
} = $path;
54 $self->{rootdir
} = PVE
::OpenVZ
::get_rootdir
($conf, $vmid);
56 $self->{shared
} = $scfg->{shared
};
59 if (PVE
::OpenVZ
::check_running
($vmid)) {
60 die "cant migrate running container without --online\n" if !$online;
64 # fixme: test if VM uses local resources
67 my $cmd = [ @{$self->{rem_ssh
}}, '/bin/true' ];
68 eval { $self->cmd_quiet($cmd); };
69 die "Can't connect to destination address using public key\n" if $@;
73 # test if OpenVZ is running
74 $cmd = [ @{$self->{rem_ssh
}}, '/etc/init.d/vz status' ];
75 eval { $self->cmd_quiet($cmd); };
76 die "OpenVZ is not running on the target machine\n" if $@;
78 # test if CPT modules are loaded for online migration
79 die "vzcpt module is not loaded\n" if ! -f
'/proc/cpt';
81 $cmd = [ @{$self->{rem_ssh
}}, 'test -f /proc/rst' ];
82 eval { $self->cmd_quiet($cmd); };
83 die "vzrst module is not loaded on the target machine\n" if $@;
86 # fixme: do we want to test if IPs exists on target node?
92 my ($self, $vmid) = @_;
94 $self->log('info', "starting migration of CT $self->{vmid} to node '$self->{node}' ($self->{nodeip})");
96 my $conf = $self->{vmconf
};
98 if ($self->{running
}) {
99 $self->log('info', "container is running - using online migration");
102 my $cmd = [ @{$self->{rem_ssh
}}, 'mkdir', '-p', $self->{rootdir
} ];
103 $self->cmd_quiet($cmd, errmsg
=> "Failed to make container root directory");
105 my $privatedir = $self->{privatedir
};
107 if (!$self->{shared
}) {
109 $cmd = [ @{$self->{rem_ssh
}}, 'mkdir', '-p', $privatedir ];
110 $self->cmd_quiet($cmd, errmsg
=> "Failed to make container private directory");
112 $self->{undo_private
} = $privatedir;
114 $self->log('info', "starting rsync phase 1");
115 my $basedir = dirname
($privatedir);
116 $cmd = [ @{$self->{rsync_cmd
}}, '--sparse', $privatedir, "root\@$self->{nodeip}:$basedir" ];
117 $self->cmd($cmd, errmsg
=> "Failed to sync container private area");
119 $self->log('info', "container data is on shared storage '$self->{storage}'");
122 my $conffile = PVE
::OpenVZ
::config_file
($vmid);
123 my $newconffile = PVE
::OpenVZ
::config_file
($vmid, $self->{node
});
125 my $srccfgdir = dirname
($conffile);
126 my $newcfgdir = dirname
($newconffile);
127 foreach my $s (PVE
::OpenVZ
::SCRIPT_EXT
) {
128 my $scriptfn = "${vmid}.$s";
129 my $srcfn = "$srccfgdir/$scriptfn";
131 my $dstfn = "$newcfgdir/$scriptfn";
132 copy
($srcfn, $dstfn) || die "copy '$srcfn' to '$dstfn' failed - $!\n";
135 if ($self->{running
}) {
136 # fixme: save state and quota
137 $self->log('info', "start live migration - suspending container");
138 $cmd = [ 'vzctl', '--skiplock', 'chkpnt', $vmid, '--suspend' ];
139 $self->cmd_quiet($cmd, errmsg
=> "Failed to suspend container");
141 $self->{undo_suspend
} = 1;
143 $self->log('info', "dump container state");
144 $self->{dumpfile
} = "$self->{dumpdir}/dump.$vmid";
145 $cmd = [ 'vzctl', '--skiplock', 'chkpnt', $vmid, '--dump', '--dumpfile', $self->{dumpfile
} ];
146 $self->cmd_quiet($cmd, errmsg
=> "Failed to dump container state");
148 if (!$self->{shared
}) {
149 $self->log('info', "copy dump file to target node");
150 $self->{undo_copy_dump
} = 1;
151 $cmd = [ @{$self->{scp_cmd
}}, $self->{dumpfile
}, "root\@$self->{nodeip}:$self->{dumpfile}"];
152 $self->cmd_quiet($cmd, errmsg
=> "Failed to copy dump file");
154 $self->log('info', "starting rsync (2nd pass)");
155 my $basedir = dirname
($privatedir);
156 $cmd = [ @{$self->{rsync_cmd
}}, $privatedir, "root\@$self->{nodeip}:$basedir" ];
157 $self->cmd($cmd, errmsg
=> "Failed to sync container private area");
160 if (PVE
::OpenVZ
::check_mounted
($conf, $vmid)) {
161 $self->log('info', "unmounting container");
162 $cmd = [ 'vzctl', '--skiplock', 'umount', $vmid ];
163 $self->cmd_quiet($cmd, errmsg
=> "Failed to umount container");
167 my $disk_quota = $conf->{disk_quota
}->{value
};
168 if (!defined($disk_quota) || ($disk_quota != 0)) {
169 $disk_quota = $self->{disk_quota
} = 1;
171 $self->log('info', "dump 2nd level quota");
172 $self->{quotadumpfile
} = "$self->{dumpdir}/quotadump.$vmid";
173 $cmd = "vzdqdump $vmid -U -G -T > " . PVE
::Tools
::shellquote
($self->{quotadumpfile
});
174 $self->cmd_quiet($cmd, errmsg
=> "Failed to dump 2nd level quota");
176 if (!$self->{shared
}) {
177 $self->log('info', "copy 2nd level quota to target node");
178 $self->{undo_copy_quota_dump
} = 1;
179 $cmd = [@{$self->{scp_cmd
}}, $self->{quotadumpfile
},
180 "root\@$self->{nodeip}:$self->{quotadumpfile}"];
181 $self->cmd_quiet($cmd, errmsg
=> "Failed to copy 2nd level quota dump");
185 # everythin copied - make sure container is stoped
186 # fixme_ do we need to start on the other node first?
187 if ($self->{running
}) {
188 delete $self->{undo_suspend
};
189 $cmd = [ 'vzctl', '--skiplock', 'chkpnt', $vmid, '--kill' ];
190 $self->cmd_quiet($cmd, errmsg
=> "Failed to kill container");
191 $cmd = [ 'vzctl', '--skiplock', 'umount', $vmid ];
192 sleep(1); # hack: wait - else there are open files
193 $self->cmd_quiet($cmd, errmsg
=> "Failed to umount container");
197 die "Failed to move config to node '$self->{node}' - rename failed: $!\n"
198 if !rename($conffile, $newconffile);
202 my ($self, $vmid, $err) = @_;
204 $self->log('info', "aborting phase 1 - cleanup resources");
206 my $conf = $self->{vmconf
};
208 if ($self->{undo_suspend
}) {
209 my $cmd = [ 'vzctl', '--skiplock', 'chkpnt', $vmid, '--resume' ];
210 $self->cmd_logerr($cmd, errmsg
=> "Failed to resume container");
213 if ($self->{undo_private
}) {
214 $self->log('info', "removing copied files on target node");
215 my $cmd = [ @{$self->{rem_ssh
}}, 'rm', '-rf', $self->{undo_private
} ];
216 $self->cmd_logerr($cmd, errmsg
=> "Failed to remove copied files");
219 # fixme: that seem to be very dangerous and not needed
220 #my $cmd = [ @{$self->{rem_ssh}}, 'rm', '-rf', $self->{rootdir} ];
221 #eval { $self->cmd_quiet($cmd); };
223 my $newconffile = PVE
::OpenVZ
::config_file
($vmid, $self->{node
});
224 my $newcfgdir = dirname
($newconffile);
225 foreach my $s (PVE
::OpenVZ
::SCRIPT_EXT
) {
226 my $scriptfn = "${vmid}.$s";
227 my $dstfn = "$newcfgdir/$scriptfn";
229 $self->log('err', "unlink '$dstfn' failed - $!") if !unlink $dstfn;
235 my ($self, $vmid) = @_;
237 my $conf = $self->{vmconf
};
239 $self->log('info', "initialize container on remote node '$self->{node}'");
241 my $cmd = [ @{$self->{rem_ssh
}}, 'vzctl', '--quiet', 'set', $vmid,
242 '--applyconfig_map', 'name', '--save' ];
244 $self->cmd_quiet($cmd, errmsg
=> "Failed to apply config on target node");
246 if ($self->{disk_quota
}) {
247 $self->log('info', "initializing remote quota");
248 $cmd = [ @{$self->{rem_ssh
}}, 'vzctl', 'quotainit', $vmid];
249 $self->cmd_quiet($cmd, errmsg
=> "Failed to initialize quota");
250 $self->log('info', "turn on remote quota");
251 $cmd = [ @{$self->{rem_ssh
}}, 'vzctl', 'quotaon', $vmid];
252 $self->cmd_quiet($cmd, errmsg
=> "Failed to turn on quota");
253 $self->log('info', "load 2nd level quota");
254 $cmd = [ @{$self->{rem_ssh
}}, "(vzdqload $vmid -U -G -T < " .
255 PVE
::Tools
::shellquote
($self->{quotadumpfile
}) .
256 " && vzquota reload2 $vmid)"];
257 $self->cmd_quiet($cmd, errmsg
=> "Failed to load 2nd level quota");
258 if (!$self->{running
}) {
259 $self->log('info', "turn off remote quota");
260 $cmd = [ @{$self->{rem_ssh
}}, 'vzquota', 'off', $vmid];
261 $self->cmd_quiet($cmd, errmsg
=> "Failed to turn off quota");
267 my ($self, $vmid) = @_;
269 my $conf = $self->{vmconf
};
271 $self->{target_initialized
} = 1;
272 init_target_vm
($self, $vmid);
274 $self->log('info', "starting container on remote node '$self->{node}'");
276 $self->log('info', "restore container state");
277 $self->{dumpfile
} = "$self->{dumpdir}/dump.$vmid";
278 my $cmd = [ @{$self->{rem_ssh
}}, 'vzctl', 'restore', $vmid, '--undump',
279 '--dumpfile', $self->{dumpfile
}, '--skip_arpdetect' ];
280 $self->cmd_quiet($cmd, errmsg
=> "Failed to restore container");
282 $cmd = [ @{$self->{rem_ssh
}}, 'vzctl', 'restore', $vmid, '--resume' ];
283 $self->cmd_quiet($cmd, errmsg
=> "Failed to resume container");
287 my ($self, $vmid) = @_;
289 if (!$self->{target_initialized
}) {
290 init_target_vm
($self, $vmid);
296 my ($self, $vmid, $err) = @_;
298 my $conf = $self->{vmconf
};
300 if (!$self->{shared
}) {
301 # destroy local container data
302 $self->log('info', "removing container files on local node");
303 my $cmd = [ 'rm', '-rf', $self->{privatedir
} ];
304 $self->cmd_logerr($cmd);
307 if ($self->{disk_quota
}) {
308 my $cmd = [ 'vzquota', 'drop', $vmid];
309 $self->cmd_logerr($cmd, errmsg
=> "Failed to drop local quota");
314 my ($self, $vmid) = @_;
316 $self->log('info', "start final cleanup");
318 my $conf = $self->{vmconf
};
320 unlink($self->{quotadumpfile
}) if $self->{quotadumpfile
};
322 unlink($self->{dumpfile
}) if $self->{dumpfile
};
324 if ($self->{undo_copy_dump
} && $self->{dumpfile
}) {
325 my $cmd = [ @{$self->{rem_ssh
}}, 'rm', '-f', $self->{dumpfile
} ];
326 $self->cmd_logerr($cmd, errmsg
=> "Failed to remove dump file");
329 if ($self->{undo_copy_quota_dump
} && $self->{quotadumpfile
}) {
330 my $cmd = [ @{$self->{rem_ssh
}}, 'rm', '-f', $self->{quotadumpfile
} ];
331 $self->cmd_logerr($cmd, errmsg
=> "Failed to remove 2nd level quota dump file");