]>
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); | |
8 | use PVE::API2::Storage::Config; | |
9 | use PVE::Tools qw(run_command trim file_set_contents file_get_contents dir_glob_foreach lock_file); | |
10 | ||
11 | use PVE::RPCEnvironment; | |
12 | use PVE::RESTHandler; | |
13 | ||
14 | use base qw(PVE::RESTHandler); | |
15 | ||
16 | my $SGDISK = '/sbin/sgdisk'; | |
17 | my $MKFS = '/sbin/mkfs'; | |
18 | my $BLKID = '/sbin/blkid'; | |
19 | ||
20 | my $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 | ||
61 | my $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 | ||
90 | __PACKAGE__->register_method ({ | |
91 | name => 'index', | |
92 | path => '', | |
93 | method => 'GET', | |
94 | proxyto => 'node', | |
95 | protected => 1, | |
96 | permissions => { | |
97 | check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], | |
98 | }, | |
99 | description => "PVE Managed Directory storages.", | |
100 | parameters => { | |
101 | additionalProperties => 0, | |
102 | properties => { | |
103 | node => get_standard_option('pve-node'), | |
104 | }, | |
105 | }, | |
106 | returns => { | |
107 | type => 'array', | |
108 | items => { | |
109 | type => 'object', | |
110 | properties => { | |
111 | unitfile => { | |
112 | type => 'string', | |
fd87487b | 113 | description => 'The path of the mount unit.', |
793d720c DC |
114 | }, |
115 | path => { | |
116 | type => 'string', | |
117 | description => 'The mount path.', | |
118 | }, | |
119 | device => { | |
120 | type => 'string', | |
121 | description => 'The mounted device.', | |
122 | }, | |
123 | type => { | |
124 | type => 'string', | |
125 | description => 'The filesystem type.', | |
126 | }, | |
127 | options => { | |
128 | type => 'string', | |
129 | description => 'The mount options.', | |
130 | }, | |
131 | }, | |
132 | }, | |
133 | }, | |
134 | code => sub { | |
135 | my ($param) = @_; | |
136 | ||
137 | my $result = []; | |
138 | ||
139 | dir_glob_foreach('/etc/systemd/system', '^mnt-pve-(.+)\.mount$', sub { | |
140 | my ($filename, $storid) = @_; | |
141 | ||
142 | my $unitfile = "/etc/systemd/system/$filename"; | |
143 | my $unit = $read_ini->($unitfile); | |
144 | ||
145 | push @$result, { | |
146 | unitfile => $unitfile, | |
147 | path => "/mnt/pve/$storid", | |
148 | device => $unit->{'Mount'}->{'What'}, | |
149 | type => $unit->{'Mount'}->{'Type'}, | |
150 | options => $unit->{'Mount'}->{'Options'}, | |
151 | }; | |
152 | }); | |
153 | ||
154 | return $result; | |
155 | }}); | |
156 | ||
157 | __PACKAGE__->register_method ({ | |
158 | name => 'create', | |
159 | path => '', | |
160 | method => 'POST', | |
161 | proxyto => 'node', | |
162 | protected => 1, | |
163 | permissions => { | |
164 | check => ['perm', '/', ['Sys.Modify', 'Datastore.Allocate']], | |
165 | }, | |
166 | description => "Create a Filesystem on an unused disk. Will be mounted under '/mnt/pve/NAME'.", | |
167 | parameters => { | |
168 | additionalProperties => 0, | |
169 | properties => { | |
170 | node => get_standard_option('pve-node'), | |
171 | name => get_standard_option('pve-storage-id'), | |
172 | device => { | |
173 | type => 'string', | |
fdc863c7 | 174 | description => 'The block device you want to create the filesystem on.', |
793d720c DC |
175 | }, |
176 | add_storage => { | |
177 | description => "Configure storage using the directory.", | |
178 | type => 'boolean', | |
179 | optional => 1, | |
180 | default => 0, | |
181 | }, | |
182 | filesystem => { | |
183 | description => "The desired filesystem.", | |
184 | type => 'string', | |
185 | enum => ['ext4', 'xfs'], | |
186 | optional => 1, | |
187 | default => 'ext4', | |
188 | }, | |
189 | }, | |
190 | }, | |
191 | returns => { type => 'string' }, | |
192 | code => sub { | |
193 | my ($param) = @_; | |
194 | ||
195 | my $rpcenv = PVE::RPCEnvironment::get(); | |
196 | my $user = $rpcenv->get_user(); | |
197 | ||
198 | my $name = $param->{name}; | |
199 | my $dev = $param->{device}; | |
200 | my $node = $param->{node}; | |
201 | my $type = $param->{filesystem} // 'ext4'; | |
202 | ||
203 | $dev = PVE::Diskmanage::verify_blockdev_path($dev); | |
76c1e57b | 204 | PVE::Diskmanage::check_unused($dev); |
9280153e | 205 | PVE::Storage::assert_sid_unused($name) if $param->{add_storage}; |
793d720c DC |
206 | |
207 | my $worker = sub { | |
208 | my $path = "/mnt/pve/$name"; | |
209 | my $mountunitname = "mnt-pve-$name.mount"; | |
210 | my $mountunitpath = "/etc/systemd/system/$mountunitname"; | |
211 | ||
e39e8ee2 | 212 | PVE::Diskmanage::locked_disk_action(sub { |
793d720c DC |
213 | # create partition |
214 | my $cmd = [$SGDISK, '-n1', '-t1:8300', $dev]; | |
215 | print "# ", join(' ', @$cmd), "\n"; | |
216 | run_command($cmd); | |
217 | ||
401b56fc DC |
218 | my ($devname) = $dev =~ m|^/dev/(.*)$|; |
219 | my $part = "/dev/"; | |
220 | dir_glob_foreach("/sys/block/$devname", qr/\Q$devname\E.+/, sub { | |
221 | my ($partition) = @_; | |
222 | $part .= $partition; | |
223 | }); | |
793d720c DC |
224 | |
225 | # create filesystem | |
226 | $cmd = [$MKFS, '-t', $type, $part]; | |
227 | print "# ", join(' ', @$cmd), "\n"; | |
228 | run_command($cmd); | |
229 | ||
230 | # create systemd mount unit and enable & start it | |
231 | my $ini = { | |
232 | 'Unit' => { | |
233 | 'Description' => "Mount storage '$name' under /mnt/pve", | |
234 | }, | |
235 | 'Install' => { | |
236 | 'WantedBy' => 'multi-user.target', | |
237 | }, | |
238 | }; | |
239 | ||
240 | my $uuid_path; | |
241 | my $uuid; | |
242 | ||
243 | $cmd = [$BLKID, $part, '-o', 'export']; | |
244 | print "# ", join(' ', @$cmd), "\n"; | |
245 | run_command($cmd, outfunc => sub { | |
246 | my ($line) = @_; | |
247 | ||
248 | if ($line =~ m/^UUID=(.*)$/) { | |
249 | $uuid = $1; | |
250 | $uuid_path = "/dev/disk/by-uuid/$uuid"; | |
251 | } | |
252 | }); | |
253 | ||
254 | die "could not get UUID of device '$part'\n" if !$uuid; | |
255 | ||
256 | $ini->{'Mount'} = { | |
257 | 'What' => $uuid_path, | |
258 | 'Where' => $path, | |
259 | 'Type' => $type, | |
260 | 'Options' => 'defaults', | |
261 | }; | |
262 | ||
263 | $write_ini->($ini, $mountunitpath); | |
264 | ||
265 | run_command(['systemctl', 'daemon-reload']); | |
266 | run_command(['systemctl', 'enable', $mountunitname]); | |
267 | run_command(['systemctl', 'start', $mountunitname]); | |
268 | ||
269 | if ($param->{add_storage}) { | |
270 | my $storage_params = { | |
271 | type => 'dir', | |
272 | storage => $name, | |
273 | content => 'rootdir,images,iso,backup,vztmpl', | |
274 | is_mountpoint => 1, | |
275 | path => $path, | |
276 | nodes => $node, | |
277 | }; | |
278 | ||
279 | PVE::API2::Storage::Config->create($storage_params); | |
280 | } | |
281 | }); | |
793d720c DC |
282 | }; |
283 | ||
793d720c DC |
284 | return $rpcenv->fork_worker('dircreate', $name, $user, $worker); |
285 | }}); | |
286 | ||
287 | 1; |