]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/ISCSIPlugin.pm
pbs: allow setting up a master key
[pve-storage.git] / PVE / Storage / ISCSIPlugin.pm
CommitLineData
1dc01b9f
DM
1package PVE::Storage::ISCSIPlugin;
2
3use strict;
4use warnings;
c29bad0d 5
1dc01b9f
DM
6use File::stat;
7use IO::Dir;
8use IO::File;
c29bad0d 9
1dc01b9f 10use PVE::JSONSchema qw(get_standard_option);
c29bad0d
TL
11use PVE::Storage::Plugin;
12use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach $IPV4RE $IPV6RE);
1dc01b9f
DM
13
14use base qw(PVE::Storage::Plugin);
15
16# iscsi helper function
17
18my $ISCSIADM = '/usr/bin/iscsiadm';
19$ISCSIADM = undef if ! -X $ISCSIADM;
20
21sub check_iscsi_support {
22 my $noerr = shift;
23
24 if (!$ISCSIADM) {
25 my $msg = "no iscsi support - please install open-iscsi";
26 if ($noerr) {
27 warn "warning: $msg\n";
28 return 0;
29 }
30
31 die "error: $msg\n";
32 }
33
34 return 1;
35}
36
37sub iscsi_session_list {
38
39 check_iscsi_support ();
40
41 my $cmd = [$ISCSIADM, '--mode', 'session'];
42
43 my $res = {};
44
7acee37a
DM
45 eval {
46 run_command($cmd, errmsg => 'iscsi session scan failed', outfunc => sub {
47 my $line = shift;
4f430a43 48
bb3fc38b 49 if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)(\s+\S+)?\s*$/) {
7acee37a
DM
50 my ($session, $target) = ($1, $2);
51 # there can be several sessions per target (multipath)
4f430a43 52 push @{$res->{$target}}, $session;
7acee37a
DM
53 }
54 });
55 };
56 if (my $err = $@) {
57 die $err if $err !~ m/: No active sessions.$/i;
58 }
1dc01b9f
DM
59
60 return $res;
61}
62
63sub iscsi_test_portal {
64 my ($portal) = @_;
65
1689e627
WB
66 my ($server, $port) = PVE::Tools::parse_host_and_port($portal);
67 return 0 if !$server;
68 return PVE::Network::tcp_ping($server, $port || 3260, 2);
1dc01b9f
DM
69}
70
71sub iscsi_discovery {
72 my ($portal) = @_;
73
74 check_iscsi_support ();
75
4f430a43 76 my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets',
1dc01b9f
DM
77 '--portal', $portal];
78
79 my $res = {};
80
81 return $res if !iscsi_test_portal($portal); # fixme: raise exception here?
82
83 run_command($cmd, outfunc => sub {
84 my $line = shift;
85
1689e627 86 if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) {
1dc01b9f
DM
87 my $portal = $1;
88 my $target = $2;
89 # one target can have more than one portal (multipath).
90 push @{$res->{$target}}, $portal;
91 }
92 });
93
94 return $res;
95}
96
97sub iscsi_login {
98 my ($target, $portal_in) = @_;
99
100 check_iscsi_support ();
101
102 eval { iscsi_discovery ($portal_in); };
103 warn $@ if $@;
104
105 my $cmd = [$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login'];
106 run_command($cmd);
107}
108
109sub iscsi_logout {
110 my ($target, $portal) = @_;
111
112 check_iscsi_support ();
113
114 my $cmd = [$ISCSIADM, '--mode', 'node', '--targetname', $target, '--logout'];
115 run_command($cmd);
116}
117
118my $rescan_filename = "/var/run/pve-iscsi-rescan.lock";
119
120sub iscsi_session_rescan {
121 my $session_list = shift;
122
123 check_iscsi_support();
124
125 my $rstat = stat($rescan_filename);
126
127 if (!$rstat) {
128 if (my $fh = IO::File->new($rescan_filename, "a")) {
129 utime undef, undef, $fh;
130 close($fh);
131 }
132 } else {
133 my $atime = $rstat->atime;
134 my $tdiff = time() - $atime;
135 # avoid frequent rescans
136 return if !($tdiff < 0 || $tdiff > 10);
137 utime undef, undef, $rescan_filename;
138 }
139
140 foreach my $session (@$session_list) {
6b4acdb4 141 my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session, '--rescan'];
1dc01b9f
DM
142 eval { run_command($cmd, outfunc => sub {}); };
143 warn $@ if $@;
144 }
145}
146
147sub load_stable_scsi_paths {
148
149 my $stable_paths = {};
150
151 my $stabledir = "/dev/disk/by-id";
152
153 if (my $dh = IO::Dir->new($stabledir)) {
245b6517 154 foreach my $tmp (sort $dh->read) {
1dc01b9f 155 # exclude filenames with part in name (same disk but partitions)
4f430a43 156 # use only filenames with scsi(with multipath i have the same device
1dc01b9f 157 # with dm-uuid-mpath , dm-name and scsi in name)
245b6517 158 if($tmp !~ m/-part\d+$/ && ($tmp =~ m/^scsi-/ || $tmp =~ m/^dm-uuid-mpath-/)) {
1dc01b9f
DM
159 my $path = "$stabledir/$tmp";
160 my $bdevdest = readlink($path);
161 if ($bdevdest && $bdevdest =~ m|^../../([^/]+)|) {
162 $stable_paths->{$1}=$tmp;
163 }
164 }
165 }
166 $dh->close;
167 }
168 return $stable_paths;
169}
170
171sub iscsi_device_list {
172
173 my $res = {};
174
175 my $dirname = '/sys/class/iscsi_session';
176
177 my $stable_paths = load_stable_scsi_paths();
178
179 dir_glob_foreach($dirname, 'session(\d+)', sub {
180 my ($ent, $session) = @_;
181
182 my $target = file_read_firstline("$dirname/$ent/targetname");
183 return if !$target;
184
185 my (undef, $host) = dir_glob_regex("$dirname/$ent/device", 'target(\d+):.*');
186 return if !defined($host);
187
188 dir_glob_foreach("/sys/bus/scsi/devices", "$host:" . '(\d+):(\d+):(\d+)', sub {
189 my ($tmp, $channel, $id, $lun) = @_;
190
191 my $type = file_read_firstline("/sys/bus/scsi/devices/$tmp/type");
192 return if !defined($type) || $type ne '0'; # list disks only
193
194 my $bdev;
195 if (-d "/sys/bus/scsi/devices/$tmp/block") { # newer kernels
196 (undef, $bdev) = dir_glob_regex("/sys/bus/scsi/devices/$tmp/block/", '([A-Za-z]\S*)');
197 } else {
198 (undef, $bdev) = dir_glob_regex("/sys/bus/scsi/devices/$tmp", 'block:(\S+)');
199 }
200 return if !$bdev;
201
4f430a43
AL
202 #check multipath
203 if (-d "/sys/block/$bdev/holders") {
1dc01b9f
DM
204 my $multipathdev = dir_glob_regex("/sys/block/$bdev/holders", '[A-Za-z]\S*');
205 $bdev = $multipathdev if $multipathdev;
206 }
207
208 my $blockdev = $stable_paths->{$bdev};
209 return if !$blockdev;
210
211 my $size = file_read_firstline("/sys/block/$bdev/size");
212 return if !$size;
213
214 my $volid = "$channel.$id.$lun.$blockdev";
215
216 $res->{$target}->{$volid} = {
4f430a43
AL
217 'format' => 'raw',
218 'size' => int($size * 512),
1dc01b9f
DM
219 'vmid' => 0, # not assigned to any vm
220 'channel' => int($channel),
221 'id' => int($id),
222 'lun' => int($lun),
223 };
224
4f430a43 225 #print "TEST: $target $session $host,$bus,$tg,$lun $blockdev\n";
1dc01b9f
DM
226 });
227
228 });
229
230 return $res;
231}
232
233# Configuration
234
235sub type {
236 return 'iscsi';
237}
238
239sub plugindata {
240 return {
241 content => [ {images => 1, none => 1}, { images => 1 }],
5da48ca6 242 select_existing => 1,
1dc01b9f
DM
243 };
244}
245
246sub properties {
247 return {
248 target => {
249 description => "iSCSI target.",
250 type => 'string',
251 },
252 portal => {
253 description => "iSCSI portal (IP or DNS name with optional port).",
254 type => 'string', format => 'pve-storage-portal-dns',
255 },
256 };
257}
258
259sub options {
260 return {
261 portal => { fixed => 1 },
262 target => { fixed => 1 },
263 nodes => { optional => 1},
264 disable => { optional => 1},
265 content => { optional => 1},
9edb99a5 266 bwlimit => { optional => 1 },
1dc01b9f
DM
267 };
268}
269
270# Storage implementation
271
272sub parse_volname {
273 my ($class, $volname) = @_;
274
275 if ($volname =~ m!^\d+\.\d+\.\d+\.(\S+)$!) {
7800e84d 276 return ('images', $1, undef, undef, undef, undef, 'raw');
1dc01b9f
DM
277 }
278
279 die "unable to parse iscsi volume name '$volname'\n";
280}
281
452e3ee7 282sub filesystem_path {
e67069eb
DM
283 my ($class, $scfg, $volname, $snapname) = @_;
284
285 die "snapshot is not possible on iscsi storage\n" if defined($snapname);
1dc01b9f
DM
286
287 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
4f430a43 288
1dc01b9f
DM
289 my $path = "/dev/disk/by-id/$name";
290
5521b580 291 return wantarray ? ($path, $vmid, $vtype) : $path;
1dc01b9f
DM
292}
293
5eab0272
DM
294sub create_base {
295 my ($class, $storeid, $scfg, $volname) = @_;
296
297 die "can't create base images in iscsi storage\n";
298}
299
300sub clone_image {
f236eaf8 301 my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
5eab0272
DM
302
303 die "can't clone images in iscsi storage\n";
304}
305
1dc01b9f
DM
306sub alloc_image {
307 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
308
309 die "can't allocate space in iscsi storage\n";
310}
311
312sub free_image {
32437ed2 313 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
1dc01b9f
DM
314
315 die "can't free space in iscsi storage\n";
316}
317
3587acc8
DC
318# list all luns regardless of set content_types, since we need it for
319# listing in the gui and we can only have images anyway
320sub list_volumes {
321 my ($class, $storeid, $scfg, $vmid, $content_types) = @_;
322
323 my $res = $class->list_images($storeid, $scfg, $vmid);
324
325 for my $item (@$res) {
326 $item->{content} = 'images'; # we only have images
327 }
328
329 return $res;
330}
331
1dc01b9f
DM
332sub list_images {
333 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
334
335 my $res = [];
336
337 $cache->{iscsi_devices} = iscsi_device_list() if !$cache->{iscsi_devices};
338
339 # we have no owner for iscsi devices
340
341 my $target = $scfg->{target};
342
343 if (my $dat = $cache->{iscsi_devices}->{$target}) {
344
345 foreach my $volname (keys %$dat) {
346
347 my $volid = "$storeid:$volname";
348
349 if ($vollist) {
350 my $found = grep { $_ eq $volid } @$vollist;
351 next if !$found;
352 } else {
353 # we have no owner for iscsi devices
354 next if defined($vmid);
355 }
356
357 my $info = $dat->{$volname};
358 $info->{volid} = $volid;
359
360 push @$res, $info;
361 }
362 }
363
364 return $res;
365}
366
553c9b21
TL
367sub iscsi_session {
368 my ($cache, $target) = @_;
369 $cache->{iscsi_sessions} = iscsi_session_list() if !$cache->{iscsi_sessions};
370 return $cache->{iscsi_sessions}->{$target};
371}
372
1dc01b9f
DM
373sub status {
374 my ($class, $storeid, $scfg, $cache) = @_;
375
553c9b21
TL
376 my $session = iscsi_session($cache, $scfg->{target});
377 my $active = defined($session) ? 1 : 0;
1dc01b9f
DM
378
379 return (0, 0, 0, $active);
380}
381
382sub activate_storage {
383 my ($class, $storeid, $scfg, $cache) = @_;
384
385 return if !check_iscsi_support(1);
386
553c9b21 387 my $session = iscsi_session($cache, $scfg->{target});
1dc01b9f 388
553c9b21 389 if (!defined ($session)) {
1dc01b9f
DM
390 eval { iscsi_login($scfg->{target}, $scfg->{portal}); };
391 warn $@ if $@;
392 } else {
393 # make sure we get all devices
553c9b21 394 iscsi_session_rescan($session);
1dc01b9f
DM
395 }
396}
397
398sub deactivate_storage {
399 my ($class, $storeid, $scfg, $cache) = @_;
400
401 return if !check_iscsi_support(1);
402
553c9b21 403 if (defined(iscsi_session($cache, $scfg->{target}))) {
1dc01b9f
DM
404 iscsi_logout($scfg->{target}, $scfg->{portal});
405 }
406}
407
03b5bfdf
AD
408sub check_connection {
409 my ($class, $storeid, $scfg) = @_;
410
411 my $portal = $scfg->{portal};
412 return iscsi_test_portal($portal);
413}
414
0244a7b9
AD
415sub volume_resize {
416 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
417 die "volume resize is not possible on iscsi device";
418}
419
852a55f7
AD
420sub volume_has_feature {
421 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
422
7e8c6888
AD
423 my $features = {
424 copy => { current => 1},
425 };
426
427 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
428 $class->parse_volname($volname);
429
430 my $key = undef;
431 if($snapname){
2c5a7097 432 $key = 'snap';
7e8c6888
AD
433 }else{
434 $key = $isBase ? 'base' : 'current';
435 }
436 return 1 if $features->{$feature}->{$key};
437
852a55f7
AD
438 return undef;
439}
440
a548fd48 441
1dc01b9f 4421;