/ext/QVD-HKD/lib/QVD/HKD/VMHandler/LXC.pm

https://github.com/BillTheBest/theqvd · Perl · 604 lines · 511 code · 71 blank · 22 comment · 30 complexity · c2724e9b7463fe69158b9044f9af15ac MD5 · raw file

  1. package QVD::HKD::VMHandler::LXC;
  2. BEGIN { *debug = \$QVD::HKD::VMHandler::debug }
  3. our $debug;
  4. use strict;
  5. use warnings;
  6. use 5.010;
  7. use POSIX;
  8. use AnyEvent;
  9. use AnyEvent::Util;
  10. use QVD::Log;
  11. use File::Temp qw(tempfile);
  12. use Linux::Proc::Mountinfo;
  13. use File::Spec;
  14. use Fcntl ();
  15. use Fcntl::Packer ();
  16. use Method::WeakCallback qw(weak_method_callback);
  17. use QVD::HKD::Helpers qw(mkpath);
  18. use QVD::HKD::VMHandler::LXC::FS;
  19. use parent qw(QVD::HKD::VMHandler);
  20. use Class::StateMachine::Declarative
  21. __any__ => { ignore => [qw(_on_cmd_start on_expired _on_lxc_done)],
  22. delay => [qw(on_hkd_kill
  23. _on_cmd_stop)],
  24. on => { on_hkd_stop => 'on_hkd_kill' },
  25. transitions => { _on_dirty => 'dirty' } },
  26. new => { transitions => { _on_cmd_start => 'starting',
  27. _on_cmd_stop => 'stopping/db',
  28. _on_cmd_catch_zombie => 'zombie' } },
  29. starting => { advance => '_on_done',
  30. on => { on_hkd_kill => '_on_error' },
  31. substates => [ db => { transitions => { _on_error => 'stopping/db' },
  32. substates => [ loading_row => { enter => '_load_row' },
  33. searching_di => { enter => '_search_di' },
  34. calculating_attrs => { enter => '_calculate_attrs' },
  35. saving_runtime_row => { enter => '_save_runtime_row' },
  36. updating_stats => { enter => '_incr_run_attempts' } ] },
  37. clean_old => { transitions => { _on_error => 'zombie/reap',
  38. on_hkd_kill => 'stopping/db' },
  39. substates => [ killing_lxc => { enter => '_kill_lxc' },
  40. unlinking_iface => { enter => '_unlink_iface' },
  41. removing_fw_rules => { enter => '_remove_fw_rules' },
  42. unmounting_filesystems => { enter => '_unmount_filesystems' } ] },
  43. os_fs => { enter => '_start_os_fs',
  44. transitions => { _on_error => 'stopping/os_fs' } },
  45. heavy => { enter => '_heavy_down',
  46. transitions => { _on_error => 'stopping/os_fs',
  47. _on_cmd_stop => 'stopping/os_fs' } },
  48. setup => { transitions => { _on_error => 'stopping/cleanup' },
  49. substates => [ allocating_home_fs => { enter => '_allocate_home_fs' },
  50. configuring_dhcpd => { enter => '_add_to_dhcpd' },
  51. creating_lxc => { enter => '_create_lxc' },
  52. running_prestart_hook => { enter => '_run_prestart_hook' },
  53. setting_fw_rules => { enter => '_set_fw_rules' },
  54. launching => { enter => '_start_lxc' } ] },
  55. waiting_for_vma => { enter => '_start_vma_monitor',
  56. transitions => { _on_alive => 'running',
  57. _on_dead => 'stopping/stop',
  58. _on_cmd_stop => 'stopping/stop',
  59. _on_lxc_done => 'stopping/cleanup',
  60. on_hkd_kill => 'stopping/stop',
  61. _on_goto_debug => 'debugging' } } ] },
  62. running => { advance => '_on_done',
  63. delay => [qw(_on_lxc_done)],
  64. transitions => { _on_error => 'stopping/stop' },
  65. substates => [ saving_state => { enter => '_save_state' },
  66. updating_stats => { enter => '_incr_run_ok' },
  67. running_poststart_hook => { enter => '_run_poststart_hook' },
  68. unheavy => { enter => '_heavy_up' },
  69. monitoring => { enter => '_start_vma_monitor',
  70. ignore => [qw(_on_alive)],
  71. transitions => { _on_dead => 'stopping/stop',
  72. _on_cmd_stop => 'stopping/shutdown',
  73. _on_lxc_done => 'stopping/cleanup',
  74. on_hkd_kill => 'stopping/stop',
  75. _on_goto_debug => 'debugging',
  76. on_expired => 'expiring' } },
  77. '(expiring)' => { enter => '_expire',
  78. transitions => { _on_done => 'monitoring' } } ] },
  79. debugging => { advance => '_on_done',
  80. delay => [qw(_on_lxc_done)],
  81. transitions => { _on_error => 'stopping/stop' },
  82. substates => [ saving_state => { enter => '_save_state' },
  83. unheavy => { enter => '_heavy_up' },
  84. monitoring => { enter => '_start_vma_monitor',
  85. ignore => [qw(_on_dead
  86. _on_goto_debug)],
  87. transitions => { _on_alive => 'running',
  88. _on_cmd_stop => 'stopping/stop',
  89. _on_lxc_done => 'stopping/cleanup',
  90. on_hkd_kill => 'stopping/stop' } } ] },
  91. stopping => { advance => '_on_done',
  92. transitions => { _on_error => 'zombie/reap' },
  93. delay => [qw(_on_lxc_done)],
  94. substates => [ shutdown => { transitions => { on_hkd_kill => 'stop' },
  95. substates => [ saving_state => { enter => '_save_state' },
  96. heavy => { enter => '_heavy_down' },
  97. shuttingdown => { enter => '_shutdown',
  98. transitions => { _on_error => 'stop',
  99. _on_lxc_done => 'cleanup' } },
  100. waiting_for_lxc => { enter => '_set_state_timer',
  101. transitions => { _on_lxc_done => 'cleanup',
  102. _on_state_timeout => 'stop' } } ] },
  103. stop => { substates => [ saving_state => { enter => '_save_state' },
  104. heavy => { enter => '_heavy_down' },
  105. running_stop => { enter => '_stop_lxc' },
  106. waiting_for_lxc => { enter => '_set_state_timer',
  107. transitions => { _on_lxc_done => 'cleanup',
  108. _on_state_timeout => 'cleanup' } } ] },
  109. cleanup => { ignore => [qw(_on_lxc_done)], # FIXME: is there really a reason for that?
  110. substates => [ saving_state => { enter => '_save_state' },
  111. checking_dirty => { enter => '_check_dirty_flag' },
  112. heavy => { enter => '_heavy_down' },
  113. killing_lxc => { enter => '_kill_lxc' },
  114. unlinking_iface => { enter => '_unlink_iface' },
  115. removing_fw_rules => { enter => '_remove_fw_rules' },
  116. running_poststop_hook => { enter => '_run_poststop_hook',
  117. transitions => { _on_error => 'destroying_lxc' } },
  118. destroying_lxc => { enter => '_destroy_lxc' },
  119. configuring_dhcpd => { enter => '_rm_from_dhcpd' } ] },
  120. os_fs => { enter => '_unmount_filesystems' },
  121. db => { enter => '_clear_runtime_row',
  122. transitions => { _on_error => 'zombie/db',
  123. _on_done => 'stopped' } } ] },
  124. stopped => { enter => '_on_stopped' },
  125. zombie => { advance => '_on_done',
  126. delay => [qw(_on_lxc_done)],
  127. ignore => [qw(on_hkd_stop)],
  128. transitions => { on_hkd_kill => 'stopped' },
  129. substates => [ config => { transitions => { _on_error => 'delaying' },
  130. substates => [ saving_state => { enter => '_save_state',
  131. on => { _on_error => '_on_done' } },
  132. calculating_attrs => { enter => '_calculate_attrs',
  133. transitions => { _on_error => 'delaying' } },
  134. '(delaying)' => { enter => '_set_state_timer',
  135. transitions => { _on_timeout => 'config' } } ] },
  136. reap => { transitions => { _on_error => 'delaying' },
  137. substates => [ saving_state => { enter => '_save_state',
  138. on => { _on_error => '_on_done' } },
  139. dirty => { enter => '_check_dirty_flag',
  140. transitions => { _on_error => 'dirty' } },
  141. heavy => { enter => '_heavy_down' },
  142. checking_lxc => { enter => '_check_lxc',
  143. transitions => { _on_error => 'killing_lxc' } },
  144. stopping_lxc => { enter => '_stop_lxc' },
  145. waiting_for_lxc => { enter => '_set_state_timer',
  146. transitions => { _on_lxc_done => 'killing_lxc',
  147. _on_state_timeout => 'killing_lxc'} },
  148. killing_lxc => { enter => '_kill_lxc' },
  149. unlinking_iface => { enter => '_unlink_iface' },
  150. removing_fw_rules => { enter => '_remove_fw_rules' },
  151. destroying_lxc => { enter => '_destroy_lxc',
  152. transitions => { _on_error => 'unmounting_filesystems' } },
  153. unmounting_filesystems => { enter => '_unmount_filesystems' },
  154. unheavy => { enter => '_heavy_up' },
  155. configuring_dhcpd => { enter => '_rm_from_dhcpd' },
  156. '(delaying)' => { enter => '_set_state_timer',
  157. transitions => { _on_state_timeout => 'reap'} } ] },
  158. db => { transitions => { _on_error => 'delaying' },
  159. substates => [ clearing_runtime_row => { enter => '_clear_runtime_row',
  160. transitions => { _on_done => 'stopped' } },
  161. '(delaying)' => { enter => '_set_state_timer',
  162. transitions => { _on_state_timeout => 'db'} } ] } ] },
  163. dirty => { ignore => [qw(on_hkd_stop)],
  164. transitions => { on_hkd_kill => 'stopped' } };
  165. sub _calculate_attrs {
  166. my $self = shift;
  167. $self->SUPER::_calculate_attrs;
  168. $self->{lxc_name} = "qvd-$self->{vm_id}";
  169. my $rootfs_parent = $self->_cfg('path.storage.rootfs');
  170. $rootfs_parent =~ s|/*$|/|;
  171. $self->{os_rootfs_parent} = $rootfs_parent;
  172. $self->{os_rootfs} = "$rootfs_parent$self->{vm_id}-fs";
  173. if (defined(my $di_path = $self->{di_path})) {
  174. # this sub is called with just the vm_id loaded into the
  175. # object when reaping zombie containers
  176. $self->{os_image_path} = $self->_cfg('path.storage.images') .'/'. $di_path;
  177. my $base_dir = $di_path;
  178. $base_dir =~ s/\.(?:tar(?:\.(?:gz|bz2|xz))?|tgz|tbz|txz)$//;
  179. my $basefs_parent = $self->_cfg('path.storage.basefs');
  180. $basefs_parent =~ s|/*$|/|;
  181. # note that os_basefs may be changed later from
  182. # _detect_os_image_type!
  183. $self->{os_basefs} = "$basefs_parent/$base_dir";
  184. $self->{os_basefs_lockfn} = "$basefs_parent/lock.$base_dir";
  185. # FIXME: use a better policy for overlay allocation
  186. my $overlays_parent = $self->_cfg('path.storage.overlayfs');
  187. $overlays_parent =~ s|/*$|/|;
  188. $self->{os_overlayfs} = $overlays_parent . join('-', $self->{di_id}, $self->{vm_id}, 'overlayfs');
  189. unless ($self->_cfg('vm.overlay.persistent')) {
  190. $self->{os_overlayfs_old} = $overlays_parent . join('-',
  191. 'deleteme', $self->{di_id}, $self->{vm_id},
  192. $$, rand(100000));
  193. }
  194. }
  195. if ($self->{user_storage_size}) {
  196. my $homefs_parent = $self->_cfg('path.storage.homefs');
  197. $homefs_parent =~ s|/*$|/|;
  198. if ($self->_cfg('vm.lxc.home.per.user')) {
  199. $self->{home_fs} = "$homefs_parent$self->{login}";
  200. $self->{home_fs_mnt} = "$self->{os_rootfs}/home/$self->{login}";
  201. }
  202. else {
  203. $self->{home_fs} = "$homefs_parent$self->{vm_id}-fs";
  204. $self->{home_fs_mnt} = "$self->{os_rootfs}/home";
  205. }
  206. }
  207. my $iface = $self->{iface} =
  208. $self->_cfg('internal.vm.network.device.prefix') . $self->{vm_id};
  209. # $self->_cfg('internal.vm.network.device.prefix') . $self->{vm_id} . 'r' . int(rand 10000);
  210. DEBUG("attributes for VM $self->{vm_id}: "
  211. . join(', ', map($_ . '=' . ($self->{$_} // '<undef>'),
  212. qw(di_path os_image_path os_basefs os_overlayfs
  213. os_overlayfs_old os_rootfs_parent os_rootfs
  214. home_fs home_fs_mnt iface netmask_len gateway) ) ) );
  215. $self->_on_done;
  216. }
  217. sub _start_os_fs {
  218. my $self = shift;
  219. my $fs = QVD::HKD::VMHandler::LXC::FS->new(vm_id => $self->{vm_id},
  220. config => $self->{config},
  221. heavy => $self->{heavy},
  222. on_error => weak_method_callback($self, '_on_error'),
  223. on_running => weak_method_callback($self, '_on_done'),
  224. image_path => $self->{os_image_path},
  225. basefs => $self->{os_basefs},
  226. basefs_lockfn => $self->{os_basefs_lockfn},
  227. overlayfs => $self->{os_overlayfs},
  228. overlayfs_old => $self->{os_overlayfs_old},
  229. rootfs => $self->{os_rootfs});
  230. $self->{os_fs} = $fs;
  231. $fs->run;
  232. }
  233. sub _allocate_home_fs {
  234. my $self = shift;
  235. my $homefs = $self->{home_fs};
  236. defined $homefs or return $self->_on_done;
  237. unless (mkpath $homefs) {
  238. ERROR "Unable to create directory '$homefs'";
  239. return $self->_on_error;
  240. }
  241. my $mount_point = $self->{home_fs_mnt};
  242. unless (mkpath $mount_point) {
  243. ERROR "Unable to create directory '$mount_point'";
  244. return $self->_on_error;
  245. }
  246. # let lxc mount the home file system for us
  247. DEBUG "Setting up homefs fstab entry as '$homefs $mount_point none defaults,bind'";
  248. $self->{home_fstab} = "$homefs $mount_point none defaults,bind";
  249. $self->_on_done
  250. }
  251. sub _create_lxc {
  252. my $self = shift;
  253. my $lxc_name = $self->{lxc_name};
  254. my $lxc_root = $self->_cfg('path.run.lxc');
  255. unless (-d $lxc_root or mkdir $lxc_root) {
  256. ERROR "Unable to create directory $lxc_root: $!";
  257. return $self->_on_error;
  258. }
  259. my $lxc_dir = "$lxc_root/$lxc_name";
  260. unless (-d $lxc_dir or mkdir $lxc_dir) {
  261. ERROR "Unable to create directory '$lxc_dir': $!";
  262. return $self->_on_error;
  263. }
  264. my $fn = "$lxc_dir/config";
  265. DEBUG "Saving lxc configuration to '$fn'";
  266. open my $cfg_fh, '>', $fn;
  267. unless ($cfg_fh) {
  268. ERROR "Unable to create file '$fn': $!";
  269. return $self->_on_error;
  270. }
  271. my $bridge = $self->_cfg('vm.network.bridge');
  272. my $console;
  273. if ($self->_cfg('vm.serial.capture')) {
  274. my $captures_dir = $self->_cfg('path.serial.captures');
  275. mkdir $captures_dir, 0700;
  276. my $err = $!;
  277. if (-d $captures_dir) {
  278. my @t = gmtime; $t[5] += 1900; $t[4] += 1;
  279. my $ts = sprintf("%04d-%02d-%02d-%02d:%02d:%2d-GMT0", @t[5,4,3,2,1,0]);
  280. $console = "$captures_dir/capture-$self->{name}-$ts.txt";
  281. DEBUG "Console output will be saved in '$console'";
  282. }
  283. else {
  284. ERROR "Captures directory '$captures_dir' does not exist and can not be created: $!";
  285. return $self->_on_error;
  286. }
  287. }
  288. else {
  289. $console = '/dev/null';
  290. DEBUG 'Console output will not be saved';
  291. }
  292. my $iface = $self->{iface};
  293. DEBUG "Local endpoint of the network device, connected to the bridge '$bridge': '$iface'";
  294. my $lxc_version = $self->_cfg('command.version.lxc');
  295. my $qvd_lxc_autodev = $self->_cfg('command.qvd-lxc-autodev');
  296. # FIXME: make this template-able or configurable in some way
  297. print $cfg_fh <<EOC;
  298. lxc.autodev=1
  299. lxc.hook.autodev=$qvd_lxc_autodev
  300. lxc.utsname=$self->{name}
  301. lxc.network.type=veth
  302. lxc.network.veth.pair=$iface
  303. lxc.network.name=eth0
  304. lxc.network.flags=up
  305. lxc.network.hwaddr=$self->{mac}
  306. lxc.network.link=$bridge
  307. lxc.console=$console
  308. lxc.tty=3
  309. lxc.pts=1
  310. lxc.rootfs=$self->{os_rootfs}
  311. lxc.mount.entry=$self->{home_fstab}
  312. lxc.pivotdir=qvd-pivot
  313. lxc.cgroup.cpu.shares=1024
  314. #lxc.cap.drop=sys_module audit_control audit_write linux_immutable mknod net_admin net_raw sys_admin sys_boot sys_resource sys_time
  315. # Deny access to all devices, except...
  316. lxc.cgroup.devices.deny = a
  317. # Allow any mknod (but not using the node)
  318. lxc.cgroup.devices.allow = c *:* m
  319. lxc.cgroup.devices.allow = b *:* m
  320. # /dev/null and zero
  321. lxc.cgroup.devices.allow = c 1:3 rwm
  322. lxc.cgroup.devices.allow = c 1:5 rwm
  323. # consoles /dev/tty, /dev/console
  324. lxc.cgroup.devices.allow = c 5:1 rwm
  325. lxc.cgroup.devices.allow = c 5:0 rwm
  326. # /dev/{,u}random
  327. lxc.cgroup.devices.allow = c 1:9 rwm
  328. lxc.cgroup.devices.allow = c 1:8 rwm
  329. lxc.cgroup.devices.allow = c 136:* rwm
  330. lxc.cgroup.devices.allow = c 5:2 rwm
  331. # rtc
  332. lxc.cgroup.devices.allow = c 254:0 rwm
  333. #fuse
  334. lxc.cgroup.devices.allow = c 10:229 rwm
  335. #tun
  336. lxc.cgroup.devices.allow = c 10:200 rwm
  337. #full
  338. lxc.cgroup.devices.allow = c 1:7 rwm
  339. #hpet
  340. lxc.cgroup.devices.allow = c 10:228 rwm
  341. #kvm
  342. lxc.cgroup.devices.allow = c 10:232 rwm
  343. EOC
  344. if (!$self->_cfg('vm.network.use_dhcp')) {
  345. print $cfg_fh <<EOC;
  346. lxc.network.ipv4 = $self->{ip}/$self->{netmask_len}
  347. EOC
  348. if ($lxc_version >= 0.8) {
  349. print $cfg_fh <<EOC;
  350. lxc.network.ipv4.gateway = $self->{gateway}
  351. EOC
  352. }
  353. }
  354. print $cfg_fh, $self->_cfg('internal.vm.lxc.conf.extra'), "\n";
  355. close $cfg_fh;
  356. $self->_on_done;
  357. }
  358. sub _start_lxc {
  359. my $self = shift;
  360. my $hv_out = $self->_hypervisor_output_redirection;
  361. $self->_run_cmd( { save_pid_to => 'vm_pid',
  362. ignore_errors => 1,
  363. outlives_state => 1,
  364. on_done => weak_method_callback($self, '_on_lxc_done'),
  365. '<' => '/dev/null',
  366. '>' => $hv_out,
  367. '2>' => $hv_out,
  368. },
  369. 'lxc-start', -n => $self->{lxc_name}, -P => $self->_cfg('path.run.lxc'));
  370. $self->_on_done;
  371. }
  372. sub _check_lxc {
  373. my $self = shift;
  374. if ($self->{vm_pid}) {
  375. $self->_on_done;
  376. }
  377. else {
  378. $self->_on_error;
  379. }
  380. }
  381. sub _stop_lxc {
  382. my $self = shift;
  383. if (defined $self->{vm_pid}) {
  384. $self->_run_cmd( { kill_after => $self->_cfg('internal.hkd.command.timeout.lxc-stop'),
  385. run_and_forget => 1 },
  386. 'lxc-stop', -n => $self->{lxc_name}, -P => $self->_cfg('path.run.lxc'));
  387. }
  388. $self->_on_done;
  389. }
  390. sub _check_dirty_flag {
  391. my $self = shift;
  392. if ($self->_cfg("internal.hkd.lxc.does.not.cleanup")) {
  393. $debug and $self->_debug("going dirty because internal.hkd.lxc.does.not.cleanup is set");
  394. return $self->_on_error;
  395. }
  396. return $self->_on_done;
  397. }
  398. sub _kill_lxc {
  399. my $self = shift;
  400. my @pids;
  401. my $cgroup_cpu_lxc = $self->_cfg('path.cgroup.cpu.lxc');
  402. my $fn = "$cgroup_cpu_lxc/$self->{lxc_name}/cgroup.procs";
  403. if (open my $fh, '<', $fn) {
  404. chomp(@pids = <$fh>);
  405. }
  406. else {
  407. $debug and $self->_debug("unable to open $fn: $!");
  408. INFO "Unable to open '$fn': $!";
  409. }
  410. my $vm_pid = $self->{vm_pid};
  411. push @pids, $vm_pid if defined $vm_pid;
  412. if (@pids) {
  413. $debug and $self->_debug("killing zombie processes and then trying again, pids: @pids");
  414. DEBUG "Killing zombie processes and then trying again, PIDs: @pids";
  415. if ($self->{killer_count}++ > $self->_cfg('internal.hkd.lxc.killer.retries')) {
  416. $debug and $self->_debug("too many retries, no more killing, peace!");
  417. WARN "Too many retries when killing cointainer processes: @pids";
  418. # $self->_abort_cmd($vm_pid);
  419. return $self->_on_error;
  420. }
  421. kill KILL => @pids;
  422. $self->_call_after(2 => '_kill_lxc');
  423. }
  424. else {
  425. $debug and $self->_debug("all processes killed");
  426. DEBUG "All processes killed";
  427. return $self->_on_done;
  428. }
  429. }
  430. sub _destroy_lxc {
  431. my $self = shift;
  432. my $lxc_name = $self->{lxc_name};
  433. my $lxc_dir = $self->_cfg('path.run.lxc'). "/$lxc_name";
  434. unlink "$lxc_dir/config" or DEBUG "unable to unlink '$lxc_dir/config': $!";
  435. rmdir $lxc_dir or DEBUG "unable to delete '$lxc_dir': $!";
  436. $self->_on_done;
  437. }
  438. sub _unlink_iface {
  439. my $self = shift;
  440. # FIXME: check that the interface has been really removed or return error
  441. $self->_run_cmd( { ignore_errors => 1 },
  442. 'ip', 'link', 'del', $self->{iface});
  443. }
  444. sub _unmount_filesystems {
  445. my $self = shift;
  446. $self->{unmounted} //= {};
  447. my $rootfs = $self->{os_rootfs};
  448. my $mi = Linux::Proc::Mountinfo->read;
  449. DEBUG "Unmounting filesystems under '$rootfs'";
  450. if (my $at = $mi->at($rootfs)) {
  451. my @mnts = map $_->mount_point, @{$at->flatten};
  452. $debug and $self->_debug("mnts behind $rootfs: @mnts");
  453. my @remaining = grep !$self->{unmounted}{$_}, @mnts;
  454. if (@remaining) {
  455. my $next = $remaining[-1];
  456. $self->{unmounted}{$next} = 1;
  457. return $self->__unmount_filesystem($next);
  458. }
  459. else {
  460. $debug and $self->_debug("Some filesystems could not be unmounted: @mnts");
  461. ERROR sprintf 'Some filesystems could not be unmounted: %s', join ', ', @mnts;
  462. delete $self->{unmounted};
  463. return $self->_on_error;
  464. }
  465. }
  466. else {
  467. $debug and $self->_debug("No filesystem mounted at $rootfs found");
  468. DEBUG "No filesystem mounted at '$rootfs' found";
  469. }
  470. delete $self->{unmounted};
  471. $self->_on_done
  472. }
  473. sub __unmount_filesystem {
  474. my ($self, $mnt) = @_;
  475. $self->_run_cmd( { timeout => $self->_cfg('internal.hkd.lxc.killer.umount.timeout'),
  476. ignore_errors => 1,
  477. on_done => '_unmount_filesystems' },
  478. umount => $mnt);
  479. }
  480. sub _hook_args {
  481. my $self = shift;
  482. map { $_ => $self->{$_} } qw( use_overlay
  483. mac
  484. name
  485. ip
  486. os_rootfs
  487. os_overlayfs
  488. lxc_name );
  489. }
  490. sub _run_hook {
  491. my ($self, $name) = @_;
  492. my $meta = $self->{os_fs}->image_metadata_dir;
  493. if (defined $meta) {
  494. my $hook = "$meta/hooks/$name";
  495. if (-f $hook) {
  496. my @args = ( id => $self->{vm_id},
  497. hook => $name,
  498. state => $self->_main_state,
  499. os_meta => $meta,
  500. $self->_hook_args );
  501. $debug and $self->_debug("running hook $hook for $name");
  502. DEBUG "Running hook '$hook' for '$name'";
  503. $self->_run_cmd( { skip_cmd_lookup => 1 },
  504. $hook => @args);
  505. return;
  506. } else {
  507. WARN "Hook '$hook' for '$name' not found";
  508. }
  509. }
  510. $debug and $self->_debug("no hooks for $name");
  511. DEBUG "No hooks for '$name'";
  512. $self->_on_done;
  513. }
  514. sub _run {
  515. my @cmd = @_;
  516. my $cmd_str = join(" ", @cmd);
  517. DEBUG "Running command: $cmd_str\n";
  518. my $ret = system(@cmd);
  519. if ( $? == -1 ) {
  520. ERROR "Failed to execute '$cmd_str': $!";
  521. return undef;
  522. } elsif ( $? & 127 ) {
  523. ERROR sprintf("Command '$cmd_str' died with signal %d, %s coredump\n", ($? & 127), ($? & 128) ? 'with' : 'without');
  524. return undef;
  525. } elsif ( ($? >> 8) > 0 ) {
  526. ERROR sprintf("Command '$cmd_str' exited with signal %d", $? >> 8);
  527. return undef;
  528. } else {
  529. DEBUG "Command executed successfully";
  530. }
  531. return $ret;
  532. }
  533. 1;