[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