Tested on pretty old 7-mode FAS2040 on iSCSI, but shuld also work on newer clustered setups and over FC media. Plugin needs non-default for PVE packages: multipath-tools, scsitools. Also option ``uid_attribute ID_WWN_WITH_EXTENSION'' and to blacklist all in multipath.conf are required.
v2 patch. Changes from v1: - Fixed status output. - Fixed `shared' option. - Fixed typo. - Code cleanups. - Rewrited switch-case to if-elsif to remove extra dependency. - Added libxml-simple-perl dependency to control.in. v3 changes: - Add new files to makefiles. - Fix case with single lun listing, where XML::Simple skips array creation in data. - Delete unused functions in NetApp.pm. - Disable inline compression (too slow). Signed-off-by: Dmitry Petuhov <mityapetu...@gmail.com> --- PVE/Storage.pm | 2 + PVE/Storage/LunCmd/Makefile | 2 +- PVE/Storage/LunCmd/NetApp.pm | 378 ++++++++++++++++++++++++++++++++++++++++++ PVE/Storage/MPDirectPlugin.pm | 352 +++++++++++++++++++++++++++++++++++++++ PVE/Storage/Makefile | 2 +- PVE/Storage/Plugin.pm | 2 +- control.in | 2 +- 7 files changed, 736 insertions(+), 4 deletions(-) diff --git a/PVE/Storage.pm b/PVE/Storage.pm index 991131a..a9260b6 100755 --- a/PVE/Storage.pm +++ b/PVE/Storage.pm @@ -32,6 +32,7 @@ use PVE::Storage::GlusterfsPlugin; use PVE::Storage::ZFSPoolPlugin; use PVE::Storage::ZFSPlugin; use PVE::Storage::DRBDPlugin; +use PVE::Storage::MPDirectPlugin; # load and initialize all plugins PVE::Storage::DirPlugin->register(); @@ -46,6 +47,7 @@ PVE::Storage::GlusterfsPlugin->register(); PVE::Storage::ZFSPoolPlugin->register(); PVE::Storage::ZFSPlugin->register(); PVE::Storage::DRBDPlugin->register(); +PVE::Storage::MPDirectPlugin->register(); PVE::Storage::Plugin->init(); my $UDEVADM = '/sbin/udevadm'; diff --git a/PVE/Storage/LunCmd/Makefile b/PVE/Storage/LunCmd/Makefile index b959255..4c0054c 100644 --- a/PVE/Storage/LunCmd/Makefile +++ b/PVE/Storage/LunCmd/Makefile @@ -1,4 +1,4 @@ -SOURCES=Comstar.pm Istgt.pm Iet.pm +SOURCES=Comstar.pm Istgt.pm Iet.pm NetApp.pm .PHONY: install install: diff --git a/PVE/Storage/LunCmd/NetApp.pm b/PVE/Storage/LunCmd/NetApp.pm new file mode 100644 index 0000000..08bedbe --- /dev/null +++ b/PVE/Storage/LunCmd/NetApp.pm @@ -0,0 +1,378 @@ +package PVE::Storage::LunCmd::NetApp; + +use strict; +use warnings; +use LWP::UserAgent; +use HTTP::Request; +use XML::Simple; + +sub netapp_request { + my ($scfg, $vserver, $params) = @_; + + my $vfiler = $vserver ? "vfiler='$vserver'" : ""; + + my $content = "<?xml version='1.0' encoding='UTF-8' ?>\n"; + $content .= "<!DOCTYPE netapp SYSTEM 'file:/etc/netapp_filer.dtd'>\n"; + $content .= "<netapp $vfiler version='1.19' xmlns='http://www.netapp.com/filer/admin'>\n"; + $content .= $params; + $content .= "</netapp>\n"; + my $url = "http://".$scfg->{adminserver}."/servlets/netapp.servlets.admin.XMLrequest_filer"; + my $request = HTTP::Request->new('POST',"$url"); + $request->authorization_basic($scfg->{login},$scfg->{password}); + + $request->content($content); + $request->content_length(length($content)); + my $ua = LWP::UserAgent->new; + my $response = $ua->request($request); + my $xmlparser = XML::Simple->new( KeepRoot => 1 ); + my $xmlresponse = $xmlparser->XMLin($response->{_content}); + + if(ref $xmlresponse->{netapp}->{results} eq 'ARRAY'){ + foreach my $result (@{$xmlresponse->{netapp}->{results}}) { + if($result->{status} ne 'passed'){ + die "netapp api error : ".$result->{reason}; + } + } + } + elsif ($xmlresponse->{netapp}->{results}->{status} ne 'passed') { + die "netapp api error : ".$content.$xmlresponse->{netapp}->{results}->{reason}; + } + + return $xmlresponse; +} + +sub netapp_build_params { + my ($execute, %params) = @_; + + my $xml = "<$execute>\n"; + while (my ($property, $value) = each(%params)){ + $xml.="<$property>$value</$property>\n"; + } + $xml.="</$execute>\n"; + + return $xml; + +} + + +sub netapp_create_volume { + my ($scfg, $volume, $size) = @_; + + print "netapp create volume $volume\n"; + my $aggregate = $scfg->{array}; + my $xmlparams = ($scfg->{api} == 8 && $scfg->{vserver})?netapp_build_params("volume-create", "containing-aggr-name" => $aggregate, "volume" => $volume, "size" => "$size", "junction-path" => "/images/$volume", "space-reserve" => "none"): + netapp_build_params("volume-create", "containing-aggr-name" => $aggregate, "volume" => $volume, "size" => "$size", "space-reserve" => "none"); + netapp_request($scfg, $scfg->{vserver}, $xmlparams); + +} + +sub netapp_sisenable_volume { + my ($scfg, $volume) = @_; + + print "netapp enable sis on volume $volume\n"; + my $xmlparams = netapp_build_params("sis-enable", "path" => "/vol/$volume"); + netapp_request($scfg, $scfg->{vserver}, $xmlparams); +} + +sub netapp_sissetconfig_volume { + my ($scfg, $volume) = @_; + + print "netapp enable compression on volume $volume\n"; + my $xmlparams = netapp_build_params("sis-set-config", "enable-compression" => "true", "enable-inline-compression" => "false", "schedule" => "-", "path" => "/vol/$volume"); + netapp_request($scfg, $scfg->{vserver}, $xmlparams); +} + +sub netapp_autosize_volume { + my ($scfg, $volume) = @_; + + print "netapp enable autosize on volume $volume\n"; + my $xmlparams = ($scfg->{api} == 8)? + netapp_build_params('volume-autosize-set', 'mode' => 'grow_shrink', 'volume' => $volume): + netapp_build_params('volume-autosize-set', 'is-enabled' => 'true', 'volume' => $volume); + netapp_request($scfg, $scfg->{vserver}, $xmlparams); +} + +sub netapp_snapshotsetreserve_volume { + my ($scfg, $volume) = @_; + + print "netapp set snapshotreserver 0% on volume $volume\n"; + my $xmlparams = netapp_build_params("snapshot-set-reserve", "volume" => $volume, "percentage" => "0"); + netapp_request($scfg, $scfg->{vserver}, $xmlparams); +} + +sub netapp_resize_volume { + my ($scfg, $volume, $size) = @_; + + netapp_request($scfg, $scfg->{vserver}, netapp_build_params("volume-size", "volume" => $volume, "new-size" => "$size" )); +} + +sub netapp_snapshot_create { + my ($scfg, $volume, $snapname) = @_; + + netapp_request($scfg, $scfg->{vserver}, netapp_build_params("snapshot-create", "volume" => $volume, "snapshot" => "$snapname" )); +} + +sub netapp_snapshot_exist { + my ($scfg, $volume, $snap) = @_; + + my $snapshotslist = netapp_request($scfg, $scfg->{vserver}, netapp_build_params("snapshot-list-info", "volume" => "$volume")); + my $snapshotexist = undef; + $snapshotexist = 1 if (defined($snapshotslist->{"netapp"}->{"results"}->{"snapshots"}->{"snapshot-info"}->{"$snap"})); + $snapshotexist = 1 if (defined($snapshotslist->{netapp}->{results}->{"snapshots"}->{"snapshot-info"}->{name}) && $snapshotslist->{netapp}->{results}->{"snapshots"}->{"snapshot-info"}->{name} eq $snap); + return $snapshotexist; +} + +sub netapp_snapshot_rollback { + my ($scfg, $volume, $snapname) = @_; + + netapp_request($scfg, $scfg->{vserver}, netapp_build_params("snapshot-restore-volume", "volume" => $volume, "snapshot" => "$snapname" )); +} + +sub netapp_snapshot_delete { + my ($scfg, $volume, $snapname) = @_; + + netapp_request($scfg, $scfg->{vserver}, netapp_build_params("snapshot-delete", "volume" => $volume, "snapshot" => "$snapname" )); +} + +sub netapp_unmount_volume { + my ($scfg, $volume) = @_; + + print "netapp umount volume $volume\n"; + my $xmlparams = netapp_build_params("volume-unmount", "volume-name" => "$volume", "force" => "true"); + netapp_request($scfg, $scfg->{vserver}, $xmlparams); +} + +sub netapp_offline_volume { + my ($scfg, $volume) = @_; + + print "netapp offline volume $volume\n"; + my $xmlparams = netapp_build_params("volume-offline", "name" => "$volume"); + netapp_request($scfg, $scfg->{vserver}, $xmlparams); +} + +sub netapp_destroy_volume { + my ($scfg, $volume) = @_; + + print "netapp destroy volume $volume\n"; + my $xmlparams = netapp_build_params("volume-destroy", "name" => "$volume"); + netapp_request($scfg, $scfg->{vserver}, $xmlparams); +} + +sub netapp_clone_volume { + my ($scfg, $volumesrc, $snap, $volumedst) = @_; + + netapp_request($scfg, $scfg->{vserver}, netapp_build_params("volume-clone-create", + "parent-volume" => "$volumesrc", + "parent-snapshot" => "$snap", + "volume" => "$volumedst", + "junction-path" => "/images/$volumedst", + "junction-active" => "true", + "space-reserve" => "none", + )); +} + +sub netapp_list_luns { + my ($scfg, $vmid) = @_; + my $list = {}; + + if ($scfg->{api} == 8) { + my $xmlresponse = netapp_request($scfg, $scfg->{'vserver'}, + '<lun-get-iter><desired-attributes><lun-attributes><path></path><serial-number></serial-number>'. + '<size></size><state></state><mapped></mapped></lun-attributes></desired-attributes>'. + '<max-records>5000</max-records></lun-get-iter>'); + + # For first LUN there may be not-array reference. So, turn it into single-element array. + my $luns = (ref($xmlresponse->{'netapp'}->{'results'}->{'attributes-list'}->{'lun-attributes'}) eq 'ARRAY')? + $xmlresponse->{'netapp'}->{'results'}->{'attributes-list'}->{'lun-attributes'}: + [$xmlresponse->{'netapp'}->{'results'}->{'attributes-list'}->{'lun-attributes'}]; + + foreach my $lun (@$luns) { + next unless $lun->{'path'} =~ m!/vol/(\S+)/(vm-(\d+)-disk-\d+)$!; + next if defined($vmid) and $3 != $vmid; + + my $name = $2; + $list->{$name}->{'vol'} = $1; + # Get wwn from LUN's serial number + $list->{$name}->{'wwn'} = $lun->{'serial-number'}; + $list->{$name}->{'wwn'} =~ s/(.)/sprintf("%x",ord($1))/eg; #convert to hex + $list->{$name}->{'wwn'} = '60a98000' . $list->{$name}->{'wwn'}; # Add netapp-specific prefix + $list->{$name}->{'path'} = $lun->{'path'}; + $list->{$name}->{'size'} = $lun->{'size'}; + $list->{$name}->{'online'} = ($lun->{'state'} eq 'online')?'true':'false'; + $list->{$name}->{'mapped'} = $lun->{'mapped'}; + } + } elsif ($scfg->{api} == 7) { + + my $xmlresponse = netapp_request($scfg, $scfg->{'vserver'}, netapp_build_params('lun-list-info')); + + # For first LUN there may be not-array reference. So, turn it into single-element array. + my $luns = (ref($xmlresponse->{'netapp'}->{'results'}->{'luns'}->{'lun-info'}) eq 'ARRAY')? + $xmlresponse->{'netapp'}->{'results'}->{'luns'}->{'lun-info'}: + [$xmlresponse->{'netapp'}->{'results'}->{'luns'}->{'lun-info'}]; + + foreach my $lun (@$luns) { + next unless $lun->{'path'} =~ m!/vol/(\S+)/(vm-(\d+)-disk-\d+)$!; + next if defined($vmid) and $3 != $vmid; + + my $name = $2; + $list->{$name}->{'vol'} = $1; + # Get wwn from LUN's serial number + $list->{$name}->{'wwn'} = $lun->{'serial-number'}; + $list->{$name}->{'wwn'} =~ s/(.)/sprintf("%x",ord($1))/eg; #convert to hex + $list->{$name}->{'wwn'} = '60a98000' . $list->{$name}->{'wwn'}; # Add netapp-specific prefix + $list->{$name}->{'path'} = $lun->{'path'}; + $list->{$name}->{'size'} = $lun->{'size'}; + $list->{$name}->{'online'} = $lun->{'online'}; + $list->{$name}->{'mapped'} = $lun->{'mapped'}; + } + } + + return $list; +} + +sub netapp_create_lun { + my ($scfg, $vol, $name, $size) = @_; + + my $xmlparams = ($scfg->{api} == 8)? + netapp_build_params('lun-create-by-size', 'ostype' => 'linux', 'path' => "/vol/$vol/$name" , 'size' => $size, + 'space-allocation-enabled' => 'true', 'space-reservation-enabled' => 'false'): + netapp_build_params('lun-create-by-size', 'ostype' => 'linux', 'path' => "/vol/$vol/$name" , 'size' => $size, + 'space-reservation-enabled' => 'false'); + + my $xmlresponse = netapp_request($scfg, $scfg->{'vserver'}, $xmlparams); +} + +sub netapp_map_lun { + my ($scfg, $vol, $name) = @_; + + my $xmlparams = netapp_build_params('lun-map', 'initiator-group' => $scfg->{igroup}, 'path' => "/vol/$vol/$name"); + + my $xmlresponse = netapp_request($scfg, $scfg->{'vserver'}, $xmlparams); +} + +sub netapp_resize_lun { + my ($scfg, $vol, $name, $newsize) = @_; + + my $xmlparams = netapp_build_params('lun-resize', 'path' => "/vol/$vol/$name", 'size' => $newsize); + my $xmlresponse = netapp_request($scfg, $scfg->{'vserver'}, $xmlparams); +} + +sub netapp_unmap_lun { + my ($scfg, $vol, $name) = @_; + + my $xmlparams = netapp_build_params('lun-unmap', 'initiator-group' => $scfg->{'igroup'}, 'path' => "/vol/$vol/$name"); + + my $xmlresponse = netapp_request($scfg, $scfg->{'vserver'}, $xmlparams); +} + +sub netapp_delete_lun { + my ($scfg, $vol, $name) = @_; + + # First, get lun offline + my $xmlresponse = netapp_request($scfg, $scfg->{'vserver'}, netapp_build_params('lun-offline', 'path' => "/vol/$vol/$name")); + + # Then delete it + $xmlresponse = netapp_request($scfg, $scfg->{'vserver'}, netapp_build_params('lun-destroy', 'path' => "/vol/$vol/$name")); +} + +sub netapp_aggregate_size { + my ($scfg) = @_; + + if ($scfg->{api} == 8) { + my $list = netapp_request($scfg, undef, netapp_build_params("aggr-get-iter", "desired-attributes" => "" )); + + foreach my $aggregate (@{$list->{netapp}->{results}->{"attributes-list"}->{"aggr-attributes"}}) { + if($aggregate->{"aggregate-name"} eq $scfg->{array}){ + my $used = $aggregate->{"aggr-space-attributes"}->{"size-used"}; + my $total = $aggregate->{"aggr-space-attributes"}->{"size-total"}; + my $free = $aggregate->{"aggr-space-attributes"}->{"size-available"}; + return ($total, $free, $used, 1); + } + } + } elsif ($scfg->{api} == 7) { + my $xmlresponse = netapp_request($scfg, undef, netapp_build_params("aggr-space-list-info")); + + foreach my $aggregate (@{$xmlresponse->{netapp}->{results}->{"aggregates"}->{"aggr-space-info"}}) { + if($aggregate->{"aggregate-name"} eq $scfg->{array}){ + my $used = $aggregate->{"size-used"}; + my $total = $aggregate->{"size-nominal"}; + my $free = $aggregate->{"size-free"}; + return [$total, $free, $used, 1]; + } + } + } +} + +sub name2vol { + my ($vol) = @_; + $vol =~ s/-/_/g; + $vol .= '_vol'; + return $vol; +} + +sub run_lun_command { + my ($scfg, $timeout, $method, @params) = @_; + + my $msg; + + if ($method eq 'create_lu') { + my ($name, $size) = @params; + # Netapp's GUI reserves 5% for snapshots by default, reproduce it. + netapp_create_volume($scfg, name2vol($name), int($size*1.05)); + netapp_sisenable_volume($scfg, name2vol($name)); + netapp_sissetconfig_volume($scfg, name2vol($name)); + netapp_autosize_volume($scfg, name2vol($name)); + netapp_snapshotsetreserve_volume($scfg, name2vol($name)); + netapp_create_lun($scfg, name2vol($name), $name, $size); + $msg = 1; + } + elsif ($method eq 'delete_lu') { + my ($name) = @params; + netapp_delete_lun($scfg, name2vol($name), $name); + netapp_unmount_volume($scfg,name2vol($name)) if ($scfg->{api} == 8 && $scfg->{vserver}); + netapp_offline_volume($scfg,name2vol($name)); + netapp_destroy_volume($scfg,name2vol($name)); + $msg = 1; + } + elsif ($method eq 'resize_lu') { + my ($name, $size) = @params; + netapp_resize_volume($scfg, name2vol($name), $size); + netapp_resize_lun($scfg, name2vol($name), $name, $size); + $msg = 1; + } + elsif ($method eq 'list_lu') { + my ($vmid) = @params; + $msg = netapp_list_luns($scfg, $vmid); + } + elsif ($method eq 'map_lu') { + my ($name) = @params; + netapp_map_lun($scfg, name2vol($name), $name); + $msg = 1; + } + elsif ($method eq 'unmap_lu') { + my ($name) = @params; + netapp_unmap_lun($scfg, name2vol($name), $name); + $msg = 1; + } + elsif ($method eq 'create_snap') { + my ($name, $snapname) = @params; + netapp_snapshot_create($scfg, name2vol($name), $snapname); + $msg = 1; + } + elsif ($method eq 'delete_snap') { + my ($name, $snapname) = @params; + netapp_snapshot_delete($scfg, name2vol($name), $snapname); + $msg = 1; + } + elsif ($method eq 'rollback_snap') { + my ($name, $snapname) = @params; + netapp_snapshot_rollback($scfg, name2vol($name), $snapname); + $msg = 1; + } + elsif ($method eq 'space_status') { + $msg = netapp_aggregate_size($scfg); + } + return $msg; +} + +1; diff --git a/PVE/Storage/MPDirectPlugin.pm b/PVE/Storage/MPDirectPlugin.pm new file mode 100644 index 0000000..b8d4c73 --- /dev/null +++ b/PVE/Storage/MPDirectPlugin.pm @@ -0,0 +1,352 @@ +package PVE::Storage::MPDirectPlugin; + +use strict; +use warnings; +use Data::Dumper; +use IO::File; +use PVE::HA::NodeStatus; +use PVE::Tools qw(run_command trim file_read_firstline dir_glob_foreach); +use PVE::Storage::Plugin; +use PVE::JSONSchema qw(get_standard_option); +use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); +use PVE::Storage::LunCmd::NetApp; + +use base qw(PVE::Storage::Plugin); + +# Scan storage bus for new paths. Linux SCSI subsystem is not automatic, +# so it doesn't know when new LUNS appear|disappear, and we need to kick it. +sub renew_media { + my ($scfg) = @_; + run_command(['/sbin/rescan-scsi-bus', '-r', '-i', '-a']); +} + +sub multipath_enable { + my ($scfg, $wwn) = @_; + open my $mpd, '<', '/etc/multipath.conf'; + open my $mpdt, '>', '/etc/multipath.conf.new'; + + renew_media($scfg); + + #Copy contents and insert line just afer exceptions block beginning + while (my $line = <$mpd>) { + print $mpdt $line; + print $mpdt "\twwid \"0x$wwn\"\n" if $line =~ m/^blacklist_exceptions \{/; + } + + close $mpdt; + close $mpd; + unlink '/etc/multipath.conf'; + rename '/etc/multipath.conf.new','/etc/multipath.conf'; + + #force devmap reload to connect new device + system('/sbin/multipath', '-r'); +} + +sub multipath_disable { + my ($scfg, $wwn) = @_; + + open my $mpd, '<', '/etc/multipath.conf'; + open my $mpdt, '>', '/etc/multipath.conf.new'; + + #Just copy contents except requested wwn + while (my $line = <$mpd>) { + print $mpdt $line unless $line =~ m/wwid "0x$wwn"/; + } + + close $mpdt; + close $mpd; + unlink '/etc/multipath.conf'; + rename '/etc/multipath.conf.new','/etc/multipath.conf'; + + #disable selected wwn multipathing + system('/sbin/multipath', '-f', $wwn); +} + +# Utility functions +sub mp_get_name { + my ($class, $scfg, $wwn) = @_; + + my $luns = $class->run_lun_command($scfg, 'list_lu', undef); + + foreach my $name (keys %$luns) { + return $name if $luns->{$name}->{'wwn'} eq $wwn; + } + die "cannot get name for wwn $wwn\n"; +} + +sub mp_get_wwn { + my ($class, $scfg, $name) = @_; + + my $luns = $class->run_lun_command($scfg, 'list_lu', undef); + + return $luns->{$name}->{'wwn'}; +} + + +sub run_lun_command { + my ($class, $scfg, $action, @params) = @_; + if ($scfg->{backend} eq 'netapp') { + return PVE::Storage::LunCmd::NetApp::run_lun_command($scfg, undef, $action, @params); + } else { + die "Unsupportned backend '". $scfg->{backend} ."' for multipath storage"; + } +} + +# Configuration + +sub type { + return 'mpdirect'; +} + +sub plugindata { + return { + content => [ {images => 1, rootdir => 1, none => 1}, { images => 1 }], + }; +} + +sub properties { + return { + vserver => { + description => "Vserver name. Netapp-spcific.", + type => 'string', + }, + array => { + description => "Array/Pool/Aggregate name.", + type => 'string', + }, + adminserver => { + description => "Management IP or DNS name of storage.", + type => 'string', format => 'pve-storage-server', + }, + login => { + description => "login", + type => 'string', + }, + password => { + description => "password", + type => 'string', + }, + igroup => { + description => "Initiator group name", + type => 'string', + }, + api => { + description => "API version (backend-specific)", + type => 'string', + }, + backend => { + description => "Backend storage type", + type => 'string', + enum => ['netapp'], + }, + media => { + description => "Backing media", + type => 'string', + enum => ['scsi','iscsi','fc'], + }, + }; +} + +sub options { + return { + adminserver => { fixed => 1 }, + login => { fixed => 1 }, + password => { optional => 1 }, + vserver => { optional => 1 }, + array => { fixed => 1 }, + nodes => { optional => 1 }, + disable => { optional => 1 }, + content => { optional => 1 }, + igroup => { optional => 1 }, + api => { optional => 1 }, + backend => { fixed => 1 }, + media => { fixed => 1 }, + } +} + +# Storage implementation + +sub parse_volname { + my ($class, $volname) = @_; + + if ($volname =~ m/vm-(\d+)-disk-\S+/) { + return ('images', $volname, $1, undef, undef, undef, 'raw'); + } else { + die "Invalid volume $volname"; + } +} + +sub filesystem_path { + my ($class, $scfg, $volname, $snapname) = @_; + + die "Direct attached device snapshot is not implemented" if defined($snapname); + + my ($vtype, $name, $vmid) = $class->parse_volname($volname); + die "Cannot find WWN for volume $volname" unless my $wwn = $class->mp_get_wwn($scfg, $name); + + my $path = "/dev/disk/by-id/dm-uuid-mpath-0x$wwn"; + + return wantarray ? ($path, $vmid, $vtype) : $path; +} + +sub create_base { + my ($class, $storeid, $scfg, $volname) = @_; + + die "Creating base image is currently unimplemented"; +} + +sub clone_image { + my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_; + + die "Cloning image is currently unimplemented"; +} + +# Seems like this method gets size in kilobytes somehow, +# while listing methost return bytes. That's strange. +sub alloc_image { + my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_; + + my $luns = $class->run_lun_command($scfg, 'list_lu', $vmid); + unless ($name) { + for (my $i = 1; $i < 100; $i++) { + if (!$luns->{"vm-$vmid-disk-$i"}) { + $name = "vm-$vmid-disk-$i"; + last; + } + } + } + + $class->run_lun_command($scfg, 'create_lu', $name, $size*1024); + $class->run_lun_command($scfg, 'map_lu', $name); #Merge it to create? + return $name; +} + +sub free_image { + my ($class, $storeid, $scfg, $volname, $isBase) = @_; + + my $wwn = $class->mp_get_wwn($scfg, $volname); + + $class->run_lun_command($scfg, 'unmap_lu', $volname); #Merge it to delete? + $class->run_lun_command($scfg, 'delete_lu', $volname); + + dir_glob_foreach('/etc/pve/nodes', '\w+', sub { + my ($node) = @_; + run_command(['/usr/bin/ssh', $node, '/sbin/rescan-scsi-bus -r -i']); + }); +} + +sub list_images { + my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_; + + my $res = []; + + my $luns = $class->run_lun_command($scfg, 'list_lu', undef); + + foreach my $name (keys %$luns) { + + my $volid = "$storeid:$name"; + + if ($vollist) { + my $found = grep { $_ eq $volid } @$vollist; + next if !$found; + } else { + next if defined($vmid); + } + + push @$res, { + 'volid' => $volid, 'format' => 'raw', 'size' => $luns->{$name}->{'size'}, + }; + + } + + return $res; +} + +sub status { + my ($class, $storeid, $scfg, $cache) = @_; + + return @{$class->run_lun_command($scfg, 'space_status')}; +} + +sub activate_storage { + my ($class, $storeid, $scfg, $cache) = @_; + + # Server's SCSI subsystem is always up, so there's nothing to do + return 1; +} + +sub deactivate_storage { + my ($class, $storeid, $scfg, $cache) = @_; + + return 1; +} + +sub activate_volume { + my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_; + + die "volume snapshot is not possible on direct-attached storage device" if $snapname; + + warn "Activating '$volname'\n"; + multipath_enable($scfg, $class->mp_get_wwn($scfg,$volname)); + + return 1; +} + +sub deactivate_volume { + my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_; + + die "volume snapshot is not possible on direct-attached storage device" if $snapname; + + warn "Deactivating '$volname'\n"; + multipath_disable($scfg, $class->mp_get_wwn($scfg,$volname)); + + return 1; +} + +sub volume_resize { + my ($class, $scfg, $storeid, $volname, $size, $running) = @_; + + $class->run_lun_command($scfg, 'delete_lu', $volname); +} + +sub volume_snapshot { + my ($class, $scfg, $storeid, $volname, $snap) = @_; + + $class->run_lun_command($scfg, 'create_snap', $volname, $snap); +} + +sub volume_snapshot_rollback { + my ($class, $scfg, $storeid, $volname, $snap) = @_; + + $class->run_lun_command($scfg, 'rollback_snap', $volname, $snap); +} + +sub volume_snapshot_delete { + my ($class, $scfg, $storeid, $volname, $snap) = @_; + + $class->run_lun_command($scfg, 'delete_snap', $volname, $snap); +} + +sub volume_has_feature { + my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_; + + my $features = { + snapshot => { current => 1, snap => 1 }, + sparseinit => { current => 1 }, + }; + + my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = + $class->parse_volname($volname); + + my $key = undef; + if($snapname) { + $key = 'snap'; + } else { + $key = $isBase ? 'base' : 'current'; + } + return 1 if $features->{$feature}->{$key}; + + return undef; +} + +1; diff --git a/PVE/Storage/Makefile b/PVE/Storage/Makefile index b924f21..c76c2f7 100644 --- a/PVE/Storage/Makefile +++ b/PVE/Storage/Makefile @@ -1,4 +1,4 @@ -SOURCES=Plugin.pm DirPlugin.pm LVMPlugin.pm NFSPlugin.pm ISCSIPlugin.pm RBDPlugin.pm SheepdogPlugin.pm ISCSIDirectPlugin.pm GlusterfsPlugin.pm ZFSPoolPlugin.pm ZFSPlugin.pm DRBDPlugin.pm LvmThinPlugin.pm +SOURCES=Plugin.pm DirPlugin.pm LVMPlugin.pm NFSPlugin.pm ISCSIPlugin.pm RBDPlugin.pm SheepdogPlugin.pm ISCSIDirectPlugin.pm GlusterfsPlugin.pm ZFSPoolPlugin.pm ZFSPlugin.pm DRBDPlugin.pm LvmThinPlugin.pm MPDirectPlugin.pm .PHONY: install install: diff --git a/PVE/Storage/Plugin.pm b/PVE/Storage/Plugin.pm index 8089302..d395267 100644 --- a/PVE/Storage/Plugin.pm +++ b/PVE/Storage/Plugin.pm @@ -325,7 +325,7 @@ sub parse_config { $d->{content} = $def->{content}->[1] if !$d->{content}; } - if ($type eq 'iscsi' || $type eq 'nfs' || $type eq 'rbd' || $type eq 'sheepdog' || $type eq 'iscsidirect' || $type eq 'glusterfs' || $type eq 'zfs' || $type eq 'drbd') { + if ($type eq 'iscsi' || $type eq 'nfs' || $type eq 'rbd' || $type eq 'sheepdog' || $type eq 'iscsidirect' || $type eq 'glusterfs' || $type eq 'zfs' || $type eq 'drbd' || $type eq 'mpdirect') { $d->{shared} = 1; } } diff --git a/control.in b/control.in index d5e2c3f..a611157 100644 --- a/control.in +++ b/control.in @@ -3,7 +3,7 @@ Version: @@VERSION@@-@@PKGRELEASE@@ Section: perl Priority: optional Architecture: @@ARCH@@ -Depends: perl (>= 5.6.0-16), nfs-common, udev, libpve-common-perl, lvm2, thin-provisioning-tools, libfile-chdir-perl, glusterfs-client (>= 3.4.0-2), cstream, libnet-dbus-perl +Depends: perl (>= 5.6.0-16), nfs-common, udev, libpve-common-perl, lvm2, thin-provisioning-tools, libfile-chdir-perl, glusterfs-client (>= 3.4.0-2), cstream, libnet-dbus-perl, libxml-simple-perl Maintainer: Proxmox Support Team <supp...@proxmox.com> Description: Proxmox VE storage management library This package contains the storage management library used by Proxmox VE. -- _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel