]> git.proxmox.com Git - qemu-server.git/blame - PVE/QemuServer/Cloudinit.pm
implement cloudinit
[qemu-server.git] / PVE / QemuServer / Cloudinit.pm
CommitLineData
0c9a7596
AD
1package PVE::QemuServer::Cloudinit;
2
3use strict;
4use warnings;
5
6use File::Path;
7use Digest::SHA;
8use URI::Escape;
9
10use PVE::Tools qw(run_command file_set_contents);
11use PVE::Storage;
12use PVE::QemuServer;
13
14sub nbd_stop {
15 my ($vmid) = @_;
16
17 PVE::QemuServer::vm_mon_cmd($vmid, 'nbd-server-stop');
18}
19
20sub 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
30sub 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
46sub 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
79sub 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
118sub 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
133sub 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
1801;