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

Wolfgang Bumiller w.bumiller at proxmox.com
Wed Oct 5 15:49:46 CEST 2016


On Wed, Oct 05, 2016 at 02:48:05PM +0200, Wolfgang Link wrote:
> ---
>  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');

This shouldn't be handled here. The CLIHandler has a special case for
this when read_password() exists in its derived class.
See PVE/CLI/pct.pm in pve-container as an example.

> +	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
> 
> 
> _______________________________________________
> pve-devel mailing list
> pve-devel at pve.proxmox.com
> http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel




More information about the pve-devel mailing list