|
1 # vim: tabstop=4 shiftwidth=4 softtabstop=4 |
|
2 # Copyright (c) 2012 OpenStack LLC. |
|
3 # All Rights Reserved. |
|
4 # |
|
5 # Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved. |
|
6 # |
|
7 # Licensed under the Apache License, Version 2.0 (the "License"); you may |
|
8 # not use this file except in compliance with the License. You may obtain |
|
9 # a copy of the License at |
|
10 # |
|
11 # http://www.apache.org/licenses/LICENSE-2.0 |
|
12 # |
|
13 # Unless required by applicable law or agreed to in writing, software |
|
14 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|
15 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|
16 # License for the specific language governing permissions and limitations |
|
17 # under the License. |
|
18 """ |
|
19 Volume driver for Solaris ZFS NFS storage |
|
20 """ |
|
21 |
|
22 import os |
|
23 |
|
24 from oslo_config import cfg |
|
25 from oslo_log import log as logging |
|
26 from oslo_utils import units |
|
27 |
|
28 from cinder import exception |
|
29 from cinder.i18n import _ |
|
30 from cinder.volume.drivers import nfs |
|
31 |
|
32 ZFS_NFS_VERSION = '1.0.0' |
|
33 |
|
34 LOG = logging.getLogger(__name__) |
|
35 |
|
36 solaris_zfs_nfs_opts = [ |
|
37 cfg.BoolOpt('nfs_round_robin', |
|
38 default=True, |
|
39 help=('Schedule volumes round robin across NFS shares.')), |
|
40 ] |
|
41 |
|
42 CONF = cfg.CONF |
|
43 CONF.register_opts(solaris_zfs_nfs_opts) |
|
44 |
|
45 |
|
46 class ZfsNfsVolumeDriver(nfs.NfsDriver): |
|
47 """Local ZFS NFS volume operations.""" |
|
48 |
|
49 driver_volume_type = 'nfs' |
|
50 driver_prefix = 'nfs' |
|
51 volume_backend_name = 'Solaris_NFS' |
|
52 |
|
53 def __init__(self, *args, **kwargs): |
|
54 super(ZfsNfsVolumeDriver, self).__init__(*args, **kwargs) |
|
55 self.configuration.append_config_values(solaris_zfs_nfs_opts) |
|
56 |
|
57 self.last_rr_pos = None |
|
58 |
|
59 if self.configuration.nfs_mount_options: |
|
60 LOG.warning(_("Solaris NFS driver ignores mount options")) |
|
61 |
|
62 def _update_volume_stats(self): |
|
63 """Retrieve volume status info.""" |
|
64 |
|
65 stats = {} |
|
66 backend_name = self.configuration.safe_get('volume_backend_name') |
|
67 stats["volume_backend_name"] = backend_name or self.__class__.__name__ |
|
68 stats["driver_version"] = ZFS_NFS_VERSION |
|
69 stats["vendor_name"] = 'Oracle' |
|
70 stats['storage_protocol'] = self.driver_volume_type |
|
71 |
|
72 self._ensure_shares_mounted() |
|
73 |
|
74 global_capacity = 0 |
|
75 global_free = 0 |
|
76 for share in self._mounted_shares: |
|
77 capacity, free, used = self._get_capacity_info(share) |
|
78 global_capacity += capacity |
|
79 global_free += free |
|
80 |
|
81 stats['total_capacity_gb'] = global_capacity / float(units.Gi) |
|
82 stats['free_capacity_gb'] = global_free / float(units.Gi) |
|
83 stats['reserved_percentage'] = 0 |
|
84 stats['QoS_support'] = False |
|
85 self._stats = stats |
|
86 |
|
87 def _create_sparsed_file(self, path, size): |
|
88 """Creates a sparse file of a given size in GiB.""" |
|
89 self._execute('/usr/bin/truncate', '-s', '%sG' % size, path) |
|
90 |
|
91 def _create_regular_file(self, path, size): |
|
92 """Creates a regular file of given size in GiB.""" |
|
93 |
|
94 block_size_mb = 1 |
|
95 block_count = size * units.Gi / (block_size_mb * units.Mi) |
|
96 |
|
97 self._execute('/usr/bin/dd', 'if=/dev/zero', 'of=%s' % path, |
|
98 'bs=%dM' % block_size_mb, |
|
99 'count=%d' % block_count) |
|
100 |
|
101 def _set_rw_permissions(self, path): |
|
102 """Sets access permissions for given NFS path. |
|
103 |
|
104 :param path: the volume file path. |
|
105 """ |
|
106 os.chmod(path, 0o660) |
|
107 |
|
108 def _set_rw_permissions_for_all(self, path): |
|
109 """Sets 666 permissions for the path.""" |
|
110 mode = os.stat(path).st_mode |
|
111 os.chmod(path, mode | 0o666) |
|
112 |
|
113 def _set_rw_permissions_for_owner(self, path): |
|
114 """Sets read-write permissions to the owner for the path.""" |
|
115 mode = os.stat(path).st_mode |
|
116 os.chmod(path, mode | 0o600) |
|
117 |
|
118 def _delete(self, path): |
|
119 os.unlink(path) |
|
120 |
|
121 def _get_capacity_info(self, nfs_share): |
|
122 """Calculate available space on the NFS share. |
|
123 |
|
124 :param nfs_share: example 172.18.194.100:/var/nfs |
|
125 """ |
|
126 |
|
127 mount_point = self._get_mount_point_for_share(nfs_share) |
|
128 |
|
129 st = os.statvfs(mount_point) |
|
130 total_available = st.f_frsize * st.f_bavail |
|
131 total_size = st.f_frsize * st.f_blocks |
|
132 |
|
133 du, _ = self._execute('/usr/bin/gdu', '-sb', '--apparent-size', |
|
134 '--exclude', '*snapshot*', mount_point) |
|
135 total_allocated = float(du.split()[0]) |
|
136 return total_size, total_available, total_allocated |
|
137 |
|
138 def _round_robin(self, sharelist): |
|
139 """ |
|
140 Implement a round robin generator for share list |
|
141 """ |
|
142 |
|
143 mylen = len(sharelist) |
|
144 |
|
145 if self.last_rr_pos is None: |
|
146 start_pos = 0 |
|
147 else: |
|
148 start_pos = (self.last_rr_pos + 1) % mylen |
|
149 |
|
150 pos = start_pos |
|
151 while True: |
|
152 yield sharelist[pos], pos |
|
153 pos = (pos + 1) % mylen |
|
154 if pos == start_pos: |
|
155 break |
|
156 |
|
157 def _find_share(self, volume_size_in_gib): |
|
158 """Choose NFS share among available ones for given volume size. |
|
159 |
|
160 For instances with more than one share that meets the criteria, the |
|
161 share with the least "allocated" space will be selected. |
|
162 |
|
163 :param volume_size_in_gib: int size in GB |
|
164 """ |
|
165 |
|
166 if not self._mounted_shares: |
|
167 raise exception.NfsNoSharesMounted() |
|
168 |
|
169 target_share = None |
|
170 if self.configuration.nfs_round_robin: |
|
171 # Round Robin volume placement on shares |
|
172 |
|
173 LOG.debug(_("_find_share using round robin")) |
|
174 |
|
175 for nfs_share, pos in self._round_robin(self._mounted_shares): |
|
176 if not self._is_share_eligible(nfs_share, volume_size_in_gib): |
|
177 continue |
|
178 target_share = nfs_share |
|
179 self.last_rr_pos = pos |
|
180 break |
|
181 else: |
|
182 # Place volume on share with the most free space. |
|
183 |
|
184 LOG.debug(_("_find_share using select most free")) |
|
185 |
|
186 target_share_reserved = 0 |
|
187 |
|
188 for nfs_share in self._mounted_shares: |
|
189 if not self._is_share_eligible(nfs_share, volume_size_in_gib): |
|
190 continue |
|
191 total_size, total_available, total_allocated = \ |
|
192 self._get_capacity_info(nfs_share) |
|
193 if target_share is not None: |
|
194 if target_share_reserved > total_allocated: |
|
195 target_share = nfs_share |
|
196 target_share_reserved = total_allocated |
|
197 else: |
|
198 target_share = nfs_share |
|
199 target_share_reserved = total_allocated |
|
200 |
|
201 if target_share is None: |
|
202 raise exception.NfsNoSuitableShareFound( |
|
203 volume_size=volume_size_in_gib) |
|
204 |
|
205 LOG.debug('Selected %s as target nfs share.', target_share) |
|
206 |
|
207 return target_share |
|
208 |
|
209 def set_nas_security_options(self, is_new_cinder_install): |
|
210 """Secure NAS options. |
|
211 |
|
212 For Solaris we always operate in a secure mode and do not |
|
213 rely on root or any rootwrap utilities. |
|
214 |
|
215 With RBAC we can do what we need as the cinder user. We |
|
216 set the nas_secure_file.XXX to be true by default. We ignore |
|
217 any conf file setting for these string vars. |
|
218 |
|
219 We don't ever use these nas_secure_file_XXX vars in this driver |
|
220 but we still set the value to true. This might prevent admin/users |
|
221 from opening bugs stating we are not running in a secure mode. |
|
222 """ |
|
223 |
|
224 self.configuration.nas_secure_file_operations = 'true' |
|
225 self.configuration.nas_secure_file_permissions = 'true' |
|
226 self._execute_as_root = False |
|
227 |
|
228 LOG.debug('NAS variable secure_file_permissions setting is: %s' % |
|
229 self.configuration.nas_secure_file_permissions) |
|
230 |
|
231 LOG.debug('NAS variable secure_file_operations setting is: %s' % |
|
232 self.configuration.nas_secure_file_operations) |