]>
Commit | Line | Data |
---|---|---|
6f42807e DM |
1 | package PVE::LXC::Migrate; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | use PVE::AbstractMigrate; | |
6 | use File::Basename; | |
7 | use File::Copy; # fixme: remove | |
8 | use PVE::Tools; | |
9 | use PVE::INotify; | |
10 | use PVE::Cluster; | |
11 | use PVE::Storage; | |
12 | use PVE::LXC; | |
13 | ||
14 | use base qw(PVE::AbstractMigrate); | |
15 | ||
16 | sub lock_vm { | |
17 | my ($self, $vmid, $code, @param) = @_; | |
18 | ||
67afe46e | 19 | return PVE::LXC::Config->lock_config($vmid, $code, @param); |
6f42807e DM |
20 | } |
21 | ||
22 | sub prepare { | |
23 | my ($self, $vmid) = @_; | |
24 | ||
25 | my $online = $self->{opts}->{online}; | |
26 | ||
27 | $self->{storecfg} = PVE::Storage::config(); | |
28 | ||
1cd1fa12 | 29 | # test if CT exists |
67afe46e | 30 | my $conf = $self->{vmconf} = PVE::LXC::Config->load_config($vmid); |
6f42807e | 31 | |
67afe46e | 32 | PVE::LXC::Config->check_lock($conf); |
6f42807e DM |
33 | |
34 | my $running = 0; | |
35 | if (PVE::LXC::check_running($vmid)) { | |
36 | die "lxc live migration is currently not implemented\n"; | |
37 | ||
fc181735 | 38 | die "can't migrate running container without --online\n" if !$online; |
6f42807e DM |
39 | $running = 1; |
40 | } | |
41 | ||
9746c095 | 42 | my $force = $self->{opts}->{force} // 0; |
20ab40f3 | 43 | my $need_activate = []; |
9746c095 | 44 | |
d250604f | 45 | PVE::LXC::Config->foreach_mountpoint($conf, sub { |
6f42807e DM |
46 | my ($ms, $mountpoint) = @_; |
47 | ||
48 | my $volid = $mountpoint->{volume}; | |
9746c095 FG |
49 | |
50 | # skip dev/bind mps when forced | |
51 | if ($mountpoint->{type} ne 'volume' && $force) { | |
52 | return; | |
53 | } | |
6f42807e DM |
54 | my ($storage, $volname) = PVE::Storage::parse_volume_id($volid, 1) if $volid; |
55 | die "can't determine assigned storage for mountpoint '$ms'\n" if !$storage; | |
56 | ||
57 | # check if storage is available on both nodes | |
58 | my $scfg = PVE::Storage::storage_check_node($self->{storecfg}, $storage); | |
59 | PVE::Storage::storage_check_node($self->{storecfg}, $storage, $self->{node}); | |
60 | ||
20ab40f3 FG |
61 | |
62 | if ($scfg->{shared}) { | |
63 | # PVE::Storage::activate_storage checks this for non-shared storages | |
64 | my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); | |
65 | warn "Used shared storage '$storage' is not online on source node!\n" | |
66 | if !$plugin->check_connection($storage, $scfg); | |
67 | } else { | |
68 | # only activate if not shared | |
69 | push @$need_activate, $volid; | |
70 | ||
71 | die "unable to migrate local mountpoint '$volid' while CT is running" | |
72 | if $running; | |
73 | } | |
6f42807e DM |
74 | |
75 | }); | |
76 | ||
20ab40f3 | 77 | PVE::Storage::activate_volumes($self->{storecfg}, $need_activate); |
6f42807e DM |
78 | |
79 | # todo: test if VM uses local resources | |
80 | ||
81 | # test ssh connection | |
82 | my $cmd = [ @{$self->{rem_ssh}}, '/bin/true' ]; | |
83 | eval { $self->cmd_quiet($cmd); }; | |
84 | die "Can't connect to destination address using public key\n" if $@; | |
85 | ||
86 | return $running; | |
87 | } | |
88 | ||
89 | sub phase1 { | |
90 | my ($self, $vmid) = @_; | |
91 | ||
92 | $self->log('info', "starting migration of CT $self->{vmid} to node '$self->{node}' ($self->{nodeip})"); | |
93 | ||
94 | my $conf = $self->{vmconf}; | |
95 | $conf->{lock} = 'migrate'; | |
67afe46e | 96 | PVE::LXC::Config->write_config($vmid, $conf); |
6f42807e DM |
97 | |
98 | if ($self->{running}) { | |
99 | $self->log('info', "container is running - using online migration"); | |
100 | } | |
101 | ||
3c5dabe1 FG |
102 | $self->{volumes} = []; # list of already migrated volumes |
103 | my $volhash = {}; # 1 for local volumes | |
6f42807e | 104 | |
3c5dabe1 FG |
105 | my $test_volid = sub { |
106 | my ($volid, $snapname) = @_; | |
6f42807e | 107 | |
3c5dabe1 FG |
108 | return if !$volid; |
109 | ||
110 | my ($sid, $volname) = PVE::Storage::parse_volume_id($volid); | |
111 | ||
112 | # check if storage is available on both nodes | |
113 | my $scfg = PVE::Storage::storage_check_node($self->{storecfg}, $sid); | |
114 | PVE::Storage::storage_check_node($self->{storecfg}, $sid, $self->{node}); | |
115 | ||
116 | return if $scfg->{shared}; | |
117 | ||
118 | my ($path, $owner) = PVE::Storage::path($self->{storecfg}, $volid); | |
119 | ||
120 | die "can't migrate volume '$volid' - owned by other guest (owner = $owner)\n" | |
121 | if !$owner || ($owner != $self->{vmid}); | |
9746c095 | 122 | |
3c5dabe1 FG |
123 | if (defined($snapname)) { |
124 | # we cannot migrate shapshots on local storage | |
125 | # exceptions: 'zfspool' | |
126 | if (($scfg->{type} eq 'zfspool')) { | |
127 | $volhash->{$volid} = 1; | |
128 | return; | |
129 | } | |
130 | die "can't migrate snapshot of local volume '$volid'\n"; | |
131 | } else { | |
132 | $volhash->{$volid} = 1; | |
133 | } | |
134 | }; | |
135 | ||
136 | my $test_mp = sub { | |
137 | my ($ms, $mountpoint, $snapname) = @_; | |
138 | ||
139 | my $volid = $mountpoint->{volume}; | |
9746c095 FG |
140 | # already checked in prepare |
141 | if ($mountpoint->{type} ne 'volume') { | |
142 | $self->log('info', "ignoring mountpoint '$ms' ('$volid') of type " . | |
3c5dabe1 FG |
143 | "'$mountpoint->{type}', migration is forced.") |
144 | if !$snapname; | |
9746c095 FG |
145 | return; |
146 | } | |
147 | ||
6f42807e DM |
148 | my ($storage, $volname) = PVE::Storage::parse_volume_id($volid); |
149 | my $scfg = PVE::Storage::storage_check_node($self->{storecfg}, $storage); | |
150 | ||
151 | if (!$scfg->{shared}) { | |
3c5dabe1 FG |
152 | $self->log('info', "copy mountpoint '$ms' ($volid) to node ' $self->{node}'") |
153 | if !$snapname; | |
154 | $volhash->{$volid} = 1; | |
6f42807e | 155 | } else { |
3c5dabe1 FG |
156 | $self->log('info', "mountpoint '$ms' is on shared storage '$storage'") |
157 | if !$snapname; | |
6f42807e | 158 | } |
3c5dabe1 FG |
159 | &$test_volid($volid, $snapname); |
160 | }; | |
161 | ||
162 | # first all currently used volumes | |
163 | PVE::LXC::Config->foreach_mountpoint($conf, $test_mp); | |
164 | ||
165 | # then all volumes referenced in snapshots | |
166 | foreach my $snapname (keys %{$conf->{snapshots}}) { | |
167 | &$test_volid($conf->{snapshots}->{$snapname}->{'vmstate'}, 0, undef) | |
168 | if defined($conf->{snapshots}->{$snapname}->{'vmstate'}); | |
169 | PVE::LXC::Config->foreach_mountpoint($conf->{snapshots}->{$snapname}, $test_mp, $snapname); | |
170 | } | |
171 | ||
172 | # finally unused / lost volumes owned by this container | |
173 | my @sids = PVE::Storage::storage_ids($self->{storecfg}); | |
174 | foreach my $storeid (@sids) { | |
175 | my $scfg = PVE::Storage::storage_config($self->{storecfg}, $storeid); | |
176 | next if $scfg->{shared}; | |
177 | next if !PVE::Storage::storage_check_enabled($self->{storecfg}, $storeid, undef, 1); | |
178 | ||
179 | # get list from PVE::Storage (for unused volumes) | |
180 | my $dl = PVE::Storage::vdisk_list($self->{storecfg}, $storeid, $vmid); | |
181 | ||
182 | next if @{$dl->{$storeid}} == 0; | |
183 | ||
184 | # check if storage is available on target node | |
185 | PVE::Storage::storage_check_node($self->{storecfg}, $storeid, $self->{node}); | |
186 | ||
187 | PVE::Storage::foreach_volid($dl, sub { | |
188 | my ($volid, $sid, $volname) = @_; | |
189 | ||
190 | $self->log('info', "copy volume '$volid' to node '$self->{node}'") | |
191 | if !$volhash->{$volid}; | |
192 | $volhash->{$volid} = 1; | |
193 | }); | |
194 | } | |
195 | ||
196 | # additional checks for local storage | |
197 | foreach my $volid (keys %$volhash) { | |
198 | my ($sid, $volname) = PVE::Storage::parse_volume_id($volid); | |
199 | my $scfg = PVE::Storage::storage_config($self->{storecfg}, $sid); | |
200 | ||
201 | my $migratable = ($scfg->{type} eq 'dir') || ($scfg->{type} eq 'zfspool') || | |
202 | ($scfg->{type} eq 'lvmthin') || ($scfg->{type} eq 'lvm'); | |
203 | ||
204 | die "can't migrate '$volid' - storage type '$scfg->{type}' not supported\n" | |
205 | if !$migratable; | |
206 | ||
207 | # image is a linked clone on local storage, se we can't migrate. | |
208 | if (my $basename = (PVE::Storage::parse_volname($self->{storecfg}, $volid))[3]) { | |
209 | die "can't migrate '$volid' as it's a clone of '$basename'"; | |
210 | } | |
211 | } | |
212 | ||
213 | foreach my $volid (keys %$volhash) { | |
214 | my ($sid, $volname) = PVE::Storage::parse_volume_id($volid); | |
215 | push @{$self->{volumes}}, $volid; | |
216 | PVE::Storage::storage_migrate($self->{storecfg}, $volid, $self->{nodeip}, $sid); | |
217 | } | |
6f42807e | 218 | |
67afe46e FG |
219 | my $conffile = PVE::LXC::Config->config_file($vmid); |
220 | my $newconffile = PVE::LXC::Config->config_file($vmid, $self->{node}); | |
6f42807e DM |
221 | |
222 | if ($self->{running}) { | |
223 | die "implement me"; | |
224 | } | |
225 | ||
226 | # make sure everything on (shared) storage is unmounted | |
227 | # Note: we must be 100% sure, else we get data corruption because | |
228 | # non-shared file system could be mounted twice (on shared storage) | |
229 | ||
230 | PVE::LXC::umount_all($vmid, $self->{storecfg}, $conf); | |
231 | ||
c9bc5018 | 232 | #to be sure there are no active volumes |
d250604f | 233 | my $vollist = PVE::LXC::Config->get_vm_volumes($conf); |
c9bc5018 WL |
234 | PVE::Storage::deactivate_volumes($self->{storecfg}, $vollist); |
235 | ||
6f42807e DM |
236 | # move config |
237 | die "Failed to move config to node '$self->{node}' - rename failed: $!\n" | |
238 | if !rename($conffile, $newconffile); | |
1d1c1b4f WL |
239 | |
240 | $self->{conf_migrated} = 1; | |
6f42807e DM |
241 | } |
242 | ||
243 | sub phase1_cleanup { | |
244 | my ($self, $vmid, $err) = @_; | |
245 | ||
246 | $self->log('info', "aborting phase 1 - cleanup resources"); | |
247 | ||
248 | if ($self->{volumes}) { | |
249 | foreach my $volid (@{$self->{volumes}}) { | |
250 | $self->log('err', "found stale volume copy '$volid' on node '$self->{node}'"); | |
251 | # fixme: try to remove ? | |
252 | } | |
253 | } | |
254 | } | |
255 | ||
256 | sub phase3 { | |
257 | my ($self, $vmid) = @_; | |
258 | ||
259 | my $volids = $self->{volumes}; | |
260 | ||
261 | # destroy local copies | |
262 | foreach my $volid (@$volids) { | |
263 | eval { PVE::Storage::vdisk_free($self->{storecfg}, $volid); }; | |
264 | if (my $err = $@) { | |
265 | $self->log('err', "removing local copy of '$volid' failed - $err"); | |
266 | $self->{errors} = 1; | |
267 | last if $err =~ /^interrupted by signal$/; | |
268 | } | |
269 | } | |
270 | } | |
271 | ||
272 | sub final_cleanup { | |
273 | my ($self, $vmid) = @_; | |
274 | ||
275 | $self->log('info', "start final cleanup"); | |
276 | ||
1d1c1b4f WL |
277 | if (!$self->{conf_migrated}) { |
278 | my $conf = $self->{vmconf}; | |
279 | delete $conf->{lock}; | |
6f42807e | 280 | |
67afe46e | 281 | eval { PVE::LXC::Config->write_config($vmid, $conf); }; |
1d1c1b4f WL |
282 | if (my $err = $@) { |
283 | $self->log('err', $err); | |
284 | } | |
285 | } else { | |
286 | my $cmd = [ @{$self->{rem_ssh}}, 'pct', 'unlock', $vmid ]; | |
287 | $self->cmd_logerr($cmd, errmsg => "failed to clear migrate lock"); | |
6f42807e DM |
288 | } |
289 | } | |
290 | ||
291 | 1; |