[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