[pve-devel] [RFC pve-storage 1/2] add CIFSPlugin

Wolfgang Link w.link at proxmox.com
Wed Oct 5 14:48:05 CEST 2016


---
 PVE/API2/Storage/Config.pm |  75 ++++++++++++++++-
 PVE/Storage.pm             |   2 +
 PVE/Storage/CIFSPlugin.pm  | 198 +++++++++++++++++++++++++++++++++++++++++++++
 PVE/Storage/Makefile       |   2 +-
 PVE/Storage/Plugin.pm      |   2 +-
 control.in                 |   2 +-
 6 files changed, 275 insertions(+), 6 deletions(-)
 create mode 100644 PVE/Storage/CIFSPlugin.pm

diff --git a/PVE/API2/Storage/Config.pm b/PVE/API2/Storage/Config.pm
index 4668af6..f446980 100755
--- a/PVE/API2/Storage/Config.pm
+++ b/PVE/API2/Storage/Config.pm
@@ -12,6 +12,8 @@ use HTTP::Status qw(:constants);
 use Storable qw(dclone);
 use PVE::JSONSchema qw(get_standard_option);
 use PVE::RPCEnvironment;
+use File::Path;
+use Term::ReadLine;
 
 use PVE::RESTHandler;
 
@@ -36,6 +38,51 @@ my $api_storage_config = sub {
     return $scfg;
 };
 
+my $extract_credentials = sub {
+    my ($param) = @_;
+
+    my $username = extract_param($param, 'username');
+    my $password = extract_param($param, 'password');
+    my $domain = extract_param($param, 'domain');
+
+    my $tries = 3;
+    while ($username && !defined($password) && $tries > 0) {
+	my $term = new Term::ReadLine ('pvesm');
+	my $attribs = $term->Attribs;
+	$attribs->{redisplay_function} = $attribs->{shadow_redisplay};
+	my $input = $term->readline('Enter password: ');
+	my $conf = $term->readline('Retype password: ');
+	if ($input ne $conf) {
+	    print "Passwords do not match.\n";
+	    $tries--;
+	    next;
+	}
+	$password = $input;
+    }
+
+    my $cred;
+    if ($username) {
+	$cred = "username=$username\n";
+	$cred .= "password=$password\n";
+	$cred .= "domain=$domain\n" if $domain;
+    }
+
+    return $cred;
+};
+
+my $set_cifs_credentials = sub {
+    my ($user_data, $storeid) = @_;
+
+    my $cred_path = '/etc/pve/priv/cifs/';
+
+    mkpath $cred_path;
+
+    my $cred_file = $cred_path.$storeid.".cred";
+    PVE::Tools::file_set_contents($cred_file, $user_data);
+
+    return $cred_file;
+};
+
 __PACKAGE__->register_method ({
     name => 'index', 
     path => '',
@@ -127,6 +174,8 @@ __PACKAGE__->register_method ({
 	my $type = extract_param($param, 'type');
 	my $storeid = extract_param($param, 'storage');
 
+	my $user_data = &$extract_credentials($param) if $type eq 'cifs';
+
 	if ($param->{portal}) {
 	    $param->{portal} = PVE::Storage::resolv_portal($param->{portal});
 	}
@@ -164,12 +213,21 @@ __PACKAGE__->register_method ({
 		    PVE::Storage::LVMPlugin::lvm_create_volume_group($path, $opts->{vgname}, $opts->{shared});
 		}
 
+		#create a password user file in /etc/pve/priv so no normal user can read it.
+		my $cred_file = &$set_cifs_credentials($user_data, $storeid)
+		    if defined($user_data);
+
 		# try to activate if enabled on local node,
 		# we only do this to detect errors/problems sooner
-		if (PVE::Storage::storage_check_enabled($cfg, $storeid, undef, 1)) {
-		    PVE::Storage::activate_storage($cfg, $storeid);
+		eval {
+		    if (PVE::Storage::storage_check_enabled($cfg, $storeid, undef, 1)) {
+			PVE::Storage::activate_storage($cfg, $storeid);
+		    }
+		};
+		if(my $err = $@) {
+		    unlink $cred_file if defined($cred_file);
+		    die $err;
 		}
-
 		PVE::Storage::write_config($cfg);
 	    
 	    }, "create storage failed");
@@ -199,6 +257,12 @@ __PACKAGE__->register_method ({
 
 	    my $cfg = PVE::Storage::config();
 
+	    my $type = $cfg->{ids}->{$storeid}->{type};
+	    my $user_data;
+	    $user_data = &$extract_credentials($param) if $type eq 'cifs';
+
+	    &$set_cifs_credentials($user_data, $storeid) if defined($user_data);
+
 	    PVE::SectionConfig::assert_if_modified($cfg, $digest);
 
 	    my $scfg = PVE::Storage::storage_config($cfg, $storeid);
@@ -251,6 +315,11 @@ __PACKAGE__->register_method ({
 		die "can't remove storage - storage is used as base of another storage\n"
 		    if PVE::Storage::storage_is_used($cfg, $storeid);
 
+		my $cred_file = '/etc/pve/priv/cifs/'.$storeid.'.cred';
+
+		unlink $cred_file if ($cfg->{ids}->{$storeid}->{type} eq 'cifs') &&
+		    (-e $cred_file);
+
 		delete $cfg->{ids}->{$storeid};
 
 		PVE::Storage::write_config($cfg);
diff --git a/PVE/Storage.pm b/PVE/Storage.pm
index a904f4e..978a2fe 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::CIFSPlugin;
 
 # Storage API version. Icrement it on changes in storage API interface.
 use constant APIVER => 1;
@@ -49,6 +50,7 @@ PVE::Storage::GlusterfsPlugin->register();
 PVE::Storage::ZFSPoolPlugin->register();
 PVE::Storage::ZFSPlugin->register();
 PVE::Storage::DRBDPlugin->register();
+PVE::Storage::CIFSPlugin->register();
 
 # load third-party plugins
 if ( -d '/usr/share/perl5/PVE/Storage/Custom' ) {
diff --git a/PVE/Storage/CIFSPlugin.pm b/PVE/Storage/CIFSPlugin.pm
new file mode 100644
index 0000000..f1a08b0
--- /dev/null
+++ b/PVE/Storage/CIFSPlugin.pm
@@ -0,0 +1,198 @@
+package PVE::Storage::CIFSPlugin;
+
+use strict;
+use warnings;
+use Net::IP;
+use PVE::Tools qw(run_command);
+use PVE::ProcFSTools;
+use File::Path;
+use PVE::Storage::Plugin;
+use PVE::JSONSchema qw(get_standard_option);
+
+use base qw(PVE::Storage::Plugin);
+
+# CIFS helper functions
+
+sub cifs_is_mounted {
+    my ($server, $share, $mountpoint, $mountdata) = @_;
+
+    $server = "[$server]" if Net::IP::ip_is_ipv6($server);
+    my $source = "//${server}/$share";
+    $mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
+
+    return $mountpoint if grep {
+	$_->[2] =~ /^cifs/ &&
+	$_->[0] =~ m|^\Q$source\E/?$| &&
+	$_->[1] eq $mountpoint
+    } @$mountdata;
+    return undef;
+}
+
+sub get_cred_file {
+    my ($storeid) = @_;
+
+    my $cred_file = '/etc/pve/priv/cifs/'.$storeid.'.cred';
+
+    if (-e $cred_file) {
+	return $cred_file;
+    }
+    return undef;
+}
+
+sub cifs_mount {
+    my ($server, $share, $mountpoint, $storeid) = @_;
+
+    $server = "[$server]" if Net::IP::ip_is_ipv6($server);
+    my $source = "//${server}/$share";
+
+    my $cmd = ['/bin/mount', '-t', 'cifs', $source, $mountpoint, '-o'];
+
+    if (my $cred_file = get_cred_file($storeid)) {
+	push @$cmd, "credentials=$cred_file";
+    } else {
+	push @$cmd, 'guest,username=guest';
+    }
+
+    run_command($cmd, errmsg => "mount error");
+}
+
+# Configuration
+
+sub type {
+    return 'cifs';
+}
+
+sub plugindata {
+    return {
+	content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1},
+		     { images => 1 }],
+	format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ],
+    };
+}
+
+sub properties {
+    return {
+	share => {
+	    description => "CIFS share.",
+	    type => 'string',
+	},
+	password => {
+	    description => "Password for CIFS share.",
+	    type => 'string',
+	},
+	domain => {
+	    description => "CIFS domain.",
+	    type => 'string',
+	},
+    };
+}
+
+sub options {
+    return {
+	path => { fixed => 1 },
+	server => { fixed => 1 },
+	share => { fixed => 1 },
+	nodes => { optional => 1 },
+	disable => { optional => 1 },
+	maxfiles => { optional => 1 },
+	content => { optional => 1 },
+	format => { optional => 1 },
+	username => { optional => 1 },
+	password => { optional => 1},
+	domain => { optional => 1},
+    };
+}
+
+
+sub check_config {
+    my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_;
+
+    $config->{path} = "/mnt/pve/$sectionId" if $create && !$config->{path};
+
+    return $class->SUPER::check_config($sectionId, $config, $create, $skipSchemaCheck);
+}
+
+# Storage implementation
+
+sub status {
+    my ($class, $storeid, $scfg, $cache) = @_;
+
+    $cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
+	if !$cache->{mountdata};
+
+    my $path = $scfg->{path};
+    my $server = $scfg->{server};
+    my $share = $scfg->{share};
+
+    return undef if !cifs_is_mounted($server, $share, $path, $cache->{mountdata}); 
+
+    return $class->SUPER::status($storeid, $scfg, $cache);
+}
+
+sub activate_storage {
+    my ($class, $storeid, $scfg, $cache) = @_;
+
+    $cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
+	if !$cache->{mountdata};
+
+    my $path = $scfg->{path};
+    my $server = $scfg->{server};
+    my $share = $scfg->{share};
+
+    if (!cifs_is_mounted($server, $share, $path, $cache->{mountdata})) {
+
+	mkpath $path;
+
+	die "unable to activate storage '$storeid' - " .
+	    "directory '$path' does not exist\n" if ! -d $path;
+
+	cifs_mount($server, $share, $path, $storeid);
+    }
+
+    $class->SUPER::activate_storage($storeid, $scfg, $cache);
+}
+
+sub deactivate_storage {
+    my ($class, $storeid, $scfg, $cache) = @_;
+
+    $cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
+	if !$cache->{mountdata};
+
+    my $path = $scfg->{path};
+    my $server = $scfg->{server};
+    my $share = $scfg->{share};
+
+    if (cifs_is_mounted($server, $share, $path, $cache->{mountdata})) {
+	my $cmd = ['/bin/umount', $path];
+	run_command($cmd, errmsg => 'umount error');
+    }
+}
+
+sub check_connection {
+    my ($class, $storeid, $scfg) = @_;
+
+    my $server = $scfg->{server};
+
+    my $cmd = ['/usr/bin/smbclient' , '-L', $server];
+
+    if (my $cred_file = get_cred_file($storeid)) {
+	push @$cmd, '-A', $cred_file;
+    } else {
+	push @$cmd, '-U', 'Guest','-N';
+    }
+
+    my $out_str;
+    eval {
+	run_command($cmd, timeout => 2, outfunc => sub {$out_str .= shift;},
+		    errfunc => sub {});
+    };
+
+    if (my $err = $@) {
+	die "$out_str\n" if ($out_str =~ m/NT_STATUS_ACCESS_DENIED$/);
+	return 0;
+    }
+
+    return 1;
+}
+
+1;
diff --git a/PVE/Storage/Makefile b/PVE/Storage/Makefile
index b924f21..4216869 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 CIFSPlugin.pm
 
 .PHONY: install
 install:
diff --git a/PVE/Storage/Plugin.pm b/PVE/Storage/Plugin.pm
index 6e73547..b6023d9 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 'cifs') {
 	    $d->{shared} = 1;
 	}
     }
diff --git a/control.in b/control.in
index 07cce0b..c1393cc 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, smartmontools
+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, smartmontools, cifs-utils, smbclient
 Maintainer: Proxmox Support Team <support at proxmox.com>
 Description: Proxmox VE storage management library
  This package contains the storage management library used by Proxmox VE.
-- 
2.1.4





More information about the pve-devel mailing list