]>
git.proxmox.com Git - pve-storage.git/blob - PVE/Storage/ISCSIPlugin.pm
ef88143a800cc13e7b49cbb8b4b4fc0ac7430d7f
1 package PVE
::Storage
::ISCSIPlugin
;
8 use PVE
::Tools
qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach);
9 use PVE
::Storage
::Plugin
;
10 use PVE
::JSONSchema
qw(get_standard_option);
13 use base
qw(PVE::Storage::Plugin);
15 # iscsi helper function
17 my $ISCSIADM = '/usr/bin/iscsiadm';
18 $ISCSIADM = undef if ! -X
$ISCSIADM;
20 sub check_iscsi_support
{
24 my $msg = "no iscsi support - please install open-iscsi";
26 warn "warning: $msg\n";
36 sub iscsi_session_list
{
38 check_iscsi_support
();
40 my $cmd = [$ISCSIADM, '--mode', 'session'];
44 run_command
($cmd, outfunc
=> sub {
47 if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)\s*$/) {
48 my ($session, $target) = ($1, $2);
49 # there can be several sessions per target (multipath)
50 push @{$res->{$target}}, $session;
58 sub iscsi_test_portal
{
61 my ($server, $port) = split(':', $portal);
62 my $p = Net
::Ping-
>new("tcp", 2);
63 $p->port_number($port || 3260);
64 return $p->ping($server);
70 check_iscsi_support
();
72 my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets',
77 return $res if !iscsi_test_portal
($portal); # fixme: raise exception here?
79 run_command
($cmd, outfunc
=> sub {
82 if ($line =~ m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+)\,\S+\s+(\S+)\s*$/) {
85 # one target can have more than one portal (multipath).
86 push @{$res->{$target}}, $portal;
94 my ($target, $portal_in) = @_;
96 check_iscsi_support
();
98 eval { iscsi_discovery
($portal_in); };
101 my $cmd = [$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login'];
106 my ($target, $portal) = @_;
108 check_iscsi_support
();
110 my $cmd = [$ISCSIADM, '--mode', 'node', '--targetname', $target, '--logout'];
114 my $rescan_filename = "/var/run/pve-iscsi-rescan.lock";
116 sub iscsi_session_rescan
{
117 my $session_list = shift;
119 check_iscsi_support
();
121 my $rstat = stat($rescan_filename);
124 if (my $fh = IO
::File-
>new($rescan_filename, "a")) {
125 utime undef, undef, $fh;
129 my $atime = $rstat->atime;
130 my $tdiff = time() - $atime;
131 # avoid frequent rescans
132 return if !($tdiff < 0 || $tdiff > 10);
133 utime undef, undef, $rescan_filename;
136 foreach my $session (@$session_list) {
137 my $cmd = [$ISCSIADM, '--mode', 'session', '-r', $session, '-R'];
138 eval { run_command
($cmd, outfunc
=> sub {}); };
143 sub load_stable_scsi_paths
{
145 my $stable_paths = {};
147 my $stabledir = "/dev/disk/by-id";
149 if (my $dh = IO
::Dir-
>new($stabledir)) {
150 while (defined(my $tmp = $dh->read)) {
151 # exclude filenames with part in name (same disk but partitions)
152 # use only filenames with scsi(with multipath i have the same device
153 # with dm-uuid-mpath , dm-name and scsi in name)
154 if($tmp !~ m/-part\d+$/ && $tmp =~ m/^scsi-/) {
155 my $path = "$stabledir/$tmp";
156 my $bdevdest = readlink($path);
157 if ($bdevdest && $bdevdest =~ m
|^../../([^/]+)|) {
158 $stable_paths->{$1}=$tmp;
164 return $stable_paths;
167 sub iscsi_device_list
{
171 my $dirname = '/sys/class/iscsi_session';
173 my $stable_paths = load_stable_scsi_paths
();
175 dir_glob_foreach
($dirname, 'session(\d+)', sub {
176 my ($ent, $session) = @_;
178 my $target = file_read_firstline
("$dirname/$ent/targetname");
181 my (undef, $host) = dir_glob_regex
("$dirname/$ent/device", 'target(\d+):.*');
182 return if !defined($host);
184 dir_glob_foreach
("/sys/bus/scsi/devices", "$host:" . '(\d+):(\d+):(\d+)', sub {
185 my ($tmp, $channel, $id, $lun) = @_;
187 my $type = file_read_firstline
("/sys/bus/scsi/devices/$tmp/type");
188 return if !defined($type) || $type ne '0'; # list disks only
191 if (-d
"/sys/bus/scsi/devices/$tmp/block") { # newer kernels
192 (undef, $bdev) = dir_glob_regex
("/sys/bus/scsi/devices/$tmp/block/", '([A-Za-z]\S*)');
194 (undef, $bdev) = dir_glob_regex
("/sys/bus/scsi/devices/$tmp", 'block:(\S+)');
199 if (-d
"/sys/block/$bdev/holders") {
200 my $multipathdev = dir_glob_regex
("/sys/block/$bdev/holders", '[A-Za-z]\S*');
201 $bdev = $multipathdev if $multipathdev;
204 my $blockdev = $stable_paths->{$bdev};
205 return if !$blockdev;
207 my $size = file_read_firstline
("/sys/block/$bdev/size");
210 my $volid = "$channel.$id.$lun.$blockdev";
212 $res->{$target}->{$volid} = {
214 'size' => int($size * 512),
215 'vmid' => 0, # not assigned to any vm
216 'channel' => int($channel),
221 #print "TEST: $target $session $host,$bus,$tg,$lun $blockdev\n";
237 content
=> [ {images
=> 1, none
=> 1}, { images
=> 1 }],
244 description
=> "iSCSI target.",
248 description
=> "iSCSI portal (IP or DNS name with optional port).",
249 type
=> 'string', format
=> 'pve-storage-portal-dns',
256 portal
=> { fixed
=> 1 },
257 target
=> { fixed
=> 1 },
258 nodes
=> { optional
=> 1},
259 disable
=> { optional
=> 1},
260 content
=> { optional
=> 1},
264 # Storage implementation
267 my ($class, $volname) = @_;
269 if ($volname =~ m!^\d+\.\d+\.\d+\.(\S+)$!) {
270 return ('images', $1, undef);
273 die "unable to parse iscsi volume name '$volname'\n";
277 my ($class, $scfg, $volname) = @_;
279 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
281 my $path = "/dev/disk/by-id/$name";
283 return wantarray ?
($path, $vmid, $vtype) : $path;
287 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
289 die "can't allocate space in iscsi storage\n";
293 my ($class, $storeid, $scfg, $volname) = @_;
295 die "can't free space in iscsi storage\n";
299 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
303 $cache->{iscsi_devices
} = iscsi_device_list
() if !$cache->{iscsi_devices
};
305 # we have no owner for iscsi devices
307 my $target = $scfg->{target
};
309 if (my $dat = $cache->{iscsi_devices
}->{$target}) {
311 foreach my $volname (keys %$dat) {
313 my $volid = "$storeid:$volname";
316 my $found = grep { $_ eq $volid } @$vollist;
319 # we have no owner for iscsi devices
320 next if defined($vmid);
323 my $info = $dat->{$volname};
324 $info->{volid
} = $volid;
334 my ($class, $storeid, $scfg, $cache) = @_;
336 $cache->{iscsi_sessions
} = iscsi_session_list
() if !$cache->{iscsi_sessions
};
338 my $active = defined($cache->{iscsi_sessions
}->{$scfg->{target
}});
340 return (0, 0, 0, $active);
343 sub activate_storage
{
344 my ($class, $storeid, $scfg, $cache) = @_;
346 return if !check_iscsi_support
(1);
348 $cache->{iscsi_sessions
} = iscsi_session_list
() if !$cache->{iscsi_sessions
};
350 my $iscsi_sess = $cache->{iscsi_sessions
}->{$scfg->{target
}};
351 if (!defined ($iscsi_sess)) {
352 eval { iscsi_login
($scfg->{target
}, $scfg->{portal
}); };
355 # make sure we get all devices
356 iscsi_session_rescan
($iscsi_sess);
360 sub deactivate_storage
{
361 my ($class, $storeid, $scfg, $cache) = @_;
363 return if !check_iscsi_support
(1);
365 $cache->{iscsi_sessions
} = iscsi_session_list
() if !$cache->{iscsi_sessions
};
367 my $iscsi_sess = $cache->{iscsi_sessions
}->{$scfg->{target
}};
369 if (defined ($iscsi_sess)) {
370 iscsi_logout
($scfg->{target
}, $scfg->{portal
});
374 sub check_connection
{
375 my ($class, $storeid, $scfg) = @_;
377 my $portal = $scfg->{portal
};
378 return iscsi_test_portal
($portal);