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