]> git.proxmox.com Git - pve-storage.git/blame - PVE/API2/Disks/Directory.pm
diskmanage: add helper for udev workaround
[pve-storage.git] / PVE / API2 / Disks / Directory.pm
CommitLineData
793d720c
DC
1package PVE::API2::Disks::Directory;
2
3use strict;
4use warnings;
5
a83d8eb1
FE
6use POSIX;
7
793d720c
DC
8use PVE::Diskmanage;
9use PVE::JSONSchema qw(get_standard_option);
b0373adc
TL
10use PVE::RESTHandler;
11use PVE::RPCEnvironment;
1022a7c4 12use PVE::Systemd;
793d720c
DC
13use PVE::Tools qw(run_command trim file_set_contents file_get_contents dir_glob_foreach lock_file);
14
b0373adc 15use PVE::API2::Storage::Config;
793d720c
DC
16
17use base qw(PVE::RESTHandler);
18
19my $SGDISK = '/sbin/sgdisk';
20my $MKFS = '/sbin/mkfs';
21my $BLKID = '/sbin/blkid';
22
23my $read_ini = sub {
24 my ($filename) = @_;
25
26 my $content = file_get_contents($filename);
27 my @lines = split /\n/, $content;
28
29 my $result = {};
30 my $section;
31
32 foreach my $line (@lines) {
33 $line = trim($line);
34 if ($line =~ m/^\[([^\]]+)\]/) {
35 $section = $1;
36 if (!defined($result->{$section})) {
37 $result->{$section} = {};
38 }
39 } elsif ($line =~ m/^(.*?)=(.*)$/) {
40 my ($key, $val) = ($1, $2);
41 if (!$section) {
42 warn "key value pair found without section, skipping\n";
43 next;
44 }
45
46 if ($result->{$section}->{$key}) {
47 # make duplicate properties to arrays to keep the order
48 my $prop = $result->{$section}->{$key};
49 if (ref($prop) eq 'ARRAY') {
50 push @$prop, $val;
51 } else {
52 $result->{$section}->{$key} = [$prop, $val];
53 }
54 } else {
55 $result->{$section}->{$key} = $val;
56 }
57 }
58 # ignore everything else
59 }
60
61 return $result;
62};
63
64my $write_ini = sub {
65 my ($ini, $filename) = @_;
66
67 my $content = "";
68
69 foreach my $sname (sort keys %$ini) {
70 my $section = $ini->{$sname};
71
72 $content .= "[$sname]\n";
73
74 foreach my $pname (sort keys %$section) {
75 my $prop = $section->{$pname};
76
77 if (!ref($prop)) {
78 $content .= "$pname=$prop\n";
79 } elsif (ref($prop) eq 'ARRAY') {
80 foreach my $val (@$prop) {
81 $content .= "$pname=$val\n";
82 }
83 } else {
84 die "invalid property '$pname'\n";
85 }
86 }
87 $content .= "\n";
88 }
89
90 file_set_contents($filename, $content);
91};
92
93__PACKAGE__->register_method ({
94 name => 'index',
95 path => '',
96 method => 'GET',
97 proxyto => 'node',
98 protected => 1,
99 permissions => {
100 check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
101 },
102 description => "PVE Managed Directory storages.",
103 parameters => {
104 additionalProperties => 0,
105 properties => {
106 node => get_standard_option('pve-node'),
107 },
108 },
109 returns => {
110 type => 'array',
111 items => {
112 type => 'object',
113 properties => {
114 unitfile => {
115 type => 'string',
fd87487b 116 description => 'The path of the mount unit.',
793d720c
DC
117 },
118 path => {
119 type => 'string',
120 description => 'The mount path.',
121 },
122 device => {
123 type => 'string',
124 description => 'The mounted device.',
125 },
126 type => {
127 type => 'string',
128 description => 'The filesystem type.',
129 },
130 options => {
131 type => 'string',
132 description => 'The mount options.',
133 },
134 },
135 },
136 },
137 code => sub {
138 my ($param) = @_;
139
140 my $result = [];
141
142 dir_glob_foreach('/etc/systemd/system', '^mnt-pve-(.+)\.mount$', sub {
143 my ($filename, $storid) = @_;
1022a7c4 144 $storid = PVE::Systemd::unescape_unit($storid);
793d720c
DC
145
146 my $unitfile = "/etc/systemd/system/$filename";
147 my $unit = $read_ini->($unitfile);
148
149 push @$result, {
150 unitfile => $unitfile,
151 path => "/mnt/pve/$storid",
152 device => $unit->{'Mount'}->{'What'},
153 type => $unit->{'Mount'}->{'Type'},
154 options => $unit->{'Mount'}->{'Options'},
155 };
156 });
157
158 return $result;
159 }});
160
161__PACKAGE__->register_method ({
162 name => 'create',
163 path => '',
164 method => 'POST',
165 proxyto => 'node',
166 protected => 1,
167 permissions => {
168 check => ['perm', '/', ['Sys.Modify', 'Datastore.Allocate']],
169 },
170 description => "Create a Filesystem on an unused disk. Will be mounted under '/mnt/pve/NAME'.",
171 parameters => {
172 additionalProperties => 0,
173 properties => {
174 node => get_standard_option('pve-node'),
175 name => get_standard_option('pve-storage-id'),
176 device => {
177 type => 'string',
fdc863c7 178 description => 'The block device you want to create the filesystem on.',
793d720c
DC
179 },
180 add_storage => {
181 description => "Configure storage using the directory.",
182 type => 'boolean',
183 optional => 1,
184 default => 0,
185 },
186 filesystem => {
187 description => "The desired filesystem.",
188 type => 'string',
189 enum => ['ext4', 'xfs'],
190 optional => 1,
191 default => 'ext4',
192 },
193 },
194 },
195 returns => { type => 'string' },
196 code => sub {
197 my ($param) = @_;
198
199 my $rpcenv = PVE::RPCEnvironment::get();
200 my $user = $rpcenv->get_user();
201
202 my $name = $param->{name};
203 my $dev = $param->{device};
204 my $node = $param->{node};
205 my $type = $param->{filesystem} // 'ext4';
206
207 $dev = PVE::Diskmanage::verify_blockdev_path($dev);
0370861c 208 PVE::Diskmanage::assert_disk_unused($dev);
9280153e 209 PVE::Storage::assert_sid_unused($name) if $param->{add_storage};
793d720c
DC
210
211 my $worker = sub {
212 my $path = "/mnt/pve/$name";
1022a7c4 213 my $mountunitname = PVE::Systemd::escape_unit($path, 1) . ".mount";
793d720c
DC
214 my $mountunitpath = "/etc/systemd/system/$mountunitname";
215
e39e8ee2 216 PVE::Diskmanage::locked_disk_action(sub {
e99bc248
FE
217 PVE::Diskmanage::assert_disk_unused($dev);
218
a2c34371
FE
219 my $part = $dev;
220
05d91712
FE
221 if (PVE::Diskmanage::is_partition($dev)) {
222 eval { PVE::Diskmanage::change_parttype($dev, '8300'); };
223 warn $@ if $@;
224 } else {
a2c34371
FE
225 # create partition
226 my $cmd = [$SGDISK, '-n1', '-t1:8300', $dev];
227 print "# ", join(' ', @$cmd), "\n";
228 run_command($cmd);
229
230 my ($devname) = $dev =~ m|^/dev/(.*)$|;
231 $part = "/dev/";
232 dir_glob_foreach("/sys/block/$devname", qr/\Q$devname\E.+/, sub {
233 my ($partition) = @_;
234 $part .= $partition;
235 });
236 }
793d720c
DC
237
238 # create filesystem
a2c34371 239 my $cmd = [$MKFS, '-t', $type, $part];
793d720c
DC
240 print "# ", join(' ', @$cmd), "\n";
241 run_command($cmd);
242
243 # create systemd mount unit and enable & start it
244 my $ini = {
245 'Unit' => {
246 'Description' => "Mount storage '$name' under /mnt/pve",
247 },
248 'Install' => {
249 'WantedBy' => 'multi-user.target',
250 },
251 };
252
253 my $uuid_path;
254 my $uuid;
255
256 $cmd = [$BLKID, $part, '-o', 'export'];
257 print "# ", join(' ', @$cmd), "\n";
258 run_command($cmd, outfunc => sub {
259 my ($line) = @_;
260
261 if ($line =~ m/^UUID=(.*)$/) {
262 $uuid = $1;
263 $uuid_path = "/dev/disk/by-uuid/$uuid";
264 }
265 });
266
267 die "could not get UUID of device '$part'\n" if !$uuid;
268
269 $ini->{'Mount'} = {
270 'What' => $uuid_path,
271 'Where' => $path,
272 'Type' => $type,
273 'Options' => 'defaults',
274 };
275
276 $write_ini->($ini, $mountunitpath);
277
26082b7d 278 PVE::Diskmanage::udevadm_trigger($part);
21a75847 279
793d720c
DC
280 run_command(['systemctl', 'daemon-reload']);
281 run_command(['systemctl', 'enable', $mountunitname]);
282 run_command(['systemctl', 'start', $mountunitname]);
283
284 if ($param->{add_storage}) {
285 my $storage_params = {
286 type => 'dir',
287 storage => $name,
4ec588fe 288 content => 'rootdir,images,iso,backup,vztmpl,snippets',
793d720c
DC
289 is_mountpoint => 1,
290 path => $path,
291 nodes => $node,
292 };
293
294 PVE::API2::Storage::Config->create($storage_params);
295 }
296 });
793d720c
DC
297 };
298
793d720c
DC
299 return $rpcenv->fork_worker('dircreate', $name, $user, $worker);
300 }});
301
a83d8eb1
FE
302__PACKAGE__->register_method ({
303 name => 'delete',
304 path => '{name}',
305 method => 'DELETE',
306 proxyto => 'node',
307 protected => 1,
308 permissions => {
309 check => ['perm', '/', ['Sys.Modify', 'Datastore.Allocate']],
310 },
311 description => "Unmounts the storage and removes the mount unit.",
312 parameters => {
313 additionalProperties => 0,
314 properties => {
315 node => get_standard_option('pve-node'),
316 name => get_standard_option('pve-storage-id'),
317 },
318 },
319 returns => { type => 'string' },
320 code => sub {
321 my ($param) = @_;
322
323 my $rpcenv = PVE::RPCEnvironment::get();
324 my $user = $rpcenv->get_user();
325
326 my $name = $param->{name};
327
328 my $worker = sub {
329 my $path = "/mnt/pve/$name";
330 my $mountunitname = PVE::Systemd::escape_unit($path, 1) . ".mount";
331 my $mountunitpath = "/etc/systemd/system/$mountunitname";
332
333 PVE::Diskmanage::locked_disk_action(sub {
334 run_command(['systemctl', 'stop', $mountunitname]);
335 run_command(['systemctl', 'disable', $mountunitname]);
336
337 unlink $mountunitpath or $! == ENOENT or die "cannot remove $mountunitpath - $!\n";
338 });
339 };
340
341 return $rpcenv->fork_worker('dirremove', $name, $user, $worker);
342 }});
343
793d720c 3441;