|
1 # Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. |
|
2 # |
|
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may |
|
4 # not use this file except in compliance with the License. You may obtain |
|
5 # a copy of the License at |
|
6 # |
|
7 # http://www.apache.org/licenses/LICENSE-2.0 |
|
8 # |
|
9 # Unless required by applicable law or agreed to in writing, software |
|
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|
12 # License for the specific language governing permissions and limitations |
|
13 # under the License. |
|
14 """ |
|
15 ZFS Storage Appliance Cinder Volume Driver |
|
16 """ |
|
17 import base64 |
|
18 |
|
19 from cinder import exception |
|
20 from cinder.openstack.common import log |
|
21 from cinder.volume import driver |
|
22 from oslo.config import cfg |
|
23 |
|
24 from cinder.volume.drivers.zfssa import zfssarest |
|
25 |
|
26 |
|
27 CONF = cfg.CONF |
|
28 LOG = log.getLogger(__name__) |
|
29 |
|
30 ZFSSA_OPTS = [ |
|
31 cfg.StrOpt('zfssa_host', required=True, |
|
32 help='ZFSSA management IP address'), |
|
33 cfg.StrOpt('zfssa_auth_user', required=True, secret=True, |
|
34 help='ZFSSA management authorized user\'s name'), |
|
35 cfg.StrOpt('zfssa_auth_password', required=True, secret=True, |
|
36 help='ZFSSA management authorized user\'s password'), |
|
37 cfg.StrOpt('zfssa_pool', required=True, |
|
38 help='ZFSSA storage pool name'), |
|
39 cfg.StrOpt('zfssa_project', required=True, |
|
40 help='ZFSSA project name'), |
|
41 cfg.StrOpt('zfssa_lun_volblocksize', default='8k', |
|
42 help='Block size: 512, 1k, 2k, 4k, 8k, 16k, 32k, 64k, 128k'), |
|
43 cfg.BoolOpt('zfssa_lun_sparse', default=False, |
|
44 help='Flag to enable sparse (thin-provisioned): True, False'), |
|
45 cfg.StrOpt('zfssa_lun_compression', default='', |
|
46 help='Data compression-off, lzjb, gzip-2, gzip, gzip-9'), |
|
47 cfg.StrOpt('zfssa_lun_logbias', default='', |
|
48 help='Synchronous write bias-latency, throughput'), |
|
49 cfg.StrOpt('zfssa_initiator_group', default='', |
|
50 help='iSCSI initiator group'), |
|
51 cfg.StrOpt('zfssa_initiator', default='', |
|
52 help='iSCSI initiator IQNs (comma separated)'), |
|
53 cfg.StrOpt('zfssa_initiator_user', default='', |
|
54 help='iSCSI initiator CHAP user'), |
|
55 cfg.StrOpt('zfssa_initiator_password', default='', |
|
56 help='iSCSI initiator CHAP password'), |
|
57 cfg.StrOpt('zfssa_target_group', default='tgt-grp', |
|
58 help='iSCSI target group name'), |
|
59 cfg.StrOpt('zfssa_target_user', default='', |
|
60 help='iSCSI target CHAP user'), |
|
61 cfg.StrOpt('zfssa_target_password', default='', |
|
62 help='iSCSI target CHAP password'), |
|
63 cfg.StrOpt('zfssa_target_portal', required=True, |
|
64 help='iSCSI target portal (Data-IP:Port, w.x.y.z:3260)'), |
|
65 cfg.StrOpt('zfssa_target_interfaces', required=True, |
|
66 help='Network interfaces of iSCSI targets (comma separated)') |
|
67 ] |
|
68 |
|
69 CONF.register_opts(ZFSSA_OPTS) |
|
70 |
|
71 SIZE_GB = 1073741824 |
|
72 |
|
73 |
|
74 #pylint: disable=R0904 |
|
75 class ZFSSAISCSIDriver(driver.ISCSIDriver): |
|
76 """ZFSSA Cinder volume driver""" |
|
77 |
|
78 VERSION = '1.0.0' |
|
79 protocol = 'iSCSI' |
|
80 |
|
81 def __init__(self, *args, **kwargs): |
|
82 super(ZFSSAISCSIDriver, self).__init__(*args, **kwargs) |
|
83 self.configuration.append_config_values(ZFSSA_OPTS) |
|
84 self.zfssa = None |
|
85 self._stats = None |
|
86 |
|
87 def _get_target_alias(self): |
|
88 """return target alias""" |
|
89 return self.configuration.zfssa_target_group |
|
90 |
|
91 def do_setup(self, context): |
|
92 """Setup - create project, initiators, initiatorgroup, target, |
|
93 targetgroup |
|
94 """ |
|
95 self.configuration._check_required_opts() |
|
96 lcfg = self.configuration |
|
97 |
|
98 LOG.info('Connecting to host: %s' % lcfg.zfssa_host) |
|
99 self.zfssa = zfssarest.ZFSSAApi(lcfg.zfssa_host) |
|
100 auth_str = base64.encodestring('%s:%s' % |
|
101 (lcfg.zfssa_auth_user, |
|
102 lcfg.zfssa_auth_password))[:-1] |
|
103 self.zfssa.login(auth_str) |
|
104 self.zfssa.create_project(lcfg.zfssa_pool, lcfg.zfssa_project, |
|
105 compression=lcfg.zfssa_lun_compression, |
|
106 logbias=lcfg.zfssa_lun_logbias) |
|
107 |
|
108 if (lcfg.zfssa_initiator != '' and |
|
109 (lcfg.zfssa_initiator_group == '' or |
|
110 lcfg.zfssa_initiator_group == 'default')): |
|
111 LOG.warning('zfssa_initiator= %s wont be used on \ |
|
112 zfssa_initiator_group= %s' % |
|
113 (lcfg.zfssa_initiator, |
|
114 lcfg.zfssa_initiator_group)) |
|
115 |
|
116 # Setup initiator and initiator group |
|
117 if lcfg.zfssa_initiator != '' and \ |
|
118 lcfg.zfssa_initiator_group != '' and \ |
|
119 lcfg.zfssa_initiator_group != 'default': |
|
120 for initiator in lcfg.zfssa_initiator.split(','): |
|
121 self.zfssa.create_initiator(initiator, |
|
122 lcfg.zfssa_initiator_group + '-' + |
|
123 initiator, |
|
124 chapuser= |
|
125 lcfg.zfssa_initiator_user, |
|
126 chapsecret= |
|
127 lcfg.zfssa_initiator_password) |
|
128 self.zfssa.add_to_initiatorgroup(initiator, |
|
129 lcfg.zfssa_initiator_group) |
|
130 # Parse interfaces |
|
131 interfaces = [] |
|
132 for interface in lcfg.zfssa_target_interfaces.split(','): |
|
133 if interface == '': |
|
134 continue |
|
135 interfaces.append(interface) |
|
136 |
|
137 # Setup target and target group |
|
138 iqn = self.zfssa.create_target( |
|
139 self._get_target_alias(), |
|
140 interfaces, |
|
141 tchapuser=lcfg.zfssa_target_user, |
|
142 tchapsecret=lcfg.zfssa_target_password) |
|
143 |
|
144 self.zfssa.add_to_targetgroup(iqn, lcfg.zfssa_target_group) |
|
145 |
|
146 def check_for_setup_error(self): |
|
147 """Check that driver can login and pool, project, initiators, |
|
148 initiatorgroup, target, targetgroup exist |
|
149 """ |
|
150 lcfg = self.configuration |
|
151 |
|
152 self.zfssa.verify_pool(lcfg.zfssa_pool) |
|
153 self.zfssa.verify_project(lcfg.zfssa_pool, lcfg.zfssa_project) |
|
154 |
|
155 if lcfg.zfssa_initiator != '' and \ |
|
156 lcfg.zfssa_initiator_group != '' and \ |
|
157 lcfg.zfssa_initiator_group != 'default': |
|
158 for initiator in lcfg.zfssa_initiator.split(','): |
|
159 self.zfssa.verify_initiator(initiator) |
|
160 |
|
161 self.zfssa.verify_target(self._get_target_alias()) |
|
162 |
|
163 def _get_provider_info(self, volume): |
|
164 """return provider information""" |
|
165 lcfg = self.configuration |
|
166 lun = self.zfssa.get_lun(lcfg.zfssa_pool, |
|
167 lcfg.zfssa_project, volume['name']) |
|
168 iqn = self.zfssa.get_target(self._get_target_alias()) |
|
169 loc = "%s %s %s" % (lcfg.zfssa_target_portal, iqn, lun['number']) |
|
170 LOG.debug('_export_volume: provider_location: %s' % loc) |
|
171 provider = {'provider_location': loc} |
|
172 if lcfg.zfssa_target_user != '' and lcfg.zfssa_target_password != '': |
|
173 provider['provider_auth'] = 'CHAP %s %s' % \ |
|
174 (lcfg.zfssa_target_user, |
|
175 lcfg.zfssa_target_password) |
|
176 return provider |
|
177 |
|
178 def create_volume(self, volume): |
|
179 """Create a volume on ZFSSA""" |
|
180 LOG.debug('zfssa.create_volume: volume=' + volume['name']) |
|
181 lcfg = self.configuration |
|
182 volsize = str(volume['size']) + 'g' |
|
183 self.zfssa.create_lun(lcfg.zfssa_pool, |
|
184 lcfg.zfssa_project, |
|
185 volume['name'], |
|
186 volsize, |
|
187 targetgroup=lcfg.zfssa_target_group, |
|
188 volblocksize=lcfg.zfssa_lun_volblocksize, |
|
189 sparse=lcfg.zfssa_lun_sparse, |
|
190 compression=lcfg.zfssa_lun_compression, |
|
191 logbias=lcfg.zfssa_lun_logbias) |
|
192 |
|
193 return self._get_provider_info(volume) |
|
194 |
|
195 def delete_volume(self, volume): |
|
196 """Deletes a volume with the given volume['name'].""" |
|
197 LOG.debug('zfssa.delete_volume: name=' + volume['name']) |
|
198 lcfg = self.configuration |
|
199 lun2del = self.zfssa.get_lun(lcfg.zfssa_pool, |
|
200 lcfg.zfssa_project, |
|
201 volume['name']) |
|
202 """Delete clone's temp snapshot. see create_cloned_volume()""" |
|
203 """clone is deleted as part of the snapshot delete.""" |
|
204 tmpsnap = 'tmp-snapshot-%s' % volume['id'] |
|
205 if 'origin' in lun2del and lun2del['origin']['snapshot'] == tmpsnap: |
|
206 self.zfssa.delete_snapshot(lcfg.zfssa_pool, |
|
207 lcfg.zfssa_project, |
|
208 lun2del['origin']['share'], |
|
209 lun2del['origin']['snapshot']) |
|
210 return |
|
211 |
|
212 self.zfssa.delete_lun(pool=lcfg.zfssa_pool, |
|
213 project=lcfg.zfssa_project, |
|
214 lun=volume['name']) |
|
215 |
|
216 def create_snapshot(self, snapshot): |
|
217 """Creates a snapshot with the given snapshot['name'] of the |
|
218 snapshot['volume_name'] |
|
219 """ |
|
220 LOG.debug('zfssa.create_snapshot: snapshot=' + snapshot['name']) |
|
221 lcfg = self.configuration |
|
222 self.zfssa.create_snapshot(lcfg.zfssa_pool, |
|
223 lcfg.zfssa_project, |
|
224 snapshot['volume_name'], |
|
225 snapshot['name']) |
|
226 |
|
227 def delete_snapshot(self, snapshot): |
|
228 """Deletes a snapshot.""" |
|
229 LOG.debug('zfssa.delete_snapshot: snapshot=' + snapshot['name']) |
|
230 lcfg = self.configuration |
|
231 has_clones = self.zfssa.has_clones(lcfg.zfssa_pool, |
|
232 lcfg.zfssa_project, |
|
233 snapshot['volume_name'], |
|
234 snapshot['name']) |
|
235 if has_clones: |
|
236 LOG.error('snapshot %s: has clones' % snapshot['name']) |
|
237 raise exception.SnapshotIsBusy(snapshot_name=snapshot['name']) |
|
238 |
|
239 self.zfssa.delete_snapshot(lcfg.zfssa_pool, |
|
240 lcfg.zfssa_project, |
|
241 snapshot['volume_name'], |
|
242 snapshot['name']) |
|
243 |
|
244 def create_volume_from_snapshot(self, volume, snapshot): |
|
245 """Creates a volume from a snapshot - clone a snapshot""" |
|
246 LOG.debug('zfssa.create_volume_from_snapshot: volume=' + |
|
247 volume['name']) |
|
248 LOG.debug('zfssa.create_volume_from_snapshot: snapshot=' + |
|
249 snapshot['name']) |
|
250 if not self._verify_clone_size(snapshot, volume['size'] * SIZE_GB): |
|
251 exception_msg = (_('Error verifying clone size on ' |
|
252 'Volume clone: %(clone)s ' |
|
253 'Size: %(size)d on' |
|
254 'Snapshot: %(snapshot)s') |
|
255 % {'clone': volume['name'], |
|
256 'size': volume['size'], |
|
257 'snapshot': snapshot['name']}) |
|
258 LOG.error(exception_msg) |
|
259 raise exception.InvalidInput(reason=exception_msg) |
|
260 |
|
261 lcfg = self.configuration |
|
262 self.zfssa.clone_snapshot(lcfg.zfssa_pool, |
|
263 lcfg.zfssa_project, |
|
264 snapshot['volume_name'], |
|
265 snapshot['name'], |
|
266 volume['name']) |
|
267 |
|
268 def _update_volume_status(self): |
|
269 """Retrieve status info from volume group.""" |
|
270 LOG.debug("Updating volume status") |
|
271 self._stats = None |
|
272 data = {} |
|
273 data["volume_backend_name"] = self.__class__.__name__ |
|
274 data["vendor_name"] = 'Oracle' |
|
275 data["driver_version"] = self.VERSION |
|
276 data["storage_protocol"] = self.protocol |
|
277 |
|
278 lcfg = self.configuration |
|
279 (avail, used) = self.zfssa.get_pool_stats(lcfg.zfssa_pool) |
|
280 if avail is None or used is None: |
|
281 return |
|
282 total = int(avail) + int(used) |
|
283 |
|
284 if lcfg.zfssa_lun_sparse: |
|
285 data['total_capacity_gb'] = 'infinite' |
|
286 else: |
|
287 data['total_capacity_gb'] = total / SIZE_GB |
|
288 data['free_capacity_gb'] = int(avail) / SIZE_GB |
|
289 data['reserved_percentage'] = 0 |
|
290 data['QoS_support'] = False |
|
291 self._stats = data |
|
292 |
|
293 def get_volume_stats(self, refresh=False): |
|
294 """Get volume status. |
|
295 If 'refresh' is True, run update the stats first. |
|
296 """ |
|
297 if refresh: |
|
298 self._update_volume_status() |
|
299 return self._stats |
|
300 |
|
301 def _export_volume(self, volume): |
|
302 """Export the volume - set the initiatorgroup property.""" |
|
303 LOG.debug('_export_volume: volume name: %s' % volume['name']) |
|
304 lcfg = self.configuration |
|
305 |
|
306 self.zfssa.set_lun_initiatorgroup(lcfg.zfssa_pool, |
|
307 lcfg.zfssa_project, |
|
308 volume['name'], |
|
309 lcfg.zfssa_initiator_group) |
|
310 return self._get_provider_info(volume) |
|
311 |
|
312 def create_export(self, context, volume): |
|
313 """Driver entry point to get the export info for a new volume.""" |
|
314 LOG.debug('create_export: volume name: %s' % volume['name']) |
|
315 return self._export_volume(volume) |
|
316 |
|
317 def remove_export(self, context, volume): |
|
318 """Driver entry point to remove an export for a volume.""" |
|
319 LOG.debug('remove_export: volume name: %s' % volume['name']) |
|
320 lcfg = self.configuration |
|
321 self.zfssa.set_lun_initiatorgroup(lcfg.zfssa_pool, |
|
322 lcfg.zfssa_project, |
|
323 volume['name'], |
|
324 '') |
|
325 |
|
326 def ensure_export(self, context, volume): |
|
327 """Driver entry point to get the export info for an existing volume.""" |
|
328 LOG.debug('ensure_export: volume name: %s' % volume['name']) |
|
329 return self._export_volume(volume) |
|
330 |
|
331 def copy_image_to_volume(self, context, volume, image_service, image_id): |
|
332 self.ensure_export(context, volume) |
|
333 super(ZFSSAISCSIDriver, self).copy_image_to_volume( |
|
334 context, volume, image_service, image_id) |
|
335 |
|
336 def extend_volume(self, volume, new_size): |
|
337 """Driver entry point to extent volume size.""" |
|
338 LOG.debug('extend_volume: volume name: %s' % volume['name']) |
|
339 lcfg = self.configuration |
|
340 self.zfssa.set_lun_size(lcfg.zfssa_pool, |
|
341 lcfg.zfssa_project, |
|
342 volume['name'], |
|
343 new_size * SIZE_GB) |
|
344 |
|
345 def _get_iscsi_properties(self, volume): |
|
346 lcfg = self.configuration |
|
347 lun = self.zfssa.get_lun(lcfg.zfssa_pool, |
|
348 lcfg.zfssa_project, |
|
349 volume['name']) |
|
350 iqn = self.zfssa.get_target(self._get_target_alias()) |
|
351 |
|
352 return {'target_discovered': True, |
|
353 'target_iqn': iqn, |
|
354 'target_portal': lcfg.zfssa_target_portal, |
|
355 'volume_id': lun['number'], |
|
356 'access_mode': 'rw'} |
|
357 |
|
358 def create_cloned_volume(self, volume, src_vref): |
|
359 """Create a clone of the specified volume.""" |
|
360 zfssa_snapshot = {'volume_name': src_vref['name'], |
|
361 'name': 'tmp-snapshot-%s' % volume['id']} |
|
362 self.create_snapshot(zfssa_snapshot) |
|
363 try: |
|
364 self.create_volume_from_snapshot(volume, zfssa_snapshot) |
|
365 except exception.VolumeBackendAPIException: |
|
366 LOG.error("Clone Volume '%s' failed from source volume '%s'" |
|
367 % (volume['name'], src_vref['name'])) |
|
368 # Cleanup snapshot |
|
369 self.delete_snapshot(zfssa_snapshot) |
|
370 |
|
371 def local_path(self, volume): |
|
372 """Not implemented""" |
|
373 pass |
|
374 |
|
375 def backup_volume(self, context, backup, backup_service): |
|
376 """Not implemented""" |
|
377 pass |
|
378 |
|
379 def restore_backup(self, context, backup, volume, backup_service): |
|
380 """Not implemented""" |
|
381 pass |
|
382 |
|
383 def _verify_clone_size(self, snapshot, size): |
|
384 """Check whether the clone size is the same as the parent volume""" |
|
385 lcfg = self.configuration |
|
386 lun = self.zfssa.get_lun(lcfg.zfssa_pool, |
|
387 lcfg.zfssa_project, |
|
388 snapshot['volume_name']) |
|
389 return (lun['size'] == size) |