]>
git.proxmox.com Git - pve-storage.git/blob - PVE/Storage/ISCSIPlugin.pm
1 package PVE
::Storage
::ISCSIPlugin
;
10 use PVE
::JSONSchema
qw(get_standard_option);
11 use PVE
::Storage
::Plugin
;
12 use PVE
::Tools
qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach $IPV4RE $IPV6RE);
14 use base
qw(PVE::Storage::Plugin);
16 # iscsi helper function
18 my $ISCSIADM = '/usr/bin/iscsiadm';
19 $ISCSIADM = undef if ! -X
$ISCSIADM;
21 sub check_iscsi_support
{
25 my $msg = "no iscsi support - please install open-iscsi";
27 warn "warning: $msg\n";
37 sub iscsi_session_list
{
39 check_iscsi_support
();
41 my $cmd = [$ISCSIADM, '--mode', 'session'];
46 run_command
($cmd, errmsg
=> 'iscsi session scan failed', outfunc
=> sub {
49 if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)(\s+\S+)?\s*$/) {
50 my ($session, $target) = ($1, $2);
51 # there can be several sessions per target (multipath)
52 push @{$res->{$target}}, $session;
57 die $err if $err !~ m/: No active sessions.$/i;
63 sub iscsi_test_portal
{
66 my ($server, $port) = PVE
::Tools
::parse_host_and_port
($portal);
68 return PVE
::Network
::tcp_ping
($server, $port || 3260, 2);
74 check_iscsi_support
();
77 return $res if !iscsi_test_portal
($portal); # fixme: raise exception here?
79 my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal];
80 run_command
($cmd, outfunc
=> sub {
83 if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) {
86 # one target can have more than one portal (multipath).
87 push @{$res->{$target}}, $portal;
95 my ($target, $portal_in) = @_;
97 check_iscsi_support
();
99 eval { iscsi_discovery
($portal_in); };
102 run_command
([$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login']);
106 my ($target, $portal) = @_;
108 check_iscsi_support
();
110 run_command
([$ISCSIADM, '--mode', 'node', '--targetname', $target, '--logout']);
113 my $rescan_filename = "/var/run/pve-iscsi-rescan.lock";
115 sub iscsi_session_rescan
{
116 my $session_list = shift;
118 check_iscsi_support
();
120 my $rstat = stat($rescan_filename);
123 if (my $fh = IO
::File-
>new($rescan_filename, "a")) {
124 utime undef, undef, $fh;
128 my $atime = $rstat->atime;
129 my $tdiff = time() - $atime;
130 # avoid frequent rescans
131 return if !($tdiff < 0 || $tdiff > 10);
132 utime undef, undef, $rescan_filename;
135 foreach my $session (@$session_list) {
136 my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session, '--rescan'];
137 eval { run_command
($cmd, outfunc
=> sub {}); };
142 sub load_stable_scsi_paths
{
144 my $stable_paths = {};
146 my $stabledir = "/dev/disk/by-id";
148 if (my $dh = IO
::Dir-
>new($stabledir)) {
149 foreach my $tmp (sort $dh->read) {
150 # exclude filenames with part in name (same disk but partitions)
151 # use only filenames with scsi(with multipath i have the same device
152 # with dm-uuid-mpath , dm-name and scsi in name)
153 if($tmp !~ m/-part\d+$/ && ($tmp =~ m/^scsi-/ || $tmp =~ m/^dm-uuid-mpath-/)) {
154 my $path = "$stabledir/$tmp";
155 my $bdevdest = readlink($path);
156 if ($bdevdest && $bdevdest =~ m
|^../../([^/]+)|) {
157 $stable_paths->{$1}=$tmp;
163 return $stable_paths;
166 sub iscsi_device_list
{
170 my $dirname = '/sys/class/iscsi_session';
172 my $stable_paths = load_stable_scsi_paths
();
174 dir_glob_foreach
($dirname, 'session(\d+)', sub {
175 my ($ent, $session) = @_;
177 my $target = file_read_firstline
("$dirname/$ent/targetname");
180 my (undef, $host) = dir_glob_regex
("$dirname/$ent/device", 'target(\d+):.*');
181 return if !defined($host);
183 dir_glob_foreach
("/sys/bus/scsi/devices", "$host:" . '(\d+):(\d+):(\d+)', sub {
184 my ($tmp, $channel, $id, $lun) = @_;
186 my $type = file_read_firstline
("/sys/bus/scsi/devices/$tmp/type");
187 return if !defined($type) || $type ne '0'; # list disks only
190 if (-d
"/sys/bus/scsi/devices/$tmp/block") { # newer kernels
191 (undef, $bdev) = dir_glob_regex
("/sys/bus/scsi/devices/$tmp/block/", '([A-Za-z]\S*)');
193 (undef, $bdev) = dir_glob_regex
("/sys/bus/scsi/devices/$tmp", 'block:(\S+)');
198 if (-d
"/sys/block/$bdev/holders") {
199 my $multipathdev = dir_glob_regex
("/sys/block/$bdev/holders", '[A-Za-z]\S*');
200 $bdev = $multipathdev if $multipathdev;
203 my $blockdev = $stable_paths->{$bdev};
204 return if !$blockdev;
206 my $size = file_read_firstline
("/sys/block/$bdev/size");
209 my $volid = "$channel.$id.$lun.$blockdev";
211 $res->{$target}->{$volid} = {
213 'size' => int($size * 512),
214 'vmid' => 0, # not assigned to any vm
215 'channel' => int($channel),
220 #print "TEST: $target $session $host,$bus,$tg,$lun $blockdev\n";
236 content
=> [ {images
=> 1, none
=> 1}, { images
=> 1 }],
237 select_existing
=> 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},
261 bwlimit
=> { optional
=> 1 },
265 # Storage implementation
268 my ($class, $volname) = @_;
270 if ($volname =~ m!^\d+\.\d+\.\d+\.(\S+)$!) {
271 return ('images', $1, undef, undef, undef, undef, 'raw');
274 die "unable to parse iscsi volume name '$volname'\n";
277 sub filesystem_path
{
278 my ($class, $scfg, $volname, $snapname) = @_;
280 die "snapshot is not possible on iscsi storage\n" if defined($snapname);
282 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
284 my $path = "/dev/disk/by-id/$name";
286 return wantarray ?
($path, $vmid, $vtype) : $path;
290 my ($class, $storeid, $scfg, $volname) = @_;
292 die "can't create base images in iscsi storage\n";
296 my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
298 die "can't clone images in iscsi storage\n";
302 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
304 die "can't allocate space in iscsi storage\n";
308 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
310 die "can't free space in iscsi storage\n";
313 # list all luns regardless of set content_types, since we need it for
314 # listing in the gui and we can only have images anyway
316 my ($class, $storeid, $scfg, $vmid, $content_types) = @_;
318 my $res = $class->list_images($storeid, $scfg, $vmid);
320 for my $item (@$res) {
321 $item->{content
} = 'images'; # we only have images
328 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
332 $cache->{iscsi_devices
} = iscsi_device_list
() if !$cache->{iscsi_devices
};
334 # we have no owner for iscsi devices
336 my $target = $scfg->{target
};
338 if (my $dat = $cache->{iscsi_devices
}->{$target}) {
340 foreach my $volname (keys %$dat) {
342 my $volid = "$storeid:$volname";
345 my $found = grep { $_ eq $volid } @$vollist;
348 # we have no owner for iscsi devices
349 next if defined($vmid);
352 my $info = $dat->{$volname};
353 $info->{volid
} = $volid;
363 my ($cache, $target) = @_;
364 $cache->{iscsi_sessions
} = iscsi_session_list
() if !$cache->{iscsi_sessions
};
365 return $cache->{iscsi_sessions
}->{$target};
369 my ($class, $storeid, $scfg, $cache) = @_;
371 my $session = iscsi_session
($cache, $scfg->{target
});
372 my $active = defined($session) ?
1 : 0;
374 return (0, 0, 0, $active);
377 sub activate_storage
{
378 my ($class, $storeid, $scfg, $cache) = @_;
380 return if !check_iscsi_support
(1);
382 my $session = iscsi_session
($cache, $scfg->{target
});
384 if (!defined ($session)) {
385 eval { iscsi_login
($scfg->{target
}, $scfg->{portal
}); };
388 # make sure we get all devices
389 iscsi_session_rescan
($session);
393 sub deactivate_storage
{
394 my ($class, $storeid, $scfg, $cache) = @_;
396 return if !check_iscsi_support
(1);
398 if (defined(iscsi_session
($cache, $scfg->{target
}))) {
399 iscsi_logout
($scfg->{target
}, $scfg->{portal
});
403 sub check_connection
{
404 my ($class, $storeid, $scfg) = @_;
406 my $portal = $scfg->{portal
};
407 return iscsi_test_portal
($portal);
411 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
412 die "volume resize is not possible on iscsi device";
415 sub volume_has_feature
{
416 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
419 copy
=> { current
=> 1},
422 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
423 $class->parse_volname($volname);
429 $key = $isBase ?
'base' : 'current';
431 return 1 if $features->{$feature}->{$key};