]>
Commit | Line | Data |
---|---|---|
1dc01b9f DM |
1 | package PVE::Storage::NFSPlugin; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | use IO::File; | |
da63f588 | 6 | use Net::IP; |
d9e4e1ce | 7 | use File::Path; |
be2e0c16 | 8 | use PVE::Tools qw(run_command); |
1dc01b9f DM |
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 | ||
da63f588 | 32 | $server = "[$server]" if Net::IP::ip_is_ipv6($server); |
1dc01b9f DM |
33 | my $source = "$server:$export"; |
34 | ||
35 | $mountdata = read_proc_mounts() if !$mountdata; | |
36 | ||
da63f588 | 37 | if ($mountdata =~ m|^\Q$source\E/?\s\Q$mountpoint\E\snfs|m) { |
1dc01b9f DM |
38 | return $mountpoint; |
39 | } | |
40 | ||
41 | return undef; | |
42 | } | |
43 | ||
44 | sub nfs_mount { | |
45 | my ($server, $export, $mountpoint, $options) = @_; | |
46 | ||
da63f588 | 47 | $server = "[$server]" if Net::IP::ip_is_ipv6($server); |
1dc01b9f DM |
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 | ||
4d284721 AD |
168 | sub check_connection { |
169 | my ($class, $storeid, $scfg) = @_; | |
170 | ||
171 | my $server = $scfg->{server}; | |
4d284721 | 172 | |
86a9a680 | 173 | # test connection to portmapper |
37121146 | 174 | my $cmd = ['/usr/sbin/rpcinfo', '-p', $server]; |
86a9a680 DM |
175 | |
176 | eval { | |
177 | run_command($cmd, timeout => 2, outfunc => sub {}, errfunc => sub {}); | |
178 | }; | |
179 | if (my $err = $@) { | |
180 | return 0; | |
181 | } | |
182 | ||
183 | return 1; | |
4d284721 | 184 | } |
86a9a680 | 185 | |
1dc01b9f | 186 | 1; |