]>
Commit | Line | Data |
---|---|---|
0c9a7596 AD |
1 | package PVE::QemuServer::Cloudinit; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use File::Path; | |
7 | use Digest::SHA; | |
8 | use URI::Escape; | |
9 | ||
10 | use PVE::Tools qw(run_command file_set_contents); | |
11 | use PVE::Storage; | |
12 | use PVE::QemuServer; | |
13 | ||
14 | sub nbd_stop { | |
15 | my ($vmid) = @_; | |
16 | ||
17 | PVE::QemuServer::vm_mon_cmd($vmid, 'nbd-server-stop'); | |
18 | } | |
19 | ||
20 | sub next_free_nbd_dev { | |
21 | for(my $i = 0;;$i++) { | |
22 | my $dev = "/dev/nbd$i"; | |
23 | last if ! -b $dev; | |
24 | next if -f "/sys/block/nbd$i/pid"; # busy | |
25 | return $dev; | |
26 | } | |
27 | die "unable to find free nbd device\n"; | |
28 | } | |
29 | ||
30 | sub commit_cloudinit_disk { | |
31 | my ($file_path, $iso_path, $format) = @_; | |
32 | ||
33 | my $nbd_dev = next_free_nbd_dev(); | |
34 | run_command(['qemu-nbd', '-c', $nbd_dev, $iso_path, '-f', $format]); | |
35 | ||
36 | eval { | |
37 | run_command([['genisoimage', '-R', '-V', 'config-2', $file_path], | |
38 | ['dd', "of=$nbd_dev", 'conv=fsync']]); | |
39 | }; | |
40 | my $err = $@; | |
41 | eval { run_command(['qemu-nbd', '-d', $nbd_dev]); }; | |
42 | warn $@ if $@; | |
43 | die $err if $err; | |
44 | } | |
45 | ||
46 | sub generate_cloudinitconfig { | |
47 | my ($conf, $vmid) = @_; | |
48 | ||
49 | PVE::QemuServer::foreach_drive($conf, sub { | |
50 | my ($ds, $drive) = @_; | |
51 | ||
52 | my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file}, 1); | |
53 | ||
54 | return if !$volname || $volname !~ m/vm-$vmid-cloudinit/; | |
55 | ||
56 | my $path = "/tmp/cloudinit/$vmid"; | |
57 | ||
58 | mkdir "/tmp/cloudinit"; | |
59 | mkdir $path; | |
60 | mkdir "$path/drive"; | |
61 | mkdir "$path/drive/openstack"; | |
62 | mkdir "$path/drive/openstack/latest"; | |
63 | mkdir "$path/drive/openstack/content"; | |
64 | my $digest_data = generate_cloudinit_userdata($conf, $path) | |
65 | . generate_cloudinit_network($conf, $path); | |
66 | generate_cloudinit_metadata($conf, $path, $digest_data); | |
67 | ||
68 | my $storecfg = PVE::Storage::config(); | |
69 | my $iso_path = PVE::Storage::path($storecfg, $drive->{file}); | |
70 | my $scfg = PVE::Storage::storage_config($storecfg, $storeid); | |
71 | my $format = PVE::QemuServer::qemu_img_format($scfg, $volname); | |
72 | #fixme : add meta as drive property to compare | |
73 | commit_cloudinit_disk("$path/drive", $iso_path, $format); | |
74 | rmtree("$path/drive"); | |
75 | }); | |
76 | } | |
77 | ||
78 | ||
79 | sub generate_cloudinit_userdata { | |
80 | my ($conf, $path) = @_; | |
81 | ||
82 | my $content = "#cloud-config\n"; | |
83 | my $hostname = $conf->{hostname}; | |
84 | if (!defined($hostname)) { | |
85 | $hostname = $conf->{name}; | |
86 | if (my $search = $conf->{searchdomain}) { | |
87 | $hostname .= ".$search"; | |
88 | } | |
89 | } | |
90 | $content .= "fqdn: $hostname\n"; | |
91 | $content .= "manage_etc_hosts: true\n"; | |
92 | $content .= "bootcmd: \n"; | |
93 | $content .= " - ifdown -a\n"; | |
94 | $content .= " - ifup -a\n"; | |
95 | ||
96 | my $keys = $conf->{sshkeys}; | |
97 | if ($keys) { | |
98 | $keys = URI::Escape::uri_unescape($keys); | |
99 | $keys = [map { chomp $_; $_ } split(/\n/, $keys)]; | |
100 | $keys = [grep { /\S/ } @$keys]; | |
101 | ||
102 | $content .= "users:\n"; | |
103 | $content .= " - default\n"; | |
104 | $content .= " - name: root\n"; | |
105 | $content .= " ssh-authorized-keys:\n"; | |
106 | foreach my $k (@$keys) { | |
107 | $content .= " - $k\n"; | |
108 | } | |
109 | } | |
110 | ||
111 | $content .= "package_upgrade: true\n"; | |
112 | ||
113 | my $fn = "$path/drive/openstack/latest/user_data"; | |
114 | file_set_contents($fn, $content); | |
115 | return $content; | |
116 | } | |
117 | ||
118 | sub generate_cloudinit_metadata { | |
119 | my ($conf, $path, $digest_data) = @_; | |
120 | ||
121 | my $uuid_str = Digest::SHA::sha1_hex($digest_data); | |
122 | ||
123 | my $content = "{\n"; | |
124 | $content .= " \"uuid\": \"$uuid_str\",\n"; | |
125 | $content .= " \"network_config\" :{ \"content_path\": \"/content/0000\"}\n"; | |
126 | $content .= "}\n"; | |
127 | ||
128 | my $fn = "$path/drive/openstack/latest/meta_data.json"; | |
129 | ||
130 | file_set_contents($fn, $content); | |
131 | } | |
132 | ||
133 | sub generate_cloudinit_network { | |
134 | my ($conf, $path) = @_; | |
135 | ||
136 | my $content = "auto lo\n"; | |
137 | $content .="iface lo inet loopback\n\n"; | |
138 | ||
139 | my @ifaces = grep(/^net(\d+)$/, keys %$conf); | |
140 | foreach my $iface (@ifaces) { | |
141 | (my $id = $iface) =~ s/^net//; | |
142 | next if !$conf->{"ipconfig$id"}; | |
143 | my $net = PVE::QemuServer::parse_ipconfig($conf->{"ipconfig$id"}); | |
144 | $id = "eth$id"; | |
145 | ||
146 | $content .="auto $id\n"; | |
147 | if ($net->{ip}) { | |
148 | if ($net->{ip} eq 'dhcp') { | |
149 | $content .= "iface $id inet dhcp\n"; | |
150 | } else { | |
151 | my ($addr, $mask) = split('/', $net->{ip}); | |
152 | $content .= "iface $id inet static\n"; | |
153 | $content .= " address $addr\n"; | |
154 | $content .= " netmask $PVE::Network::ipv4_reverse_mask->[$mask]\n"; | |
155 | $content .= " gateway $net->{gw}\n" if $net->{gw}; | |
156 | } | |
157 | } | |
158 | if ($net->{ip6}) { | |
159 | if ($net->{ip6} =~ /^(auto|dhcp)$/) { | |
160 | $content .= "iface $id inet6 $1\n"; | |
161 | } else { | |
162 | my ($addr, $mask) = split('/', $net->{ip6}); | |
163 | $content .= "iface $id inet6 static\n"; | |
164 | $content .= " address $addr\n"; | |
165 | $content .= " netmask $mask\n"; | |
166 | $content .= " gateway $net->{gw6}\n" if $net->{gw6}; | |
167 | } | |
168 | } | |
169 | } | |
170 | ||
171 | $content .=" dns_nameservers $conf->{nameserver}\n" if $conf->{nameserver}; | |
172 | $content .=" dns_search $conf->{searchdomain}\n" if $conf->{searchdomain}; | |
173 | ||
174 | my $fn = "$path/drive/openstack/content/0000"; | |
175 | file_set_contents($fn, $content); | |
176 | return $content; | |
177 | } | |
178 | ||
179 | ||
180 | 1; |