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