27
27
#include < multipass/process.h>
28
28
#include < multipass/ssh/ssh_session.h>
29
29
#include < multipass/utils.h>
30
- #include < multipass/virtual_machine_description.h>
31
30
#include < multipass/vm_status_monitor.h>
32
31
33
32
#include < multipass/format.h>
@@ -193,103 +192,12 @@ mp::QemuVirtualMachine::QemuVirtualMachine(const VirtualMachineDescription& desc
193
192
DNSMasqServer& dnsmasq_server, VMStatusMonitor& monitor)
194
193
: VirtualMachine{instance_image_has_snapshot (desc.image .image_path ) ? State::suspended : State::off, desc.vm_name },
195
194
tap_device_name{tap_device_name},
196
- vm_process{make_qemu_process (
197
- desc, ((state == State::suspended) ? mp::make_optional (monitor.retrieve_metadata_for (vm_name)) : mp::nullopt ),
198
- tap_device_name)},
195
+ desc{desc},
199
196
mac_addr{desc.mac_addr },
200
197
username{desc.ssh_username },
201
198
dnsmasq_server{&dnsmasq_server},
202
199
monitor{&monitor}
203
200
{
204
- QObject::connect (vm_process.get (), &Process::started, [this ]() {
205
- mpl::log (mpl::Level::info, vm_name, " process started" );
206
- on_started ();
207
- });
208
- QObject::connect (vm_process.get (), &Process::ready_read_standard_output, [this ]() {
209
- auto qmp_output = vm_process->read_all_standard_output ();
210
- mpl::log (mpl::Level::debug, vm_name, fmt::format (" QMP: {}" , qmp_output));
211
- auto qmp_object = QJsonDocument::fromJson (qmp_output.split (' \n ' ).first ()).object ();
212
- auto event = qmp_object[" event" ];
213
-
214
- if (!event.isNull ())
215
- {
216
- if (event.toString () == " RESET" && state != State::restarting)
217
- {
218
- mpl::log (mpl::Level::info, vm_name, " VM restarting" );
219
- on_restart ();
220
- }
221
- else if (event.toString () == " POWERDOWN" )
222
- {
223
- mpl::log (mpl::Level::info, vm_name, " VM powering down" );
224
- }
225
- else if (event.toString () == " SHUTDOWN" )
226
- {
227
- mpl::log (mpl::Level::info, vm_name, " VM shut down" );
228
- }
229
- else if (event.toString () == " STOP" )
230
- {
231
- mpl::log (mpl::Level::info, vm_name, " VM suspending" );
232
- }
233
- else if (event.toString () == " RESUME" )
234
- {
235
- mpl::log (mpl::Level::info, vm_name, " VM suspended" );
236
- if (state == State::suspending || state == State::running)
237
- {
238
- vm_process->kill ();
239
- on_suspend ();
240
- }
241
- }
242
- }
243
- });
244
-
245
- QObject::connect (vm_process.get (), &Process::ready_read_standard_error, [this ]() {
246
- saved_error_msg = vm_process->read_all_standard_error ().data ();
247
- mpl::log (mpl::Level::warning, vm_name, saved_error_msg);
248
- });
249
-
250
- QObject::connect (vm_process.get (), &Process::state_changed, [this ](QProcess::ProcessState newState) {
251
- mpl::log (mpl::Level::info, vm_name,
252
- fmt::format (" process state changed to {}" , utils::qenum_to_string (newState)));
253
- });
254
-
255
- QObject::connect (
256
- vm_process.get (), &Process::error_occurred, [this ](QProcess::ProcessError error, QString error_string) {
257
- // We just kill the process when suspending, so we don't want to print
258
- // out any scary error messages for this state
259
- if (update_shutdown_status)
260
- {
261
- mpl::log (mpl::Level::error, vm_name,
262
- fmt::format (" process error occurred {} {}" , utils::qenum_to_string (error), error_string));
263
- on_error ();
264
- }
265
- });
266
-
267
- QObject::connect (vm_process.get (), &Process::finished, [this ](ProcessState process_state) {
268
- if (process_state.exit_code )
269
- {
270
- mpl::log (mpl::Level::info, vm_name,
271
- fmt::format (" process finished with exit code {}" , process_state.exit_code .value ()));
272
- }
273
- if (process_state.error )
274
- {
275
- if (process_state.error ->state == QProcess::Crashed &&
276
- (state == State::suspending || state == State::suspended))
277
- {
278
- // when suspending, we ask Qemu to savevm. Once it confirms that's done, we kill it. Catch the "crash"
279
- mpl::log (mpl::Level::debug, vm_name, " Suspended VM successfully stopped" );
280
- }
281
- else
282
- {
283
- mpl::log (mpl::Level::error, vm_name, fmt::format (" error: {}" , process_state.error ->message ));
284
- }
285
- }
286
-
287
- if (update_shutdown_status || state == State::starting)
288
- {
289
- on_shutdown ();
290
- }
291
- });
292
-
293
201
QObject::connect (this , &QemuVirtualMachine::on_delete_memory_snapshot, this ,
294
202
[this ] {
295
203
mpl::log (mpl::Level::debug, vm_name, fmt::format (" Deleted memory snapshot" ));
@@ -301,14 +209,23 @@ mp::QemuVirtualMachine::QemuVirtualMachine(const VirtualMachineDescription& desc
301
209
302
210
mp::QemuVirtualMachine::~QemuVirtualMachine ()
303
211
{
304
- update_shutdown_status = false ;
305
- if (state == State::running)
306
- suspend ();
307
- else
308
- shutdown ();
212
+ if (vm_process)
213
+ {
214
+ update_shutdown_status = false ;
215
+
216
+ if (state == State::running)
217
+ {
218
+ suspend ();
219
+ }
220
+ else
221
+ {
222
+ shutdown ();
223
+ }
224
+
225
+ vm_process->wait_for_finished ();
226
+ }
309
227
310
228
remove_tap_device (QString::fromStdString (tap_device_name));
311
- vm_process->wait_for_finished ();
312
229
}
313
230
314
231
void mp::QemuVirtualMachine::start ()
@@ -319,12 +236,12 @@ void mp::QemuVirtualMachine::start()
319
236
if (state == State::suspending)
320
237
throw std::runtime_error (" cannot start the instance while suspending" );
321
238
239
+ initialize_vm_process ();
240
+
322
241
if (state == State::suspended)
323
242
{
324
243
mpl::log (mpl::Level::info, vm_name, fmt::format (" Resuming from a suspended state" ));
325
244
326
- auto metadata = monitor->retrieve_metadata_for (vm_name);
327
-
328
245
update_shutdown_status = true ;
329
246
delete_memory_snapshot = true ;
330
247
}
@@ -396,6 +313,7 @@ void mp::QemuVirtualMachine::suspend()
396
313
397
314
update_shutdown_status = false ;
398
315
vm_process->wait_for_finished ();
316
+ vm_process.reset (nullptr );
399
317
}
400
318
}
401
319
else if (state == State::off || state == State::suspended)
@@ -448,6 +366,7 @@ void mp::QemuVirtualMachine::on_shutdown()
448
366
449
367
ip = nullopt ;
450
368
update_state ();
369
+ vm_process.reset (nullptr );
451
370
lock.unlock ();
452
371
monitor->on_shutdown ();
453
372
}
@@ -538,3 +457,100 @@ void mp::QemuVirtualMachine::wait_until_ssh_up(std::chrono::milliseconds timeout
538
457
emit on_delete_memory_snapshot ();
539
458
}
540
459
}
460
+
461
+ void mp::QemuVirtualMachine::initialize_vm_process ()
462
+ {
463
+ vm_process = make_qemu_process (
464
+ desc, ((state == State::suspended) ? mp::make_optional (monitor->retrieve_metadata_for (vm_name)) : mp::nullopt ),
465
+ tap_device_name);
466
+
467
+ QObject::connect (vm_process.get (), &Process::started, [this ]() {
468
+ mpl::log (mpl::Level::info, vm_name, " process started" );
469
+ on_started ();
470
+ });
471
+
472
+ QObject::connect (vm_process.get (), &Process::ready_read_standard_output, [this ]() {
473
+ auto qmp_output = vm_process->read_all_standard_output ();
474
+ mpl::log (mpl::Level::debug, vm_name, fmt::format (" QMP: {}" , qmp_output));
475
+ auto qmp_object = QJsonDocument::fromJson (qmp_output.split (' \n ' ).first ()).object ();
476
+ auto event = qmp_object[" event" ];
477
+
478
+ if (!event.isNull ())
479
+ {
480
+ if (event.toString () == " RESET" && state != State::restarting)
481
+ {
482
+ mpl::log (mpl::Level::info, vm_name, " VM restarting" );
483
+ on_restart ();
484
+ }
485
+ else if (event.toString () == " POWERDOWN" )
486
+ {
487
+ mpl::log (mpl::Level::info, vm_name, " VM powering down" );
488
+ }
489
+ else if (event.toString () == " SHUTDOWN" )
490
+ {
491
+ mpl::log (mpl::Level::info, vm_name, " VM shut down" );
492
+ }
493
+ else if (event.toString () == " STOP" )
494
+ {
495
+ mpl::log (mpl::Level::info, vm_name, " VM suspending" );
496
+ }
497
+ else if (event.toString () == " RESUME" )
498
+ {
499
+ mpl::log (mpl::Level::info, vm_name, " VM suspended" );
500
+ if (state == State::suspending || state == State::running)
501
+ {
502
+ vm_process->kill ();
503
+ on_suspend ();
504
+ }
505
+ }
506
+ }
507
+ });
508
+
509
+ QObject::connect (vm_process.get (), &Process::ready_read_standard_error, [this ]() {
510
+ saved_error_msg = vm_process->read_all_standard_error ().data ();
511
+ mpl::log (mpl::Level::warning, vm_name, saved_error_msg);
512
+ });
513
+
514
+ QObject::connect (vm_process.get (), &Process::state_changed, [this ](QProcess::ProcessState newState) {
515
+ mpl::log (mpl::Level::info, vm_name,
516
+ fmt::format (" process state changed to {}" , utils::qenum_to_string (newState)));
517
+ });
518
+
519
+ QObject::connect (
520
+ vm_process.get (), &Process::error_occurred, [this ](QProcess::ProcessError error, QString error_string) {
521
+ // We just kill the process when suspending, so we don't want to print
522
+ // out any scary error messages for this state
523
+ if (update_shutdown_status)
524
+ {
525
+ mpl::log (mpl::Level::error, vm_name,
526
+ fmt::format (" process error occurred {} {}" , utils::qenum_to_string (error), error_string));
527
+ on_error ();
528
+ }
529
+ });
530
+
531
+ QObject::connect (vm_process.get (), &Process::finished, [this ](ProcessState process_state) {
532
+ if (process_state.exit_code )
533
+ {
534
+ mpl::log (mpl::Level::info, vm_name,
535
+ fmt::format (" process finished with exit code {}" , process_state.exit_code .value ()));
536
+ }
537
+ if (process_state.error )
538
+ {
539
+ if (process_state.error ->state == QProcess::Crashed &&
540
+ (state == State::suspending || state == State::suspended))
541
+ {
542
+ // when suspending, we ask Qemu to savevm. Once it confirms that's done, we kill it. Catch the "crash"
543
+ mpl::log (mpl::Level::debug, vm_name, " Suspended VM successfully stopped" );
544
+ }
545
+ else
546
+ {
547
+ mpl::log (mpl::Level::error, vm_name, fmt::format (" error: {}" , process_state.error ->message ));
548
+ }
549
+ }
550
+
551
+ if (update_shutdown_status || state == State::starting)
552
+ {
553
+ on_shutdown ();
554
+ }
555
+ });
556
+ }
0 commit comments