]> git.proxmox.com Git - pve-storage.git/blob - PVE/Storage/NFSPlugin.pm
add subvol support for directory storage
[pve-storage.git] / PVE / Storage / NFSPlugin.pm
1 package PVE::Storage::NFSPlugin;
2
3 use strict;
4 use warnings;
5 use IO::File;
6 use Net::IP;
7 use File::Path;
8 use PVE::Tools qw(run_command);
9 use PVE::Storage::Plugin;
10 use PVE::JSONSchema qw(get_standard_option);
11
12 use base qw(PVE::Storage::Plugin);
13
14 # NFS helper functions
15
16 sub read_proc_mounts {
17
18 local $/; # enable slurp mode
19
20 my $data = "";
21 if (my $fd = IO::File->new("/proc/mounts", "r")) {
22 $data = <$fd>;
23 close ($fd);
24 }
25
26 return $data;
27 }
28
29 sub nfs_is_mounted {
30 my ($server, $export, $mountpoint, $mountdata) = @_;
31
32 $server = "[$server]" if Net::IP::ip_is_ipv6($server);
33 my $source = "$server:$export";
34
35 $mountdata = read_proc_mounts() if !$mountdata;
36
37 if ($mountdata =~ m|^\Q$source\E/?\s\Q$mountpoint\E\snfs|m) {
38 return $mountpoint;
39 }
40
41 return undef;
42 }
43
44 sub nfs_mount {
45 my ($server, $export, $mountpoint, $options) = @_;
46
47 $server = "[$server]" if Net::IP::ip_is_ipv6($server);
48 my $source = "$server:$export";
49
50 my $cmd = ['/bin/mount', '-t', 'nfs', $source, $mountpoint];
51 if ($options) {
52 push @$cmd, '-o', $options;
53 }
54
55 run_command($cmd, errmsg => "mount error");
56 }
57
58 # Configuration
59
60 sub type {
61 return 'nfs';
62 }
63
64 sub plugindata {
65 return {
66 content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1},
67 { images => 1 }],
68 format => [ { raw => 1, qcow2 => 1, vmdk => 1 } , 'raw' ],
69 };
70 }
71
72 sub properties {
73 return {
74 export => {
75 description => "NFS export path.",
76 type => 'string', format => 'pve-storage-path',
77 },
78 server => {
79 description => "Server IP or DNS name.",
80 type => 'string', format => 'pve-storage-server',
81 },
82 options => {
83 description => "NFS mount options (see 'man nfs')",
84 type => 'string', format => 'pve-storage-options',
85 },
86 };
87 }
88
89 sub options {
90 return {
91 path => { fixed => 1 },
92 server => { fixed => 1 },
93 export => { fixed => 1 },
94 nodes => { optional => 1 },
95 disable => { optional => 1 },
96 maxfiles => { optional => 1 },
97 options => { optional => 1 },
98 content => { optional => 1 },
99 format => { optional => 1 },
100 };
101 }
102
103
104 sub check_config {
105 my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_;
106
107 $config->{path} = "/mnt/pve/$sectionId" if $create && !$config->{path};
108
109 return $class->SUPER::check_config($sectionId, $config, $create, $skipSchemaCheck);
110 }
111
112 # Storage implementation
113
114 sub status {
115 my ($class, $storeid, $scfg, $cache) = @_;
116
117 $cache->{mountdata} = read_proc_mounts() if !$cache->{mountdata};
118
119 my $path = $scfg->{path};
120 my $server = $scfg->{server};
121 my $export = $scfg->{export};
122
123 return undef if !nfs_is_mounted($server, $export, $path, $cache->{mountdata});
124
125 return $class->SUPER::status($storeid, $scfg, $cache);
126 }
127
128 sub activate_storage {
129 my ($class, $storeid, $scfg, $cache) = @_;
130
131 $cache->{mountdata} = read_proc_mounts() if !$cache->{mountdata};
132
133 my $path = $scfg->{path};
134 my $server = $scfg->{server};
135 my $export = $scfg->{export};
136
137 if (!nfs_is_mounted($server, $export, $path, $cache->{mountdata})) {
138
139 # NOTE: only call mkpath when not mounted (avoid hang
140 # when NFS server is offline
141
142 mkpath $path;
143
144 die "unable to activate storage '$storeid' - " .
145 "directory '$path' does not exist\n" if ! -d $path;
146
147 nfs_mount($server, $export, $path, $scfg->{options});
148 }
149
150 $class->SUPER::activate_storage($storeid, $scfg, $cache);
151 }
152
153 sub deactivate_storage {
154 my ($class, $storeid, $scfg, $cache) = @_;
155
156 $cache->{mountdata} = read_proc_mounts() if !$cache->{mountdata};
157
158 my $path = $scfg->{path};
159 my $server = $scfg->{server};
160 my $export = $scfg->{export};
161
162 if (nfs_is_mounted($server, $export, $path, $cache->{mountdata})) {
163 my $cmd = ['/bin/umount', $path];
164 run_command($cmd, errmsg => 'umount error');
165 }
166 }
167
168 sub check_connection {
169 my ($class, $storeid, $scfg) = @_;
170
171 my $server = $scfg->{server};
172
173 my $cmd = ['/sbin/showmount', '--no-headers', '--exports', $server];
174
175 eval {
176 run_command($cmd, timeout => 2, outfunc => sub {}, errfunc => sub {});
177 };
178 if (my $err = $@) {
179 return 0;
180 }
181
182 return 1;
183 }
184
185 1;