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