summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorchaithanyak <chaithanyak@vedams.com>2016-09-26 18:57:43 +0530
committerchaithanyak <chaithanyak@vedams.com>2016-09-26 18:57:43 +0530
commit25e93d0f2aa29d576c270df79337faa8e4637a63 (patch)
tree280001358d8a75258686628977223982b0d2a1c3
Initial Commit
-rw-r--r--LICENSE202
-rw-r--r--README.md4
-rw-r--r--components.yaml15
-rw-r--r--deployment_scripts/puppet/manifests/cinder_kaminario.pp9
-rw-r--r--deployment_scripts/puppet/manifests/cinder_parser.pp8
-rw-r--r--deployment_scripts/puppet/manifests/cinder_type.pp1
-rw-r--r--deployment_scripts/puppet/modules/kaminario/files/__init__.py0
-rw-r--r--deployment_scripts/puppet/modules/kaminario/files/exception.py1292
-rw-r--r--deployment_scripts/puppet/modules/kaminario/files/kaminario_common.py893
-rw-r--r--deployment_scripts/puppet/modules/kaminario/files/kaminario_fc.py184
-rw-r--r--deployment_scripts/puppet/modules/kaminario/files/kaminario_iscsi.py127
-rw-r--r--deployment_scripts/puppet/modules/kaminario/lib/puppet/parser/functions/get_replication_device.rb11
-rw-r--r--deployment_scripts/puppet/modules/kaminario/lib/puppet/parser/functions/section_name.rb9
-rw-r--r--deployment_scripts/puppet/modules/kaminario/manifests/driver.pp39
-rw-r--r--deployment_scripts/puppet/modules/kaminario/manifests/init.pp82
-rw-r--r--deployment_scripts/puppet/modules/kaminario/manifests/krest.pp8
-rw-r--r--deployment_scripts/puppet/modules/kaminario/manifests/type.pp45
-rw-r--r--deployment_tasks.yaml39
-rw-r--r--environment_config.yaml1078
-rw-r--r--metadata.yaml34
-rwxr-xr-xpre_build_hook5
-rw-r--r--repositories/centos/.gitkeep0
-rw-r--r--repositories/ubuntu/.gitkeep0
-rw-r--r--volumes.yaml7
24 files changed, 4092 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e06d208
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
1Apache License
2 Version 2.0, January 2004
3 http://www.apache.org/licenses/
4
5 TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
7 1. Definitions.
8
9 "License" shall mean the terms and conditions for use, reproduction,
10 and distribution as defined by Sections 1 through 9 of this document.
11
12 "Licensor" shall mean the copyright owner or entity authorized by
13 the copyright owner that is granting the License.
14
15 "Legal Entity" shall mean the union of the acting entity and all
16 other entities that control, are controlled by, or are under common
17 control with that entity. For the purposes of this definition,
18 "control" means (i) the power, direct or indirect, to cause the
19 direction or management of such entity, whether by contract or
20 otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 outstanding shares, or (iii) beneficial ownership of such entity.
22
23 "You" (or "Your") shall mean an individual or Legal Entity
24 exercising permissions granted by this License.
25
26 "Source" form shall mean the preferred form for making modifications,
27 including but not limited to software source code, documentation
28 source, and configuration files.
29
30 "Object" form shall mean any form resulting from mechanical
31 transformation or translation of a Source form, including but
32 not limited to compiled object code, generated documentation,
33 and conversions to other media types.
34
35 "Work" shall mean the work of authorship, whether in Source or
36 Object form, made available under the License, as indicated by a
37 copyright notice that is included in or attached to the work
38 (an example is provided in the Appendix below).
39
40 "Derivative Works" shall mean any work, whether in Source or Object
41 form, that is based on (or derived from) the Work and for which the
42 editorial revisions, annotations, elaborations, or other modifications
43 represent, as a whole, an original work of authorship. For the purposes
44 of this License, Derivative Works shall not include works that remain
45 separable from, or merely link (or bind by name) to the interfaces of,
46 the Work and Derivative Works thereof.
47
48 "Contribution" shall mean any work of authorship, including
49 the original version of the Work and any modifications or additions
50 to that Work or Derivative Works thereof, that is intentionally
51 submitted to Licensor for inclusion in the Work by the copyright owner
52 or by an individual or Legal Entity authorized to submit on behalf of
53 the copyright owner. For the purposes of this definition, "submitted"
54 means any form of electronic, verbal, or written communication sent
55 to the Licensor or its representatives, including but not limited to
56 communication on electronic mailing lists, source code control systems,
57 and issue tracking systems that are managed by, or on behalf of, the
58 Licensor for the purpose of discussing and improving the Work, but
59 excluding communication that is conspicuously marked or otherwise
60 designated in writing by the copyright owner as "Not a Contribution."
61
62 "Contributor" shall mean Licensor and any individual or Legal Entity
63 on behalf of whom a Contribution has been received by Licensor and
64 subsequently incorporated within the Work.
65
66 2. Grant of Copyright License. Subject to the terms and conditions of
67 this License, each Contributor hereby grants to You a perpetual,
68 worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 copyright license to reproduce, prepare Derivative Works of,
70 publicly display, publicly perform, sublicense, and distribute the
71 Work and such Derivative Works in Source or Object form.
72
73 3. Grant of Patent License. Subject to the terms and conditions of
74 this License, each Contributor hereby grants to You a perpetual,
75 worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 (except as stated in this section) patent license to make, have made,
77 use, offer to sell, sell, import, and otherwise transfer the Work,
78 where such license applies only to those patent claims licensable
79 by such Contributor that are necessarily infringed by their
80 Contribution(s) alone or by combination of their Contribution(s)
81 with the Work to which such Contribution(s) was submitted. If You
82 institute patent litigation against any entity (including a
83 cross-claim or counterclaim in a lawsuit) alleging that the Work
84 or a Contribution incorporated within the Work constitutes direct
85 or contributory patent infringement, then any patent licenses
86 granted to You under this License for that Work shall terminate
87 as of the date such litigation is filed.
88
89 4. Redistribution. You may reproduce and distribute copies of the
90 Work or Derivative Works thereof in any medium, with or without
91 modifications, and in Source or Object form, provided that You
92 meet the following conditions:
93
94 (a) You must give any other recipients of the Work or
95 Derivative Works a copy of this License; and
96
97 (b) You must cause any modified files to carry prominent notices
98 stating that You changed the files; and
99
100 (c) You must retain, in the Source form of any Derivative Works
101 that You distribute, all copyright, patent, trademark, and
102 attribution notices from the Source form of the Work,
103 excluding those notices that do not pertain to any part of
104 the Derivative Works; and
105
106 (d) If the Work includes a "NOTICE" text file as part of its
107 distribution, then any Derivative Works that You distribute must
108 include a readable copy of the attribution notices contained
109 within such NOTICE file, excluding those notices that do not
110 pertain to any part of the Derivative Works, in at least one
111 of the following places: within a NOTICE text file distributed
112 as part of the Derivative Works; within the Source form or
113 documentation, if provided along with the Derivative Works; or,
114 within a display generated by the Derivative Works, if and
115 wherever such third-party notices normally appear. The contents
116 of the NOTICE file are for informational purposes only and
117 do not modify the License. You may add Your own attribution
118 notices within Derivative Works that You distribute, alongside
119 or as an addendum to the NOTICE text from the Work, provided
120 that such additional attribution notices cannot be construed
121 as modifying the License.
122
123 You may add Your own copyright statement to Your modifications and
124 may provide additional or different license terms and conditions
125 for use, reproduction, or distribution of Your modifications, or
126 for any such Derivative Works as a whole, provided Your use,
127 reproduction, and distribution of the Work otherwise complies with
128 the conditions stated in this License.
129
130 5. Submission of Contributions. Unless You explicitly state otherwise,
131 any Contribution intentionally submitted for inclusion in the Work
132 by You to the Licensor shall be under the terms and conditions of
133 this License, without any additional terms or conditions.
134 Notwithstanding the above, nothing herein shall supersede or modify
135 the terms of any separate license agreement you may have executed
136 with Licensor regarding such Contributions.
137
138 6. Trademarks. This License does not grant permission to use the trade
139 names, trademarks, service marks, or product names of the Licensor,
140 except as required for reasonable and customary use in describing the
141 origin of the Work and reproducing the content of the NOTICE file.
142
143 7. Disclaimer of Warranty. Unless required by applicable law or
144 agreed to in writing, Licensor provides the Work (and each
145 Contributor provides its Contributions) on an "AS IS" BASIS,
146 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 implied, including, without limitation, any warranties or conditions
148 of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 PARTICULAR PURPOSE. You are solely responsible for determining the
150 appropriateness of using or redistributing the Work and assume any
151 risks associated with Your exercise of permissions under this License.
152
153 8. Limitation of Liability. In no event and under no legal theory,
154 whether in tort (including negligence), contract, or otherwise,
155 unless required by applicable law (such as deliberate and grossly
156 negligent acts) or agreed to in writing, shall any Contributor be
157 liable to You for damages, including any direct, indirect, special,
158 incidental, or consequential damages of any character arising as a
159 result of this License or out of the use or inability to use the
160 Work (including but not limited to damages for loss of goodwill,
161 work stoppage, computer failure or malfunction, or any and all
162 other commercial damages or losses), even if such Contributor
163 has been advised of the possibility of such damages.
164
165 9. Accepting Warranty or Additional Liability. While redistributing
166 the Work or Derivative Works thereof, You may choose to offer,
167 and charge a fee for, acceptance of support, warranty, indemnity,
168 or other liability obligations and/or rights consistent with this
169 License. However, in accepting such obligations, You may act only
170 on Your own behalf and on Your sole responsibility, not on behalf
171 of any other Contributor, and only if You agree to indemnify,
172 defend, and hold each Contributor harmless for any liability
173 incurred by, or claims asserted against, such Contributor by reason
174 of your accepting any such warranty or additional liability.
175
176 END OF TERMS AND CONDITIONS
177
178 APPENDIX: How to apply the Apache License to your work.
179
180 To apply the Apache License to your work, attach the following
181 boilerplate notice, with the fields enclosed by brackets "{}"
182 replaced with your own identifying information. (Don't include
183 the brackets!) The text should be enclosed in the appropriate
184 comment syntax for the file format. We also recommend that a
185 file or class name and description of purpose be included on the
186 same "printed page" as the copyright notice for easier
187 identification within third-party archives.
188
189 Copyright {yyyy} {name of copyright owner}
190
191 Licensed under the Apache License, Version 2.0 (the "License");
192 you may not use this file except in compliance with the License.
193 You may obtain a copy of the License at
194
195 http://www.apache.org/licenses/LICENSE-2.0
196
197 Unless required by applicable law or agreed to in writing, software
198 distributed under the License is distributed on an "AS IS" BASIS,
199 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 See the License for the specific language governing permissions and
201 limitations under the License.
202
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..922ec62
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
1fuel-plugin-cinder-kaminario
2============
3
4Plugin description \ No newline at end of file
diff --git a/components.yaml b/components.yaml
new file mode 100644
index 0000000..056eaad
--- /dev/null
+++ b/components.yaml
@@ -0,0 +1,15 @@
1- name: 'storage:block:backend:kaminario'
2 label: 'Kaminario'
3 description: 'Cinder with Kaminario backend'
4 compatible:
5 - name: storage:block:lvm
6 - name: storage:block:ceph
7 - name: storage:object:ceph
8 - name: storage:ephemeral:ceph
9 - name: storage:image:ceph
10 - name: hypervisor:qemu
11 - name: network:neutron:core:ml2
12 - name: network:neutron:ml2:vlan
13 - name: network:neutron:ml2:tun
14 incompatible:
15 - name: hypervisor:vmware
diff --git a/deployment_scripts/puppet/manifests/cinder_kaminario.pp b/deployment_scripts/puppet/manifests/cinder_kaminario.pp
new file mode 100644
index 0000000..78727fa
--- /dev/null
+++ b/deployment_scripts/puppet/manifests/cinder_kaminario.pp
@@ -0,0 +1,9 @@
1notice('MODULAR: cinder_kaminario')
2
3
4class { 'kaminario::driver': }->
5class { 'kaminario::krest': }->
6class { 'kaminario::config': }~> Exec[cinder_volume]
7
8exec {'cinder_volume':
9 command => '/usr/sbin/service cinder-volume restart',}
diff --git a/deployment_scripts/puppet/manifests/cinder_parser.pp b/deployment_scripts/puppet/manifests/cinder_parser.pp
new file mode 100644
index 0000000..d5c10eb
--- /dev/null
+++ b/deployment_scripts/puppet/manifests/cinder_parser.pp
@@ -0,0 +1,8 @@
1ini_setting { 'parser':
2 ensure => present,
3 path => '/etc/puppet/puppet.conf',
4 section => 'main',
5 setting => 'parser',
6 value => 'future',
7 }
8
diff --git a/deployment_scripts/puppet/manifests/cinder_type.pp b/deployment_scripts/puppet/manifests/cinder_type.pp
new file mode 100644
index 0000000..0cc4f96
--- /dev/null
+++ b/deployment_scripts/puppet/manifests/cinder_type.pp
@@ -0,0 +1 @@
include kaminario::type
diff --git a/deployment_scripts/puppet/modules/kaminario/files/__init__.py b/deployment_scripts/puppet/modules/kaminario/files/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/deployment_scripts/puppet/modules/kaminario/files/__init__.py
diff --git a/deployment_scripts/puppet/modules/kaminario/files/exception.py b/deployment_scripts/puppet/modules/kaminario/files/exception.py
new file mode 100644
index 0000000..9e0f230
--- /dev/null
+++ b/deployment_scripts/puppet/modules/kaminario/files/exception.py
@@ -0,0 +1,1292 @@
1# Copyright 2010 United States Government as represented by the
2# Administrator of the National Aeronautics and Space Administration.
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17"""Cinder base exception handling.
18
19Includes decorator for re-raising Cinder-type exceptions.
20
21SHOULD include dedicated exception logging.
22
23"""
24
25import sys
26
27from oslo_config import cfg
28from oslo_log import log as logging
29from oslo_versionedobjects import exception as obj_exc
30import six
31import webob.exc
32from webob.util import status_generic_reasons
33from webob.util import status_reasons
34
35from cinder.i18n import _, _LE
36
37
38LOG = logging.getLogger(__name__)
39
40exc_log_opts = [
41 cfg.BoolOpt('fatal_exception_format_errors',
42 default=False,
43 help='Make exception message format errors fatal.'),
44]
45
46CONF = cfg.CONF
47CONF.register_opts(exc_log_opts)
48
49
50class ConvertedException(webob.exc.WSGIHTTPException):
51 def __init__(self, code=500, title="", explanation=""):
52 self.code = code
53 # There is a strict rule about constructing status line for HTTP:
54 # '...Status-Line, consisting of the protocol version followed by a
55 # numeric status code and its associated textual phrase, with each
56 # element separated by SP characters'
57 # (http://www.faqs.org/rfcs/rfc2616.html)
58 # 'code' and 'title' can not be empty because they correspond
59 # to numeric status code and its associated text
60 if title:
61 self.title = title
62 else:
63 try:
64 self.title = status_reasons[self.code]
65 except KeyError:
66 generic_code = self.code // 100
67 self.title = status_generic_reasons[generic_code]
68 self.explanation = explanation
69 super(ConvertedException, self).__init__()
70
71
72class Error(Exception):
73 pass
74
75
76class CinderException(Exception):
77 """Base Cinder Exception
78
79 To correctly use this class, inherit from it and define
80 a 'message' property. That message will get printf'd
81 with the keyword arguments provided to the constructor.
82
83 """
84 message = _("An unknown exception occurred.")
85 code = 500
86 headers = {}
87 safe = False
88
89 def __init__(self, message=None, **kwargs):
90 self.kwargs = kwargs
91 self.kwargs['message'] = message
92
93 if 'code' not in self.kwargs:
94 try:
95 self.kwargs['code'] = self.code
96 except AttributeError:
97 pass
98
99 for k, v in self.kwargs.items():
100 if isinstance(v, Exception):
101 self.kwargs[k] = six.text_type(v)
102
103 if self._should_format():
104 try:
105 message = self.message % kwargs
106
107 except Exception:
108 exc_info = sys.exc_info()
109 # kwargs doesn't match a variable in the message
110 # log the issue and the kwargs
111 LOG.exception(_LE('Exception in string format operation'))
112 for name, value in kwargs.items():
113 LOG.error(_LE("%(name)s: %(value)s"),
114 {'name': name, 'value': value})
115 if CONF.fatal_exception_format_errors:
116 six.reraise(*exc_info)
117 # at least get the core message out if something happened
118 message = self.message
119 elif isinstance(message, Exception):
120 message = six.text_type(message)
121
122 # NOTE(luisg): We put the actual message in 'msg' so that we can access
123 # it, because if we try to access the message via 'message' it will be
124 # overshadowed by the class' message attribute
125 self.msg = message
126 super(CinderException, self).__init__(message)
127
128 def _should_format(self):
129 return self.kwargs['message'] is None or '%(message)' in self.message
130
131 def __unicode__(self):
132 return six.text_type(self.msg)
133
134
135class VolumeBackendAPIException(CinderException):
136 message = _("Bad or unexpected response from the storage volume "
137 "backend API: %(data)s")
138
139
140class VolumeDriverException(CinderException):
141 message = _("Volume driver reported an error: %(message)s")
142
143
144class BackupDriverException(CinderException):
145 message = _("Backup driver reported an error: %(message)s")
146
147
148class GlanceConnectionFailed(CinderException):
149 message = _("Connection to glance failed: %(reason)s")
150
151
152class ProgrammingError(CinderException):
153 message = _('Programming error in Cinder: %(reason)s')
154
155
156class NotAuthorized(CinderException):
157 message = _("Not authorized.")
158 code = 403
159
160
161class AdminRequired(NotAuthorized):
162 message = _("User does not have admin privileges")
163
164
165class PolicyNotAuthorized(NotAuthorized):
166 message = _("Policy doesn't allow %(action)s to be performed.")
167
168
169class ImageNotAuthorized(CinderException):
170 message = _("Not authorized for image %(image_id)s.")
171
172
173class DriverNotInitialized(CinderException):
174 message = _("Volume driver not ready.")
175
176
177class Invalid(CinderException):
178 message = _("Unacceptable parameters.")
179 code = 400
180
181
182class InvalidSnapshot(Invalid):
183 message = _("Invalid snapshot: %(reason)s")
184
185
186class InvalidVolumeAttachMode(Invalid):
187 message = _("Invalid attaching mode '%(mode)s' for "
188 "volume %(volume_id)s.")
189
190
191class VolumeAttached(Invalid):
192 message = _("Volume %(volume_id)s is still attached, detach volume first.")
193
194
195class InvalidResults(Invalid):
196 message = _("The results are invalid.")
197
198
199class InvalidInput(Invalid):
200 message = _("Invalid input received: %(reason)s")
201
202
203class InvalidVolumeType(Invalid):
204 message = _("Invalid volume type: %(reason)s")
205
206
207class InvalidGroupType(Invalid):
208 message = _("Invalid group type: %(reason)s")
209
210
211class InvalidVolume(Invalid):
212 message = _("Invalid volume: %(reason)s")
213
214
215class InvalidContentType(Invalid):
216 message = _("Invalid content type %(content_type)s.")
217
218
219class InvalidHost(Invalid):
220 message = _("Invalid host: %(reason)s")
221
222
223# Cannot be templated as the error syntax varies.
224# msg needs to be constructed when raised.
225class InvalidParameterValue(Invalid):
226 message = _("%(err)s")
227
228
229class InvalidAuthKey(Invalid):
230 message = _("Invalid auth key: %(reason)s")
231
232
233class InvalidConfigurationValue(Invalid):
234 message = _('Value "%(value)s" is not valid for '
235 'configuration option "%(option)s"')
236
237
238class ServiceUnavailable(Invalid):
239 message = _("Service is unavailable at this time.")
240
241
242class ImageUnacceptable(Invalid):
243 message = _("Image %(image_id)s is unacceptable: %(reason)s")
244
245
246class DeviceUnavailable(Invalid):
247 message = _("The device in the path %(path)s is unavailable: %(reason)s")
248
249
250class SnapshotUnavailable(VolumeBackendAPIException):
251 message = _("The snapshot is unavailable: %(data)s")
252
253
254class InvalidUUID(Invalid):
255 message = _("Expected a uuid but received %(uuid)s.")
256
257
258class InvalidAPIVersionString(Invalid):
259 message = _("API Version String %(version)s is of invalid format. Must "
260 "be of format MajorNum.MinorNum.")
261
262
263class VersionNotFoundForAPIMethod(Invalid):
264 message = _("API version %(version)s is not supported on this method.")
265
266
267class InvalidGlobalAPIVersion(Invalid):
268 message = _("Version %(req_ver)s is not supported by the API. Minimum "
269 "is %(min_ver)s and maximum is %(max_ver)s.")
270
271
272class MissingRequired(Invalid):
273 message = _("Missing required element '%(element)s' in request body.")
274
275
276class APIException(CinderException):
277 message = _("Error while requesting %(service)s API.")
278
279 def __init__(self, message=None, **kwargs):
280 if 'service' not in kwargs:
281 kwargs['service'] = 'unknown'
282 super(APIException, self).__init__(message, **kwargs)
283
284
285class APITimeout(APIException):
286 message = _("Timeout while requesting %(service)s API.")
287
288
289class RPCTimeout(CinderException):
290 message = _("Timeout while requesting capabilities from backend "
291 "%(service)s.")
292 code = 502
293
294
295class Duplicate(CinderException):
296 pass
297
298
299class NotFound(CinderException):
300 message = _("Resource could not be found.")
301 code = 404
302 safe = True
303
304
305class VolumeNotFound(NotFound):
306 message = _("Volume %(volume_id)s could not be found.")
307
308
309class MessageNotFound(NotFound):
310 message = _("Message %(message_id)s could not be found.")
311
312
313class VolumeAttachmentNotFound(NotFound):
314 message = _("Volume attachment could not be found with "
315 "filter: %(filter)s .")
316
317
318class VolumeMetadataNotFound(NotFound):
319 message = _("Volume %(volume_id)s has no metadata with "
320 "key %(metadata_key)s.")
321
322
323class VolumeAdminMetadataNotFound(NotFound):
324 message = _("Volume %(volume_id)s has no administration metadata with "
325 "key %(metadata_key)s.")
326
327
328class InvalidVolumeMetadata(Invalid):
329 message = _("Invalid metadata: %(reason)s")
330
331
332class InvalidVolumeMetadataSize(Invalid):
333 message = _("Invalid metadata size: %(reason)s")
334
335
336class SnapshotMetadataNotFound(NotFound):
337 message = _("Snapshot %(snapshot_id)s has no metadata with "
338 "key %(metadata_key)s.")
339
340
341class VolumeTypeNotFound(NotFound):
342 message = _("Volume type %(volume_type_id)s could not be found.")
343
344
345class VolumeTypeNotFoundByName(VolumeTypeNotFound):
346 message = _("Volume type with name %(volume_type_name)s "
347 "could not be found.")
348
349
350class VolumeTypeAccessNotFound(NotFound):
351 message = _("Volume type access not found for %(volume_type_id)s / "
352 "%(project_id)s combination.")
353
354
355class VolumeTypeExtraSpecsNotFound(NotFound):
356 message = _("Volume Type %(volume_type_id)s has no extra specs with "
357 "key %(extra_specs_key)s.")
358
359
360class VolumeTypeInUse(CinderException):
361 message = _("Volume Type %(volume_type_id)s deletion is not allowed with "
362 "volumes present with the type.")
363
364
365class GroupTypeNotFound(NotFound):
366 message = _("Group type %(group_type_id)s could not be found.")
367
368
369class GroupTypeNotFoundByName(GroupTypeNotFound):
370 message = _("Group type with name %(group_type_name)s "
371 "could not be found.")
372
373
374class GroupTypeAccessNotFound(NotFound):
375 message = _("Group type access not found for %(group_type_id)s / "
376 "%(project_id)s combination.")
377
378
379class GroupTypeSpecsNotFound(NotFound):
380 message = _("Group Type %(group_type_id)s has no specs with "
381 "key %(group_specs_key)s.")
382
383
384class GroupTypeInUse(CinderException):
385 message = _("Group Type %(group_type_id)s deletion is not allowed with "
386 "groups present with the type.")
387
388
389class SnapshotNotFound(NotFound):
390 message = _("Snapshot %(snapshot_id)s could not be found.")
391
392
393class ServerNotFound(NotFound):
394 message = _("Instance %(uuid)s could not be found.")
395
396
397class VolumeIsBusy(CinderException):
398 message = _("deleting volume %(volume_name)s that has snapshot")
399
400
401class SnapshotIsBusy(CinderException):
402 message = _("deleting snapshot %(snapshot_name)s that has "
403 "dependent volumes")
404
405
406class ISCSITargetNotFoundForVolume(NotFound):
407 message = _("No target id found for volume %(volume_id)s.")
408
409
410class InvalidImageRef(Invalid):
411 message = _("Invalid image href %(image_href)s.")
412
413
414class ImageNotFound(NotFound):
415 message = _("Image %(image_id)s could not be found.")
416
417
418class ServiceNotFound(NotFound):
419
420 def __init__(self, message=None, **kwargs):
421 if kwargs.get('host', None):
422 self.message = _("Service %(service_id)s could not be "
423 "found on host %(host)s.")
424 else:
425 self.message = _("Service %(service_id)s could not be found.")
426 super(ServiceNotFound, self).__init__(None, **kwargs)
427
428
429class ServiceTooOld(Invalid):
430 message = _("Service is too old to fulfil this request.")
431
432
433class WorkerNotFound(NotFound):
434 message = _("Worker with %s could not be found.")
435
436 def __init__(self, message=None, **kwargs):
437 keys_list = ('{0}=%({0})s'.format(key) for key in kwargs)
438 placeholder = ', '.join(keys_list)
439 self.message = self.message % placeholder
440 super(WorkerNotFound, self).__init__(message, **kwargs)
441
442
443class WorkerExists(Duplicate):
444 message = _("Worker for %(type)s %(id)s already exists.")
445
446
447class ClusterNotFound(NotFound):
448 message = _('Cluster %(id)s could not be found.')
449
450
451class ClusterHasHosts(Invalid):
452 message = _("Cluster %(id)s still has hosts.")
453
454
455class ClusterExists(Duplicate):
456 message = _("Cluster %(name)s already exists.")
457
458
459class HostNotFound(NotFound):
460 message = _("Host %(host)s could not be found.")
461
462
463class SchedulerHostFilterNotFound(NotFound):
464 message = _("Scheduler Host Filter %(filter_name)s could not be found.")
465
466
467class SchedulerHostWeigherNotFound(NotFound):
468 message = _("Scheduler Host Weigher %(weigher_name)s could not be found.")
469
470
471class InvalidReservationExpiration(Invalid):
472 message = _("Invalid reservation expiration %(expire)s.")
473
474
475class InvalidQuotaValue(Invalid):
476 message = _("Change would make usage less than 0 for the following "
477 "resources: %(unders)s")
478
479
480class InvalidNestedQuotaSetup(CinderException):
481 message = _("Project quotas are not properly setup for nested quotas: "
482 "%(reason)s.")
483
484
485class QuotaNotFound(NotFound):
486 message = _("Quota could not be found")
487
488
489class QuotaResourceUnknown(QuotaNotFound):
490 message = _("Unknown quota resources %(unknown)s.")
491
492
493class ProjectQuotaNotFound(QuotaNotFound):
494 message = _("Quota for project %(project_id)s could not be found.")
495
496
497class QuotaClassNotFound(QuotaNotFound):
498 message = _("Quota class %(class_name)s could not be found.")
499
500
501class QuotaUsageNotFound(QuotaNotFound):
502 message = _("Quota usage for project %(project_id)s could not be found.")
503
504
505class ReservationNotFound(QuotaNotFound):
506 message = _("Quota reservation %(uuid)s could not be found.")
507
508
509class OverQuota(CinderException):
510 message = _("Quota exceeded for resources: %(overs)s")
511
512
513class FileNotFound(NotFound):
514 message = _("File %(file_path)s could not be found.")
515
516
517class VolumeTypeExists(Duplicate):
518 message = _("Volume Type %(id)s already exists.")
519
520
521class VolumeTypeAccessExists(Duplicate):
522 message = _("Volume type access for %(volume_type_id)s / "
523 "%(project_id)s combination already exists.")
524
525
526class VolumeTypeEncryptionExists(Invalid):
527 message = _("Volume type encryption for type %(type_id)s already exists.")
528
529
530class VolumeTypeEncryptionNotFound(NotFound):
531 message = _("Volume type encryption for type %(type_id)s does not exist.")
532
533
534class GroupTypeExists(Duplicate):
535 message = _("Group Type %(id)s already exists.")
536
537
538class GroupTypeAccessExists(Duplicate):
539 message = _("Group type access for %(group_type_id)s / "
540 "%(project_id)s combination already exists.")
541
542
543class GroupTypeEncryptionExists(Invalid):
544 message = _("Group type encryption for type %(type_id)s already exists.")
545
546
547class GroupTypeEncryptionNotFound(NotFound):
548 message = _("Group type encryption for type %(type_id)s does not exist.")
549
550
551class MalformedRequestBody(CinderException):
552 message = _("Malformed message body: %(reason)s")
553
554
555class ConfigNotFound(NotFound):
556 message = _("Could not find config at %(path)s")
557
558
559class ParameterNotFound(NotFound):
560 message = _("Could not find parameter %(param)s")
561
562
563class PasteAppNotFound(NotFound):
564 message = _("Could not load paste app '%(name)s' from %(path)s")
565
566
567class NoValidHost(CinderException):
568 message = _("No valid host was found. %(reason)s")
569
570
571class NoMoreTargets(CinderException):
572 """No more available targets."""
573 pass
574
575
576class QuotaError(CinderException):
577 message = _("Quota exceeded: code=%(code)s")
578 code = 413
579 headers = {'Retry-After': '0'}
580 safe = True
581
582
583class VolumeSizeExceedsAvailableQuota(QuotaError):
584 message = _("Requested volume or snapshot exceeds allowed %(name)s "
585 "quota. Requested %(requested)sG, quota is %(quota)sG and "
586 "%(consumed)sG has been consumed.")
587
588 def __init__(self, message=None, **kwargs):
589 kwargs.setdefault('name', 'gigabytes')
590 super(VolumeSizeExceedsAvailableQuota, self).__init__(
591 message, **kwargs)
592
593
594class VolumeSizeExceedsLimit(QuotaError):
595 message = _("Requested volume size %(size)d is larger than "
596 "maximum allowed limit %(limit)d.")
597
598
599class VolumeBackupSizeExceedsAvailableQuota(QuotaError):
600 message = _("Requested backup exceeds allowed Backup gigabytes "
601 "quota. Requested %(requested)sG, quota is %(quota)sG and "
602 "%(consumed)sG has been consumed.")
603
604
605class VolumeLimitExceeded(QuotaError):
606 message = _("Maximum number of volumes allowed (%(allowed)d) exceeded for "
607 "quota '%(name)s'.")
608
609 def __init__(self, message=None, **kwargs):
610 kwargs.setdefault('name', 'volumes')
611 super(VolumeLimitExceeded, self).__init__(message, **kwargs)
612
613
614class SnapshotLimitExceeded(QuotaError):
615 message = _("Maximum number of snapshots allowed (%(allowed)d) exceeded")
616
617
618class UnexpectedOverQuota(QuotaError):
619 message = _("Unexpected over quota on %(name)s.")
620
621
622class BackupLimitExceeded(QuotaError):
623 message = _("Maximum number of backups allowed (%(allowed)d) exceeded")
624
625
626class ImageLimitExceeded(QuotaError):
627 message = _("Image quota exceeded")
628
629
630class DuplicateSfVolumeNames(Duplicate):
631 message = _("Detected more than one volume with name %(vol_name)s")
632
633
634class VolumeTypeCreateFailed(CinderException):
635 message = _("Cannot create volume_type with "
636 "name %(name)s and specs %(extra_specs)s")
637
638
639class VolumeTypeUpdateFailed(CinderException):
640 message = _("Cannot update volume_type %(id)s")
641
642
643class GroupTypeCreateFailed(CinderException):
644 message = _("Cannot create group_type with "
645 "name %(name)s and specs %(group_specs)s")
646
647
648class GroupTypeUpdateFailed(CinderException):
649 message = _("Cannot update group_type %(id)s")
650
651
652class UnknownCmd(VolumeDriverException):
653 message = _("Unknown or unsupported command %(cmd)s")
654
655
656class MalformedResponse(VolumeDriverException):
657 message = _("Malformed response to command %(cmd)s: %(reason)s")
658
659
660class FailedCmdWithDump(VolumeDriverException):
661 message = _("Operation failed with status=%(status)s. Full dump: %(data)s")
662
663
664class InvalidConnectorException(VolumeDriverException):
665 message = _("Connector doesn't have required information: %(missing)s")
666
667
668class GlanceMetadataExists(Invalid):
669 message = _("Glance metadata cannot be updated, key %(key)s"
670 " exists for volume id %(volume_id)s")
671
672
673class GlanceMetadataNotFound(NotFound):
674 message = _("Glance metadata for volume/snapshot %(id)s cannot be found.")
675
676
677class ExportFailure(Invalid):
678 message = _("Failed to export for volume: %(reason)s")
679
680
681class RemoveExportException(VolumeDriverException):
682 message = _("Failed to remove export for volume %(volume)s: %(reason)s")
683
684
685class MetadataCreateFailure(Invalid):
686 message = _("Failed to create metadata for volume: %(reason)s")
687
688
689class MetadataUpdateFailure(Invalid):
690 message = _("Failed to update metadata for volume: %(reason)s")
691
692
693class MetadataCopyFailure(Invalid):
694 message = _("Failed to copy metadata to volume: %(reason)s")
695
696
697class InvalidMetadataType(Invalid):
698 message = _("The type of metadata: %(metadata_type)s for volume/snapshot "
699 "%(id)s is invalid.")
700
701
702class ImageCopyFailure(Invalid):
703 message = _("Failed to copy image to volume: %(reason)s")
704
705
706class BackupInvalidCephArgs(BackupDriverException):
707 message = _("Invalid Ceph args provided for backup rbd operation")
708
709
710class BackupOperationError(Invalid):
711 message = _("An error has occurred during backup operation")
712
713
714class BackupMetadataUnsupportedVersion(BackupDriverException):
715 message = _("Unsupported backup metadata version requested")
716
717
718class BackupVerifyUnsupportedDriver(BackupDriverException):
719 message = _("Unsupported backup verify driver")
720
721
722class VolumeMetadataBackupExists(BackupDriverException):
723 message = _("Metadata backup already exists for this volume")
724
725
726class BackupRBDOperationFailed(BackupDriverException):
727 message = _("Backup RBD operation failed")
728
729
730class EncryptedBackupOperationFailed(BackupDriverException):
731 message = _("Backup operation of an encrypted volume failed.")
732
733
734class BackupNotFound(NotFound):
735 message = _("Backup %(backup_id)s could not be found.")
736
737
738class BackupFailedToGetVolumeBackend(NotFound):
739 message = _("Failed to identify volume backend.")
740
741
742class InvalidBackup(Invalid):
743 message = _("Invalid backup: %(reason)s")
744
745
746class SwiftConnectionFailed(BackupDriverException):
747 message = _("Connection to swift failed: %(reason)s")
748
749
750class TransferNotFound(NotFound):
751 message = _("Transfer %(transfer_id)s could not be found.")
752
753
754class VolumeMigrationFailed(CinderException):
755 message = _("Volume migration failed: %(reason)s")
756
757
758class SSHInjectionThreat(CinderException):
759 message = _("SSH command injection detected: %(command)s")
760
761
762class QoSSpecsExists(Duplicate):
763 message = _("QoS Specs %(specs_id)s already exists.")
764
765
766class QoSSpecsCreateFailed(CinderException):
767 message = _("Failed to create qos_specs: "
768 "%(name)s with specs %(qos_specs)s.")
769
770
771class QoSSpecsUpdateFailed(CinderException):
772 message = _("Failed to update qos_specs: "
773 "%(specs_id)s with specs %(qos_specs)s.")
774
775
776class QoSSpecsNotFound(NotFound):
777 message = _("No such QoS spec %(specs_id)s.")
778
779
780class QoSSpecsAssociateFailed(CinderException):
781 message = _("Failed to associate qos_specs: "
782 "%(specs_id)s with type %(type_id)s.")
783
784
785class QoSSpecsDisassociateFailed(CinderException):
786 message = _("Failed to disassociate qos_specs: "
787 "%(specs_id)s with type %(type_id)s.")
788
789
790class QoSSpecsKeyNotFound(NotFound):
791 message = _("QoS spec %(specs_id)s has no spec with "
792 "key %(specs_key)s.")
793
794
795class InvalidQoSSpecs(Invalid):
796 message = _("Invalid qos specs: %(reason)s")
797
798
799class QoSSpecsInUse(CinderException):
800 message = _("QoS Specs %(specs_id)s is still associated with entities.")
801
802
803class KeyManagerError(CinderException):
804 message = _("key manager error: %(reason)s")
805
806
807class ManageExistingInvalidReference(CinderException):
808 message = _("Manage existing volume failed due to invalid backend "
809 "reference %(existing_ref)s: %(reason)s")
810
811
812class ManageExistingAlreadyManaged(CinderException):
813 message = _("Unable to manage existing volume. "
814 "Volume %(volume_ref)s already managed.")
815
816
817class InvalidReplicationTarget(Invalid):
818 message = _("Invalid Replication Target: %(reason)s")
819
820
821class UnableToFailOver(CinderException):
822 message = _("Unable to failover to replication target:"
823 "%(reason)s).")
824
825
826class ReplicationError(CinderException):
827 message = _("Volume %(volume_id)s replication "
828 "error: %(reason)s")
829
830
831class ReplicationNotFound(NotFound):
832 message = _("Volume replication for %(volume_id)s "
833 "could not be found.")
834
835
836class ManageExistingVolumeTypeMismatch(CinderException):
837 message = _("Manage existing volume failed due to volume type mismatch: "
838 "%(reason)s")
839
840
841class ExtendVolumeError(CinderException):
842 message = _("Error extending volume: %(reason)s")
843
844
845class EvaluatorParseException(Exception):
846 message = _("Error during evaluator parsing: %(reason)s")
847
848
849class LockCreationFailed(CinderException):
850 message = _('Unable to create lock. Coordination backend not started.')
851
852
853class LockingFailed(CinderException):
854 message = _('Lock acquisition failed.')
855
856
857UnsupportedObjectError = obj_exc.UnsupportedObjectError
858OrphanedObjectError = obj_exc.OrphanedObjectError
859IncompatibleObjectVersion = obj_exc.IncompatibleObjectVersion
860ReadOnlyFieldError = obj_exc.ReadOnlyFieldError
861ObjectActionError = obj_exc.ObjectActionError
862ObjectFieldInvalid = obj_exc.ObjectFieldInvalid
863
864
865class CappedVersionUnknown(CinderException):
866 message = _('Unrecoverable Error: Versioned Objects in DB are capped to '
867 'unknown version %(version)s.')
868
869
870class VolumeGroupNotFound(CinderException):
871 message = _('Unable to find Volume Group: %(vg_name)s')
872
873
874class VolumeGroupCreationFailed(CinderException):
875 message = _('Failed to create Volume Group: %(vg_name)s')
876
877
878class VolumeNotDeactivated(CinderException):
879 message = _('Volume %(name)s was not deactivated in time.')
880
881
882class VolumeDeviceNotFound(CinderException):
883 message = _('Volume device not found at %(device)s.')
884
885
886# Driver specific exceptions
887# Dell
888class DellDriverRetryableException(VolumeBackendAPIException):
889 message = _("Retryable Dell Exception encountered")
890
891
892# Pure Storage
893class PureDriverException(VolumeDriverException):
894 message = _("Pure Storage Cinder driver failure: %(reason)s")
895
896
897class PureRetryableException(VolumeBackendAPIException):
898 message = _("Retryable Pure Storage Exception encountered")
899
900
901# SolidFire
902class SolidFireAPIException(VolumeBackendAPIException):
903 message = _("Bad response from SolidFire API")
904
905
906class SolidFireDriverException(VolumeDriverException):
907 message = _("SolidFire Cinder Driver exception")
908
909
910class SolidFireAPIDataException(SolidFireAPIException):
911 message = _("Error in SolidFire API response: data=%(data)s")
912
913
914class SolidFireAccountNotFound(SolidFireDriverException):
915 message = _("Unable to locate account %(account_name)s on "
916 "Solidfire device")
917
918
919class SolidFireRetryableException(VolumeBackendAPIException):
920 message = _("Retryable SolidFire Exception encountered")
921
922
923# HP 3Par
924class Invalid3PARDomain(VolumeDriverException):
925 message = _("Invalid 3PAR Domain: %(err)s")
926
927
928# RemoteFS drivers
929class RemoteFSException(VolumeDriverException):
930 message = _("Unknown RemoteFS exception")
931
932
933class RemoteFSConcurrentRequest(RemoteFSException):
934 message = _("A concurrent, possibly contradictory, request "
935 "has been made.")
936
937
938class RemoteFSNoSharesMounted(RemoteFSException):
939 message = _("No mounted shares found")
940
941
942class RemoteFSNoSuitableShareFound(RemoteFSException):
943 message = _("There is no share which can host %(volume_size)sG")
944
945
946# NFS driver
947class NfsException(RemoteFSException):
948 message = _("Unknown NFS exception")
949
950
951class NfsNoSharesMounted(RemoteFSNoSharesMounted):
952 message = _("No mounted NFS shares found")
953
954
955class NfsNoSuitableShareFound(RemoteFSNoSuitableShareFound):
956 message = _("There is no share which can host %(volume_size)sG")
957
958
959# Smbfs driver
960class SmbfsException(RemoteFSException):
961 message = _("Unknown SMBFS exception.")
962
963
964class SmbfsNoSharesMounted(RemoteFSNoSharesMounted):
965 message = _("No mounted SMBFS shares found.")
966
967
968class SmbfsNoSuitableShareFound(RemoteFSNoSuitableShareFound):
969 message = _("There is no share which can host %(volume_size)sG.")
970
971
972# Gluster driver
973class GlusterfsException(RemoteFSException):
974 message = _("Unknown Gluster exception")
975
976
977class GlusterfsNoSharesMounted(RemoteFSNoSharesMounted):
978 message = _("No mounted Gluster shares found")
979
980
981class GlusterfsNoSuitableShareFound(RemoteFSNoSuitableShareFound):
982 message = _("There is no share which can host %(volume_size)sG")
983
984
985# Virtuozzo Storage Driver
986
987class VzStorageException(RemoteFSException):
988 message = _("Unknown Virtuozzo Storage exception")
989
990
991class VzStorageNoSharesMounted(RemoteFSNoSharesMounted):
992 message = _("No mounted Virtuozzo Storage shares found")
993
994
995class VzStorageNoSuitableShareFound(RemoteFSNoSuitableShareFound):
996 message = _("There is no share which can host %(volume_size)sG")
997
998
999# Fibre Channel Zone Manager
1000class ZoneManagerException(CinderException):
1001 message = _("Fibre Channel connection control failure: %(reason)s")
1002
1003
1004class FCZoneDriverException(CinderException):
1005 message = _("Fibre Channel Zone operation failed: %(reason)s")
1006
1007
1008class FCSanLookupServiceException(CinderException):
1009 message = _("Fibre Channel SAN Lookup failure: %(reason)s")
1010
1011
1012class BrocadeZoningCliException(CinderException):
1013 message = _("Brocade Fibre Channel Zoning CLI error: %(reason)s")
1014
1015
1016class BrocadeZoningHttpException(CinderException):
1017 message = _("Brocade Fibre Channel Zoning HTTP error: %(reason)s")
1018
1019
1020class CiscoZoningCliException(CinderException):
1021 message = _("Cisco Fibre Channel Zoning CLI error: %(reason)s")
1022
1023
1024class NetAppDriverException(VolumeDriverException):
1025 message = _("NetApp Cinder Driver exception.")
1026
1027
1028class EMCVnxCLICmdError(VolumeBackendAPIException):
1029 message = _("EMC VNX Cinder Driver CLI exception: %(cmd)s "
1030 "(Return Code: %(rc)s) (Output: %(out)s).")
1031
1032
1033class EMCSPUnavailableException(EMCVnxCLICmdError):
1034 message = _("EMC VNX Cinder Driver SPUnavailableException: %(cmd)s "
1035 "(Return Code: %(rc)s) (Output: %(out)s).")
1036
1037
1038# ConsistencyGroup
1039class ConsistencyGroupNotFound(NotFound):
1040 message = _("ConsistencyGroup %(consistencygroup_id)s could not be found.")
1041
1042
1043class InvalidConsistencyGroup(Invalid):
1044 message = _("Invalid ConsistencyGroup: %(reason)s")
1045
1046
1047# CgSnapshot
1048class CgSnapshotNotFound(NotFound):
1049 message = _("CgSnapshot %(cgsnapshot_id)s could not be found.")
1050
1051
1052class InvalidCgSnapshot(Invalid):
1053 message = _("Invalid CgSnapshot: %(reason)s")
1054
1055
1056# Hitachi Block Storage Driver
1057class HBSDError(CinderException):
1058 message = _("HBSD error occurs.")
1059
1060
1061class HBSDCmdError(HBSDError):
1062
1063 def __init__(self, message=None, ret=None, err=None):
1064 self.ret = ret
1065 self.stderr = err
1066
1067 super(HBSDCmdError, self).__init__(message=message)
1068
1069
1070class HBSDBusy(HBSDError):
1071 message = "Device or resource is busy."
1072
1073
1074class HBSDNotFound(NotFound):
1075 message = _("Storage resource could not be found.")
1076
1077
1078class HBSDVolumeIsBusy(VolumeIsBusy):
1079 message = _("Volume %(volume_name)s is busy.")
1080
1081
1082# Datera driver
1083class DateraAPIException(VolumeBackendAPIException):
1084 message = _("Bad response from Datera API")
1085
1086
1087# Target drivers
1088class ISCSITargetCreateFailed(CinderException):
1089 message = _("Failed to create iscsi target for volume %(volume_id)s.")
1090
1091
1092class ISCSITargetRemoveFailed(CinderException):
1093 message = _("Failed to remove iscsi target for volume %(volume_id)s.")
1094
1095
1096class ISCSITargetAttachFailed(CinderException):
1097 message = _("Failed to attach iSCSI target for volume %(volume_id)s.")
1098
1099
1100class ISCSITargetDetachFailed(CinderException):
1101 message = _("Failed to detach iSCSI target for volume %(volume_id)s.")
1102
1103
1104class ISCSITargetHelperCommandFailed(CinderException):
1105 message = _("%(error_message)s")
1106
1107
1108# X-IO driver exception.
1109class XIODriverException(VolumeDriverException):
1110 message = _("X-IO Volume Driver exception!")
1111
1112
1113# Violin Memory drivers
1114class ViolinInvalidBackendConfig(VolumeDriverException):
1115 message = _("Volume backend config is invalid: %(reason)s")
1116
1117
1118class ViolinRequestRetryTimeout(VolumeDriverException):
1119 message = _("Backend service retry timeout hit: %(timeout)s sec")
1120
1121
1122class ViolinBackendErr(VolumeBackendAPIException):
1123 message = _("Backend reports: %(message)s")
1124
1125
1126class ViolinBackendErrExists(VolumeBackendAPIException):
1127 message = _("Backend reports: item already exists")
1128
1129
1130class ViolinBackendErrNotFound(NotFound):
1131 message = _("Backend reports: item not found")
1132
1133
1134class ViolinResourceNotFound(NotFound):
1135 message = _("Backend reports: %(message)s")
1136
1137
1138class BadHTTPResponseStatus(VolumeDriverException):
1139 message = _("Bad HTTP response status %(status)s")
1140
1141
1142# ZADARA STORAGE VPSA driver exception
1143class ZadaraServerCreateFailure(VolumeDriverException):
1144 message = _("Unable to create server object for initiator %(name)s")
1145
1146
1147class ZadaraServerNotFound(NotFound):
1148 message = _("Unable to find server object for initiator %(name)s")
1149
1150
1151class ZadaraVPSANoActiveController(VolumeDriverException):
1152 message = _("Unable to find any active VPSA controller")
1153
1154
1155class ZadaraAttachmentsNotFound(NotFound):
1156 message = _("Failed to retrieve attachments for volume %(name)s")
1157
1158
1159class ZadaraInvalidAttachmentInfo(Invalid):
1160 message = _("Invalid attachment info for volume %(name)s: %(reason)s")
1161
1162
1163class ZadaraVolumeNotFound(VolumeDriverException):
1164 message = _("%(reason)s")
1165
1166
1167# ZFSSA NFS driver exception.
1168class WebDAVClientError(CinderException):
1169 message = _("The WebDAV request failed. Reason: %(msg)s, "
1170 "Return code/reason: %(code)s, Source Volume: %(src)s, "
1171 "Destination Volume: %(dst)s, Method: %(method)s.")
1172
1173
1174# XtremIO Drivers
1175class XtremIOAlreadyMappedError(CinderException):
1176 message = _("Volume to Initiator Group mapping already exists")
1177
1178
1179class XtremIOArrayBusy(CinderException):
1180 message = _("System is busy, retry operation.")
1181
1182
1183class XtremIOSnapshotsLimitExceeded(CinderException):
1184 message = _("Exceeded the limit of snapshots per volume")
1185
1186
1187# Infortrend EonStor DS Driver
1188class InfortrendCliException(CinderException):
1189 message = _("Infortrend CLI exception: %(err)s Param: %(param)s "
1190 "(Return Code: %(rc)s) (Output: %(out)s)")
1191
1192
1193# DOTHILL drivers
1194class DotHillInvalidBackend(CinderException):
1195 message = _("Backend doesn't exist (%(backend)s)")
1196
1197
1198class DotHillConnectionError(CinderException):
1199 message = _("%(message)s")
1200
1201
1202class DotHillAuthenticationError(CinderException):
1203 message = _("%(message)s")
1204
1205
1206class DotHillNotEnoughSpace(CinderException):
1207 message = _("Not enough space on backend (%(backend)s)")
1208
1209
1210class DotHillRequestError(CinderException):
1211 message = _("%(message)s")
1212
1213
1214class DotHillNotTargetPortal(CinderException):
1215 message = _("No active iSCSI portals with supplied iSCSI IPs")
1216
1217
1218# Sheepdog
1219class SheepdogError(VolumeBackendAPIException):
1220 message = _("An error has occurred in SheepdogDriver. "
1221 "(Reason: %(reason)s)")
1222
1223
1224class SheepdogCmdError(SheepdogError):
1225 message = _("(Command: %(cmd)s) "
1226 "(Return Code: %(exit_code)s) "
1227 "(Stdout: %(stdout)s) "
1228 "(Stderr: %(stderr)s)")
1229
1230
1231class MetadataAbsent(CinderException):
1232 message = _("There is no metadata in DB object.")
1233
1234
1235class NotSupportedOperation(Invalid):
1236 message = _("Operation not supported: %(operation)s.")
1237 code = 405
1238
1239
1240# Hitachi HNAS drivers
1241class HNASConnError(CinderException):
1242 message = _("%(message)s")
1243
1244
1245# Coho drivers
1246class CohoException(VolumeDriverException):
1247 message = _("Coho Data Cinder driver failure: %(message)s")
1248
1249
1250# Tegile Storage drivers
1251class TegileAPIException(VolumeBackendAPIException):
1252 message = _("Unexpected response from Tegile IntelliFlash API")
1253
1254
1255# NexentaStor driver exception
1256class NexentaException(VolumeDriverException):
1257 message = _("%(message)s")
1258
1259
1260# Google Cloud Storage(GCS) backup driver
1261class GCSConnectionFailure(BackupDriverException):
1262 message = _("Google Cloud Storage connection failure: %(reason)s")
1263
1264
1265class GCSApiFailure(BackupDriverException):
1266 message = _("Google Cloud Storage api failure: %(reason)s")
1267
1268
1269class GCSOAuth2Failure(BackupDriverException):
1270 message = _("Google Cloud Storage oauth2 failure: %(reason)s")
1271
1272
1273# Kaminario K2
1274class KaminarioCinderDriverException(VolumeDriverException):
1275 message = _("KaminarioCinderDriver failure: %(reason)s")
1276
1277
1278class KaminarioRetryableException(VolumeDriverException):
1279 message = _("Kaminario retryable exception: %(reason)s")
1280
1281
1282# Synology driver
1283class SynoAPIHTTPError(CinderException):
1284 message = _("HTTP exit code: [%(code)s]")
1285
1286
1287class SynoAuthError(CinderException):
1288 message = _("Synology driver authentication failed: %(reason)s.")
1289
1290
1291class SynoLUNNotExist(CinderException):
1292 message = _("LUN not found by UUID: %(uuid)s.")
diff --git a/deployment_scripts/puppet/modules/kaminario/files/kaminario_common.py b/deployment_scripts/puppet/modules/kaminario/files/kaminario_common.py
new file mode 100644
index 0000000..b93c54e
--- /dev/null
+++ b/deployment_scripts/puppet/modules/kaminario/files/kaminario_common.py
@@ -0,0 +1,893 @@
1# Copyright (c) 2016 by Kaminario Technologies, Ltd.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15"""Volume driver for Kaminario K2 all-flash arrays."""
16
17import math
18import re
19import threading
20
21import eventlet
22from oslo_config import cfg
23from oslo_log import log as logging
24from oslo_utils import importutils
25from oslo_utils import units
26from oslo_utils import versionutils
27import requests
28import six
29
30import cinder
31from cinder import exception
32from cinder.i18n import _, _LE, _LW, _LI
33from cinder.objects import fields
34from cinder import utils
35from cinder.volume.drivers.san import san
36from cinder.volume import utils as vol_utils
37
38krest = importutils.try_import("krest")
39
40K2_MIN_VERSION = '2.2.0'
41K2_LOCK_PREFIX = 'Kaminario'
42MAX_K2_RETRY = 5
43LOG = logging.getLogger(__name__)
44
45kaminario1_opts = [
46 cfg.StrOpt('kaminario_nodedup_substring',
47 default='K2-nodedup',
48 help="If volume-type name contains this substring "
49 "nodedup volume will be created, otherwise "
50 "dedup volume wil be created.",
51 deprecated_for_removal=True,
52 deprecated_reason="This option is deprecated in favour of "
53 "'kaminario:thin_prov_type' in extra-specs "
54 "and will be removed in the next release.")]
55kaminario2_opts = [
56 cfg.BoolOpt('auto_calc_max_oversubscription_ratio',
57 default=False,
58 help="K2 driver will calculate max_oversubscription_ratio "
59 "on setting this option as True.")]
60
61CONF = cfg.CONF
62CONF.register_opts(kaminario1_opts)
63
64K2HTTPError = requests.exceptions.HTTPError
65K2_RETRY_ERRORS = ("MC_ERR_BUSY", "MC_ERR_BUSY_SPECIFIC",
66 "MC_ERR_INPROGRESS", "MC_ERR_START_TIMEOUT")
67
68if krest:
69 class KrestWrap(krest.EndPoint):
70 def __init__(self, *args, **kwargs):
71 self.krestlock = threading.Lock()
72 super(KrestWrap, self).__init__(*args, **kwargs)
73
74 def _should_retry(self, err_code, err_msg):
75 if err_code == 400:
76 for er in K2_RETRY_ERRORS:
77 if er in err_msg:
78 LOG.debug("Retry ERROR: %d with status %s",
79 err_code, err_msg)
80 return True
81 return False
82
83 @utils.retry(exception.KaminarioRetryableException,
84 retries=MAX_K2_RETRY)
85 def _request(self, method, *args, **kwargs):
86 try:
87 LOG.debug("running through the _request wrapper...")
88 self.krestlock.acquire()
89 return super(KrestWrap, self)._request(method,
90 *args, **kwargs)
91 except K2HTTPError as err:
92 err_code = err.response.status_code
93 err_msg = err.response.text
94 if self._should_retry(err_code, err_msg):
95 raise exception.KaminarioRetryableException(
96 reason=six.text_type(err_msg))
97 raise
98 finally:
99 self.krestlock.release()
100
101
102def kaminario_logger(func):
103 """Return a function wrapper.
104
105 The wrapper adds log for entry and exit to the function.
106 """
107 def func_wrapper(*args, **kwargs):
108 LOG.debug('Entering %(function)s of %(class)s with arguments: '
109 ' %(args)s, %(kwargs)s',
110 {'class': args[0].__class__.__name__,
111 'function': func.__name__,
112 'args': args[1:],
113 'kwargs': kwargs})
114 ret = func(*args, **kwargs)
115 LOG.debug('Exiting %(function)s of %(class)s '
116 'having return value: %(ret)s',
117 {'class': args[0].__class__.__name__,
118 'function': func.__name__,
119 'ret': ret})
120 return ret
121 return func_wrapper
122
123
124class Replication(object):
125 def __init__(self, config, *args, **kwargs):
126 self.backend_id = config.get('backend_id')
127 self.login = config.get('login')
128 self.password = config.get('password')
129 self.rpo = config.get('rpo')
130
131
132class KaminarioCinderDriver(cinder.volume.driver.ISCSIDriver):
133 VENDOR = "Kaminario"
134 stats = {}
135
136 def __init__(self, *args, **kwargs):
137 super(KaminarioCinderDriver, self).__init__(*args, **kwargs)
138 self.configuration.append_config_values(san.san_opts)
139 self.configuration.append_config_values(kaminario2_opts)
140 self.replica = None
141 self._protocol = None
142 k2_lock_sfx = self.configuration.safe_get('volume_backend_name') or ''
143 self.k2_lock_name = "%s-%s" % (K2_LOCK_PREFIX, k2_lock_sfx)
144
145 def check_for_setup_error(self):
146 if krest is None:
147 msg = _("Unable to import 'krest' python module.")
148 LOG.error(msg)
149 raise exception.KaminarioCinderDriverException(reason=msg)
150 else:
151 conf = self.configuration
152 self.client = KrestWrap(conf.san_ip,
153 conf.san_login,
154 conf.san_password,
155 ssl_validate=False)
156 if self.replica:
157 self.target = KrestWrap(self.replica.backend_id,
158 self.replica.login,
159 self.replica.password,
160 ssl_validate=False)
161 v_rs = self.client.search("system/state")
162 if hasattr(v_rs, 'hits') and v_rs.total != 0:
163 ver = v_rs.hits[0].rest_api_version
164 ver_exist = versionutils.convert_version_to_int(ver)
165 ver_min = versionutils.convert_version_to_int(K2_MIN_VERSION)
166 if ver_exist < ver_min:
167 msg = _("K2 rest api version should be "
168 ">= %s.") % K2_MIN_VERSION
169 LOG.error(msg)
170 raise exception.KaminarioCinderDriverException(reason=msg)
171
172 else:
173 msg = _("K2 rest api version search failed.")
174 LOG.error(msg)
175 raise exception.KaminarioCinderDriverException(reason=msg)
176
177 @kaminario_logger
178 def _check_ops(self):
179 """Ensure that the options we care about are set."""
180 required_ops = ['san_ip', 'san_login', 'san_password']
181 for attr in required_ops:
182 if not getattr(self.configuration, attr, None):
183 raise exception.InvalidInput(reason=_('%s is not set.') % attr)
184
185 replica = self.configuration.safe_get('replication_device')
186 if replica and isinstance(replica, list):
187 replica_ops = ['backend_id', 'login', 'password', 'rpo']
188 for attr in replica_ops:
189 if attr not in replica[0]:
190 msg = _('replication_device %s is not set.') % attr
191 raise exception.InvalidInput(reason=msg)
192 self.replica = Replication(replica[0])
193
194 @kaminario_logger
195 def do_setup(self, context):
196 super(KaminarioCinderDriver, self).do_setup(context)
197 self._check_ops()
198
199 @kaminario_logger
200 def create_volume(self, volume):
201 """Volume creation in K2 needs a volume group.
202
203 - create a volume group
204 - create a volume in the volume group
205 """
206 vg_name = self.get_volume_group_name(volume.id)
207 vol_name = self.get_volume_name(volume.id)
208 prov_type = self._get_is_dedup(volume.get('volume_type'))
209 try:
210 LOG.debug("Creating volume group with name: %(name)s, "
211 "quota: unlimited and dedup_support: %(dedup)s",
212 {'name': vg_name, 'dedup': prov_type})
213
214 vg = self.client.new("volume_groups", name=vg_name, quota=0,
215 is_dedup=prov_type).save()
216 LOG.debug("Creating volume with name: %(name)s, size: %(size)s "
217 "GB, volume_group: %(vg)s",
218 {'name': vol_name, 'size': volume.size, 'vg': vg_name})
219 vol = self.client.new("volumes", name=vol_name,
220 size=volume.size * units.Mi,
221 volume_group=vg).save()
222 except Exception as ex:
223 vg_rs = self.client.search("volume_groups", name=vg_name)
224 if vg_rs.total != 0:
225 LOG.debug("Deleting vg: %s for failed volume in K2.", vg_name)
226 vg_rs.hits[0].delete()
227 LOG.exception(_LE("Creation of volume %s failed."), vol_name)
228 raise exception.KaminarioCinderDriverException(
229 reason=six.text_type(ex.message))
230
231 if self._get_is_replica(volume.volume_type) and self.replica:
232 self._create_volume_replica(volume, vg, vol, self.replica.rpo)
233
234 @kaminario_logger
235 def _create_volume_replica(self, volume, vg, vol, rpo):
236 """Volume replica creation in K2 needs session and remote volume.
237
238 - create a session
239 - create a volume in the volume group
240
241 """
242 session_name = self.get_session_name(volume.id)
243 rsession_name = self.get_rep_name(session_name)
244
245 rvg_name = self.get_rep_name(vg.name)
246 rvol_name = self.get_rep_name(vol.name)
247
248 k2peer_rs = self.client.search("replication/peer_k2arrays",
249 mgmt_host=self.replica.backend_id)
250 if hasattr(k2peer_rs, 'hits') and k2peer_rs.total != 0:
251 k2peer = k2peer_rs.hits[0]
252 else:
253 msg = _("Unable to find K2peer in source K2:")
254 LOG.error(msg)
255 raise exception.KaminarioCinderDriverException(reason=msg)
256 try:
257 LOG.debug("Creating source session with name: %(sname)s and "
258 " target session name: %(tname)s",
259 {'sname': session_name, 'tname': rsession_name})
260 src_ssn = self.client.new("replication/sessions")
261 src_ssn.replication_peer_k2array = k2peer
262 src_ssn.auto_configure_peer_volumes = "False"
263 src_ssn.local_volume_group = vg
264 src_ssn.replication_peer_volume_group_name = rvg_name
265 src_ssn.remote_replication_session_name = rsession_name
266 src_ssn.name = session_name
267 src_ssn.rpo = rpo
268 src_ssn.save()
269 LOG.debug("Creating remote volume with name: %s",
270 rvol_name)
271 self.client.new("replication/peer_volumes",
272 local_volume=vol,
273 name=rvol_name,
274 replication_session=src_ssn).save()
275 src_ssn.state = "in_sync"
276 src_ssn.save()
277 except Exception as ex:
278 LOG.exception(_LE("Replication for the volume %s has "
279 "failed."), vol.name)
280 self._delete_by_ref(self.client, "replication/sessions",
281 session_name, 'session')
282 self._delete_by_ref(self.target, "replication/sessions",
283 rsession_name, 'remote session')
284 self._delete_by_ref(self.target, "volumes",
285 rvol_name, 'remote volume')
286 self._delete_by_ref(self.client, "volumes", vol.name, "volume")
287 self._delete_by_ref(self.target, "volume_groups",
288 rvg_name, "remote vg")
289 self._delete_by_ref(self.client, "volume_groups", vg.name, "vg")
290 raise exception.KaminarioCinderDriverException(
291 reason=six.text_type(ex.message))
292
293 def _delete_by_ref(self, device, url, name, msg):
294 rs = device.search(url, name=name)
295 for result in rs.hits:
296 result.delete()
297 LOG.debug("Deleting %(msg)s: %(name)s", {'msg': msg, 'name': name})
298
299 @kaminario_logger
300 def _failover_volume(self, volume):
301 """Promoting a secondary volume to primary volume."""
302 session_name = self.get_session_name(volume.id)
303 rsession_name = self.get_rep_name(session_name)
304 tgt_ssn = self.target.search("replication/sessions",
305 name=rsession_name).hits[0]
306 if tgt_ssn.state == 'in_sync':
307 tgt_ssn.state = 'failed_over'
308 tgt_ssn.save()
309 LOG.debug("The target session: %s state is "
310 "changed to failed_over ", rsession_name)
311
312 @kaminario_logger
313 def failover_host(self, context, volumes, secondary_id=None):
314 """Failover to replication target."""
315 volume_updates = []
316 if secondary_id and secondary_id != self.replica.backend_id:
317 LOG.error(_LE("Kaminario driver received failover_host "
318 "request, But backend is non replicated device"))
319 raise exception.UnableToFailOver(reason=_("Failover requested "
320 "on non replicated "
321 "backend."))
322 for v in volumes:
323 vol_name = self.get_volume_name(v['id'])
324 rv = self.get_rep_name(vol_name)
325 if self.target.search("volumes", name=rv).total:
326 self._failover_volume(v)
327 volume_updates.append(
328 {'volume_id': v['id'],
329 'updates':
330 {'replication_status':
331 fields.ReplicationStatus.FAILED_OVER}})
332 else:
333 volume_updates.append({'volume_id': v['id'],
334 'updates': {'status': 'error', }})
335
336 return self.replica.backend_id, volume_updates
337
338 @kaminario_logger
339 def create_volume_from_snapshot(self, volume, snapshot):
340 """Create volume from snapshot.
341
342 - search for snapshot and retention_policy
343 - create a view from snapshot and attach view
344 - create a volume and attach volume
345 - copy data from attached view to attached volume
346 - detach volume and view and finally delete view
347 """
348 snap_name = self.get_snap_name(snapshot.id)
349 view_name = self.get_view_name(volume.id)
350 vol_name = self.get_volume_name(volume.id)
351 cview = src_attach_info = dest_attach_info = None
352 rpolicy = self.get_policy()
353 properties = utils.brick_get_connector_properties()
354 LOG.debug("Searching for snapshot: %s in K2.", snap_name)
355 snap_rs = self.client.search("snapshots", short_name=snap_name)
356 if hasattr(snap_rs, 'hits') and snap_rs.total != 0:
357 snap = snap_rs.hits[0]
358 LOG.debug("Creating a view: %(view)s from snapshot: %(snap)s",
359 {'view': view_name, 'snap': snap_name})
360 try:
361 cview = self.client.new("snapshots",
362 short_name=view_name,
363 source=snap, retention_policy=rpolicy,
364 is_exposable=True).save()
365 except Exception as ex:
366 LOG.exception(_LE("Creating a view: %(view)s from snapshot: "
367 "%(snap)s failed"), {"view": view_name,
368 "snap": snap_name})
369 raise exception.KaminarioCinderDriverException(
370 reason=six.text_type(ex.message))
371
372 else:
373 msg = _("Snapshot: %s search failed in K2.") % snap_name
374 LOG.error(msg)
375 raise exception.KaminarioCinderDriverException(reason=msg)
376
377 try:
378 conn = self.initialize_connection(cview, properties)
379 src_attach_info = self._connect_device(conn)
380 self.create_volume(volume)
381 conn = self.initialize_connection(volume, properties)
382 dest_attach_info = self._connect_device(conn)
383 vol_utils.copy_volume(src_attach_info['device']['path'],
384 dest_attach_info['device']['path'],
385 snapshot.volume.size * units.Ki,
386 self.configuration.volume_dd_blocksize,
387 sparse=True)
388 self.terminate_connection(volume, properties)
389 self.terminate_connection(cview, properties)
390 except Exception as ex:
391 self.terminate_connection(cview, properties)
392 self.terminate_connection(volume, properties)
393 cview.delete()
394 self.delete_volume(volume)
395 LOG.exception(_LE("Copy to volume: %(vol)s from view: %(view)s "
396 "failed"), {"vol": vol_name, "view": view_name})
397 raise exception.KaminarioCinderDriverException(
398 reason=six.text_type(ex.message))
399
400 @kaminario_logger
401 def create_cloned_volume(self, volume, src_vref):
402 """Create a clone from source volume.
403
404 - attach source volume
405 - create and attach new volume
406 - copy data from attached source volume to attached new volume
407 - detach both volumes
408 """
409 clone_name = self.get_volume_name(volume.id)
410 src_name = self.get_volume_name(src_vref.id)
411 src_vol = self.client.search("volumes", name=src_name)
412 src_map = self.client.search("mappings", volume=src_vol)
413 if src_map.total != 0:
414 msg = _("K2 driver does not support clone of a attached volume. "
415 "To get this done, create a snapshot from the attached "
416 "volume and then create a volume from the snapshot.")
417 LOG.error(msg)
418 raise exception.KaminarioCinderDriverException(reason=msg)
419 try:
420 properties = utils.brick_get_connector_properties()
421 conn = self.initialize_connection(src_vref, properties)
422 src_attach_info = self._connect_device(conn)
423 self.create_volume(volume)
424 conn = self.initialize_connection(volume, properties)
425 dest_attach_info = self._connect_device(conn)
426 vol_utils.copy_volume(src_attach_info['device']['path'],
427 dest_attach_info['device']['path'],
428 src_vref.size * units.Ki,
429 self.configuration.volume_dd_blocksize,
430 sparse=True)
431
432 self.terminate_connection(volume, properties)
433 self.terminate_connection(src_vref, properties)
434 except Exception as ex:
435 self.terminate_connection(src_vref, properties)
436 self.terminate_connection(volume, properties)
437 self.delete_volume(volume)
438 LOG.exception(_LE("Create a clone: %s failed."), clone_name)
439 raise exception.KaminarioCinderDriverException(
440 reason=six.text_type(ex.message))
441
442 @kaminario_logger
443 def delete_volume(self, volume):
444 """Volume in K2 exists in a volume group.
445
446 - delete the volume
447 - delete the corresponding volume group
448 """
449 vg_name = self.get_volume_group_name(volume.id)
450 vol_name = self.get_volume_name(volume.id)
451 try:
452 if self._get_is_replica(volume.volume_type) and self.replica:
453 self._delete_volume_replica(volume, vg_name, vol_name)
454
455 LOG.debug("Searching and deleting volume: %s in K2.", vol_name)
456 vol_rs = self.client.search("volumes", name=vol_name)
457 if vol_rs.total != 0:
458 vol_rs.hits[0].delete()
459 LOG.debug("Searching and deleting vg: %s in K2.", vg_name)
460 vg_rs = self.client.search("volume_groups", name=vg_name)
461 if vg_rs.total != 0:
462 vg_rs.hits[0].delete()
463 except Exception as ex:
464 LOG.exception(_LE("Deletion of volume %s failed."), vol_name)
465 raise exception.KaminarioCinderDriverException(
466 reason=six.text_type(ex.message))
467
468 @kaminario_logger
469 def _delete_volume_replica(self, volume, vg_name, vol_name):
470 rvg_name = self.get_rep_name(vg_name)
471 rvol_name = self.get_rep_name(vol_name)
472 session_name = self.get_session_name(volume.id)
473 rsession_name = self.get_rep_name(session_name)
474 src_ssn = self.client.search('replication/sessions',
475 name=session_name).hits[0]
476 tgt_ssn = self.target.search('replication/sessions',
477 name=rsession_name).hits[0]
478 src_ssn.state = 'suspended'
479 src_ssn.save()
480 self._check_for_status(tgt_ssn, 'suspended')
481 src_ssn.state = 'idle'
482 src_ssn.save()
483 self._check_for_status(tgt_ssn, 'idle')
484 tgt_ssn.delete()
485 src_ssn.delete()
486
487 LOG.debug("Searching and deleting snapshots for volume groups:"
488 "%(vg1)s, %(vg2)s in K2.", {'vg1': vg_name, 'vg2': rvg_name})
489 vg = self.client.search('volume_groups', name=vg_name).hits
490 rvg = self.target.search('volume_groups', name=rvg_name).hits
491 snaps = self.client.search('snapshots', volume_group=vg).hits
492 for s in snaps:
493 s.delete()
494 rsnaps = self.target.search('snapshots', volume_group=rvg).hits
495 for s in rsnaps:
496 s.delete()
497
498 self._delete_by_ref(self.target, "volumes", rvol_name, 'remote volume')
499 self._delete_by_ref(self.target, "volume_groups",
500 rvg_name, "remote vg")
501
502 @kaminario_logger
503 def _check_for_status(self, obj, status):
504 while obj.state != status:
505 obj.refresh()
506 eventlet.sleep(1)
507
508 @kaminario_logger
509 def get_volume_stats(self, refresh=False):
510 if refresh:
511 self.update_volume_stats()
512 stats = self.stats
513 stats['storage_protocol'] = self._protocol
514 stats['driver_version'] = self.VERSION
515 stats['vendor_name'] = self.VENDOR
516 backend_name = self.configuration.safe_get('volume_backend_name')
517 stats['volume_backend_name'] = (backend_name or
518 self.__class__.__name__)
519 return stats
520
521 def create_export(self, context, volume, connector):
522 pass
523
524 def ensure_export(self, context, volume):
525 pass
526
527 def remove_export(self, context, volume):
528 pass
529
530 @kaminario_logger
531 def create_snapshot(self, snapshot):
532 """Create a snapshot from a volume_group."""
533 vg_name = self.get_volume_group_name(snapshot.volume_id)
534 snap_name = self.get_snap_name(snapshot.id)
535 rpolicy = self.get_policy()
536 try:
537 LOG.debug("Searching volume_group: %s in K2.", vg_name)
538 vg = self.client.search("volume_groups", name=vg_name).hits[0]
539 LOG.debug("Creating a snapshot: %(snap)s from vg: %(vg)s",
540 {'snap': snap_name, 'vg': vg_name})
541 self.client.new("snapshots", short_name=snap_name,
542 source=vg, retention_policy=rpolicy,
543 is_auto_deleteable=False).save()
544 except Exception as ex:
545 LOG.exception(_LE("Creation of snapshot: %s failed."), snap_name)
546 raise exception.KaminarioCinderDriverException(
547 reason=six.text_type(ex.message))
548
549 @kaminario_logger
550 def delete_snapshot(self, snapshot):
551 """Delete a snapshot."""
552 snap_name = self.get_snap_name(snapshot.id)
553 try:
554 LOG.debug("Searching and deleting snapshot: %s in K2.", snap_name)
555 snap_rs = self.client.search("snapshots", short_name=snap_name)
556 if snap_rs.total != 0:
557 snap_rs.hits[0].delete()
558 except Exception as ex:
559 LOG.exception(_LE("Deletion of snapshot: %s failed."), snap_name)
560 raise exception.KaminarioCinderDriverException(
561 reason=six.text_type(ex.message))
562
563 @kaminario_logger
564 def extend_volume(self, volume, new_size):
565 """Extend volume."""
566 vol_name = self.get_volume_name(volume.id)
567 try:
568 LOG.debug("Searching volume: %s in K2.", vol_name)
569 vol = self.client.search("volumes", name=vol_name).hits[0]
570 vol.size = new_size * units.Mi
571 LOG.debug("Extending volume: %s in K2.", vol_name)
572 vol.save()
573 except Exception as ex:
574 LOG.exception(_LE("Extending volume: %s failed."), vol_name)
575 raise exception.KaminarioCinderDriverException(
576 reason=six.text_type(ex.message))
577
578 @kaminario_logger
579 def update_volume_stats(self):
580 conf = self.configuration
581 LOG.debug("Searching system capacity in K2.")
582 cap = self.client.search("system/capacity").hits[0]
583 LOG.debug("Searching total volumes in K2 for updating stats.")
584 total_volumes = self.client.search("volumes").total - 1
585 provisioned_vol = cap.provisioned_volumes
586 if (conf.auto_calc_max_oversubscription_ratio and cap.provisioned
587 and (cap.total - cap.free) != 0):
588 ratio = provisioned_vol / float(cap.total - cap.free)
589 else:
590 ratio = conf.max_over_subscription_ratio
591 self.stats = {'QoS_support': False,
592 'free_capacity_gb': cap.free / units.Mi,
593 'total_capacity_gb': cap.total / units.Mi,
594 'thin_provisioning_support': True,
595 'sparse_copy_volume': True,
596 'total_volumes': total_volumes,
597 'thick_provisioning_support': False,
598 'provisioned_capacity_gb': provisioned_vol / units.Mi,
599 'max_oversubscription_ratio': ratio,
600 'kaminario:thin_prov_type': 'dedup/nodedup',
601 'replication_enabled': True,
602 'kaminario:replication': True}
603
604 @kaminario_logger
605 def get_initiator_host_name(self, connector):
606 """Return the initiator host name.
607
608 Valid characters: 0-9, a-z, A-Z, '-', '_'
609 All other characters are replaced with '_'.
610 Total characters in initiator host name: 32
611 """
612 return re.sub('[^0-9a-zA-Z-_]', '_', connector.get('host', ''))[:32]
613
614 @kaminario_logger
615 def get_volume_group_name(self, vid):
616 """Return the volume group name."""
617 return "cvg-{0}".format(vid)
618
619 @kaminario_logger
620 def get_volume_name(self, vid):
621 """Return the volume name."""
622 return "cv-{0}".format(vid)
623
624 @kaminario_logger
625 def get_session_name(self, vid):
626 """Return the volume name."""
627 return "ssn-{0}".format(vid)
628
629 @kaminario_logger
630 def get_snap_name(self, sid):
631 """Return the snapshot name."""
632 return "cs-{0}".format(sid)
633
634 @kaminario_logger
635 def get_view_name(self, vid):
636 """Return the view name."""
637 return "cview-{0}".format(vid)
638
639 @kaminario_logger
640 def get_rep_name(self, name):
641 """Return the corresponding replication names."""
642 return "r{0}".format(name)
643
644 @kaminario_logger
645 def _delete_host_by_name(self, name):
646 """Deleting host by name."""
647 host_rs = self.client.search("hosts", name=name)
648 if hasattr(host_rs, "hits") and host_rs.total != 0:
649 host = host_rs.hits[0]
650 host.delete()
651
652 @kaminario_logger
653 def get_policy(self):
654 """Return the retention policy."""
655 try:
656 LOG.debug("Searching for retention_policy in K2.")
657 return self.client.search("retention_policies",
658 name="Best_Effort_Retention").hits[0]
659 except Exception as ex:
660 LOG.exception(_LE("Retention policy search failed in K2."))
661 raise exception.KaminarioCinderDriverException(
662 reason=six.text_type(ex.message))
663
664 @kaminario_logger
665 def _get_volume_object(self, volume):
666 vol_name = self.get_volume_name(volume.id)
667 if volume.replication_status == 'failed-over':
668 vol_name = self.get_rep_name(vol_name)
669 self.client = self.target
670 LOG.debug("Searching volume : %s in K2.", vol_name)
671 vol_rs = self.client.search("volumes", name=vol_name)
672 if not hasattr(vol_rs, 'hits') or vol_rs.total == 0:
673 msg = _("Unable to find volume: %s from K2.") % vol_name
674 LOG.error(msg)
675 raise exception.KaminarioCinderDriverException(reason=msg)
676 return vol_rs.hits[0]
677
678 @kaminario_logger
679 def _get_lun_number(self, vol, host):
680 volsnap = None
681 LOG.debug("Searching volsnaps in K2.")
682 volsnap_rs = self.client.search("volsnaps", snapshot=vol)
683 if hasattr(volsnap_rs, 'hits') and volsnap_rs.total != 0:
684 volsnap = volsnap_rs.hits[0]
685
686 LOG.debug("Searching mapping of volsnap in K2.")
687 map_rs = self.client.search("mappings", volume=volsnap, host=host)
688 return map_rs.hits[0].lun
689
690 def initialize_connection(self, volume, connector):
691 pass
692
693 @kaminario_logger
694 def terminate_connection(self, volume, connector):
695 """Terminate connection of volume from host."""
696 # Get volume object
697 if type(volume).__name__ != 'RestObject':
698 vol_name = self.get_volume_name(volume.id)
699 if volume.replication_status == 'failed-over':
700 vol_name = self.get_rep_name(vol_name)
701 self.client = self.target
702 LOG.debug("Searching volume: %s in K2.", vol_name)
703 volume_rs = self.client.search("volumes", name=vol_name)
704 if hasattr(volume_rs, "hits") and volume_rs.total != 0:
705 volume = volume_rs.hits[0]
706 else:
707 vol_name = volume.name
708
709 # Get host object.
710 host_name = self.get_initiator_host_name(connector)
711 host_rs = self.client.search("hosts", name=host_name)
712 if hasattr(host_rs, "hits") and host_rs.total != 0 and volume:
713 host = host_rs.hits[0]
714 LOG.debug("Searching and deleting mapping of volume: %(name)s to "
715 "host: %(host)s", {'host': host_name, 'name': vol_name})
716 map_rs = self.client.search("mappings", volume=volume, host=host)
717 if hasattr(map_rs, "hits") and map_rs.total != 0:
718 map_rs.hits[0].delete()
719 if self.client.search("mappings", host=host).total == 0:
720 LOG.debug("Deleting initiator hostname: %s in K2.", host_name)
721 host.delete()
722 else:
723 LOG.warning(_LW("Host: %s not found on K2."), host_name)
724
725 def k2_initialize_connection(self, volume, connector):
726 # Get volume object.
727 if type(volume).__name__ != 'RestObject':
728 vol = self._get_volume_object(volume)
729 else:
730 vol = volume
731 # Get host object.
732 host, host_rs, host_name = self._get_host_object(connector)
733 try:
734 # Map volume object to host object.
735 LOG.debug("Mapping volume: %(vol)s to host: %(host)s",
736 {'host': host_name, 'vol': vol.name})
737 mapping = self.client.new("mappings", volume=vol, host=host).save()
738 except Exception as ex:
739 if host_rs.total == 0:
740 self._delete_host_by_name(host_name)
741 LOG.exception(_LE("Unable to map volume: %(vol)s to host: "
742 "%(host)s"), {'host': host_name,
743 'vol': vol.name})
744 raise exception.KaminarioCinderDriverException(
745 reason=six.text_type(ex.message))
746 # Get lun number.
747 if type(volume).__name__ == 'RestObject':
748 return self._get_lun_number(vol, host)
749 else:
750 return mapping.lun
751
752 def _get_host_object(self, connector):
753 pass
754
755 def _get_is_dedup(self, vol_type):
756 if vol_type:
757 specs_val = vol_type.get('extra_specs', {}).get(
758 'kaminario:thin_prov_type')
759 if specs_val == 'nodedup':
760 return False
761 elif CONF.kaminario_nodedup_substring in vol_type.get('name'):
762 LOG.info(_LI("'kaminario_nodedup_substring' option is "
763 "deprecated in favour of 'kaminario:thin_prov_"
764 "type' in extra-specs and will be removed in "
765 "the 10.0.0 release."))
766 return False
767 else:
768 return True
769 else:
770 return True
771
772 def _get_is_replica(self, vol_type):
773 replica = False
774 if vol_type and vol_type.get('extra_specs'):
775 specs = vol_type.get('extra_specs')
776 if (specs.get('kaminario:replication') == 'enabled' and
777 self.replica):
778 replica = True
779 return replica
780
781 def _get_replica_status(self, vg_name):
782 vg = self.client.search("volume_groups", name=vg_name).hits[0]
783 if self.client.search("replication/sessions",
784 local_volume_group=vg).total != 0:
785 return True
786 else:
787 return False
788
789 def manage_existing(self, volume, existing_ref):
790 vol_name = existing_ref['source-name']
791 new_name = self.get_volume_name(volume.id)
792 vg_new_name = self.get_volume_group_name(volume.id)
793 vg_name = None
794 is_dedup = self._get_is_dedup(volume.get('volume_type'))
795 try:
796 LOG.debug("Searching volume: %s in K2.", vol_name)
797 vol = self.client.search("volumes", name=vol_name).hits[0]
798 vg = vol.volume_group
799 vg_replica = self._get_replica_status(vg.name)
800 vol_map = False
801 if self.client.search("mappings", volume=vol).total != 0:
802 vol_map = True
803 if is_dedup != vg.is_dedup or vg_replica or vol_map:
804 raise exception.ManageExistingInvalidReference(
805 existing_ref=existing_ref,
806 reason=_('Manage volume type invalid.'))
807 vol.name = new_name
808 vg_name = vg.name
809 LOG.debug("Manage new volume name: %s", new_name)
810 vg.name = vg_new_name
811 LOG.debug("Manage volume group name: %s", vg_new_name)
812 vg.save()
813 LOG.debug("Manage volume: %s in K2.", vol_name)
814 vol.save()
815 except Exception as ex:
816 vg_rs = self.client.search("volume_groups", name=vg_new_name)
817 if hasattr(vg_rs, 'hits') and vg_rs.total != 0:
818 vg = vg_rs.hits[0]
819 if vg_name and vg.name == vg_new_name:
820 vg.name = vg_name
821 LOG.debug("Updating vg new name to old name: %s ", vg_name)
822 vg.save()
823 LOG.exception(_LE("manage volume: %s failed."), vol_name)
824 raise exception.ManageExistingInvalidReference(
825 existing_ref=existing_ref,
826 reason=six.text_type(ex.message))
827
828 def manage_existing_get_size(self, volume, existing_ref):
829 vol_name = existing_ref['source-name']
830 v_rs = self.client.search("volumes", name=vol_name)
831 if hasattr(v_rs, 'hits') and v_rs.total != 0:
832 vol = v_rs.hits[0]
833 size = vol.size / units.Mi
834 return math.ceil(size)
835 else:
836 raise exception.ManageExistingInvalidReference(
837 existing_ref=existing_ref,
838 reason=_('Unable to get size of manage volume.'))
839
840 def after_volume_copy(self, ctxt, volume, new_volume, remote=None):
841 self.delete_volume(volume)
842 vg_name_old = self.get_volume_group_name(volume.id)
843 vol_name_old = self.get_volume_name(volume.id)
844 vg_name_new = self.get_volume_group_name(new_volume.id)
845 vol_name_new = self.get_volume_name(new_volume.id)
846 vg_new = self.client.search("volume_groups", name=vg_name_new).hits[0]
847 vg_new.name = vg_name_old
848 vg_new.save()
849 vol_new = self.client.search("volumes", name=vol_name_new).hits[0]
850 vol_new.name = vol_name_old
851 vol_new.save()
852
853 def retype(self, ctxt, volume, new_type, diff, host):
854 old_type = volume.get('volume_type')
855 vg_name = self.get_volume_group_name(volume.id)
856 old_rep_type = self._get_replica_status(vg_name)
857 new_rep_type = self._get_is_replica(new_type)
858 new_prov_type = self._get_is_dedup(new_type)
859 old_prov_type = self._get_is_dedup(old_type)
860 # Change dedup<->nodedup with add/remove replication is complex in K2
861 # since K2 does not have api to change dedup<->nodedup.
862 if new_prov_type == old_prov_type:
863 if not old_rep_type and new_rep_type:
864 self._add_replication(volume)
865 return True
866 elif old_rep_type and not new_rep_type:
867 self._delete_replication(volume)
868 return True
869 elif not new_rep_type and not old_rep_type:
870 LOG.debug("Use '--migration-policy on-demand' to change 'dedup "
871 "without replication'<->'nodedup without replication'.")
872 return False
873 else:
874 LOG.error(_LE('Change from type1: %(type1)s to type2: %(type2)s '
875 'is not supported directly in K2.'),
876 {'type1': old_type, 'type2': new_type})
877 return False
878
879 def _add_replication(self, volume):
880 vg_name = self.get_volume_group_name(volume.id)
881 vol_name = self.get_volume_name(volume.id)
882 LOG.debug("Searching volume group with name: %(name)s",
883 {'name': vg_name})
884 vg = self.client.search("volume_groups", name=vg_name).hits[0]
885 LOG.debug("Searching volume with name: %(name)s",
886 {'name': vol_name})
887 vol = self.client.search("volumes", name=vol_name).hits[0]
888 self._create_volume_replica(volume, vg, vol, self.replica.rpo)
889
890 def _delete_replication(self, volume):
891 vg_name = self.get_volume_group_name(volume.id)
892 vol_name = self.get_volume_name(volume.id)
893 self._delete_volume_replica(volume, vg_name, vol_name)
diff --git a/deployment_scripts/puppet/modules/kaminario/files/kaminario_fc.py b/deployment_scripts/puppet/modules/kaminario/files/kaminario_fc.py
new file mode 100644
index 0000000..fbaff41
--- /dev/null
+++ b/deployment_scripts/puppet/modules/kaminario/files/kaminario_fc.py
@@ -0,0 +1,184 @@
1# Copyright (c) 2016 by Kaminario Technologies, Ltd.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15"""Volume driver for Kaminario K2 all-flash arrays."""
16import six
17
18from oslo_log import log as logging
19
20from cinder import coordination
21from cinder import exception
22from cinder.i18n import _, _LE
23from cinder.objects import fields
24from cinder.volume.drivers.kaminario import kaminario_common as common
25from cinder.zonemanager import utils as fczm_utils
26
27LOG = logging.getLogger(__name__)
28kaminario_logger = common.kaminario_logger
29
30
31class KaminarioFCDriver(common.KaminarioCinderDriver):
32 """Kaminario K2 FC Volume Driver.
33
34 Version history:
35 1.0 - Initial driver
36 1.1 - Added manage/unmanage and extra-specs support for nodedup
37 1.2 - Added replication support
38 1.3 - Added retype support
39 """
40
41 VERSION = '1.3'
42
43 # ThirdPartySystems wiki page name
44 CI_WIKI_NAME = "Kaminario_K2_CI"
45
46 @kaminario_logger
47 def __init__(self, *args, **kwargs):
48 super(KaminarioFCDriver, self).__init__(*args, **kwargs)
49 self._protocol = 'FC'
50 self.lookup_service = fczm_utils.create_lookup_service()
51
52 @fczm_utils.AddFCZone
53 @kaminario_logger
54 @coordination.synchronized('{self.k2_lock_name}')
55 def initialize_connection(self, volume, connector):
56 """Attach K2 volume to host."""
57 # Check wwpns in host connector.
58 if not connector.get('wwpns'):
59 msg = _("No wwpns found in host connector.")
60 LOG.error(msg)
61 raise exception.KaminarioCinderDriverException(reason=msg)
62 # Get target wwpns.
63 target_wwpns = self.get_target_info(volume)
64 # Map volume.
65 lun = self.k2_initialize_connection(volume, connector)
66 # Create initiator-target mapping.
67 target_wwpns, init_target_map = self._build_initiator_target_map(
68 connector, target_wwpns)
69 # Return target volume information.
70 return {'driver_volume_type': 'fibre_channel',
71 'data': {"target_discovered": True,
72 "target_lun": lun,
73 "target_wwn": target_wwpns,
74 "initiator_target_map": init_target_map}}
75
76 @fczm_utils.RemoveFCZone
77 @kaminario_logger
78 @coordination.synchronized('{self.k2_lock_name}')
79 def terminate_connection(self, volume, connector, **kwargs):
80 super(KaminarioFCDriver, self).terminate_connection(volume, connector)
81 properties = {"driver_volume_type": "fibre_channel", "data": {}}
82 host_name = self.get_initiator_host_name(connector)
83 host_rs = self.client.search("hosts", name=host_name)
84 # In terminate_connection, host_entry is deleted if host
85 # is not attached to any volume
86 if host_rs.total == 0:
87 # Get target wwpns.
88 target_wwpns = self.get_target_info(volume)
89 target_wwpns, init_target_map = self._build_initiator_target_map(
90 connector, target_wwpns)
91 properties["data"] = {"target_wwn": target_wwpns,
92 "initiator_target_map": init_target_map}
93 return properties
94
95 @kaminario_logger
96 def get_target_info(self, volume):
97 rep_status = fields.ReplicationStatus.FAILED_OVER
98 if (hasattr(volume, 'replication_status') and
99 volume.replication_status == rep_status):
100 self.client = self.target
101 LOG.debug("Searching target wwpns in K2.")
102 fc_ports_rs = self.client.search("system/fc_ports")
103 target_wwpns = []
104 if hasattr(fc_ports_rs, 'hits') and fc_ports_rs.total != 0:
105 for port in fc_ports_rs.hits:
106 if port.pwwn:
107 target_wwpns.append((port.pwwn).replace(':', ''))
108 if not target_wwpns:
109 msg = _("Unable to get FC target wwpns from K2.")
110 LOG.error(msg)
111 raise exception.KaminarioCinderDriverException(reason=msg)
112 return target_wwpns
113
114 @kaminario_logger
115 def _get_host_object(self, connector):
116 host_name = self.get_initiator_host_name(connector)
117 LOG.debug("Searching initiator hostname: %s in K2.", host_name)
118 host_rs = self.client.search("hosts", name=host_name)
119 host_wwpns = connector['wwpns']
120 if host_rs.total == 0:
121 try:
122 LOG.debug("Creating initiator hostname: %s in K2.", host_name)
123 host = self.client.new("hosts", name=host_name,
124 type="Linux").save()
125 except Exception as ex:
126 LOG.exception(_LE("Unable to create host : %s in K2."),
127 host_name)
128 raise exception.KaminarioCinderDriverException(
129 reason=six.text_type(ex.message))
130 else:
131 # Use existing host.
132 LOG.debug("Use existing initiator hostname: %s in K2.", host_name)
133 host = host_rs.hits[0]
134 # Adding host wwpn.
135 for wwpn in host_wwpns:
136 wwpn = ":".join([wwpn[i:i + 2] for i in range(0, len(wwpn), 2)])
137 if self.client.search("host_fc_ports", pwwn=wwpn,
138 host=host).total == 0:
139 LOG.debug("Adding wwpn: %(wwpn)s to host: "
140 "%(host)s in K2.", {'wwpn': wwpn,
141 'host': host_name})
142 try:
143 self.client.new("host_fc_ports", pwwn=wwpn,
144 host=host).save()
145 except Exception as ex:
146 if host_rs.total == 0:
147 self._delete_host_by_name(host_name)
148 LOG.exception(_LE("Unable to add wwpn : %(wwpn)s to "
149 "host: %(host)s in K2."),
150 {'wwpn': wwpn, 'host': host_name})
151 raise exception.KaminarioCinderDriverException(
152 reason=six.text_type(ex.message))
153 return host, host_rs, host_name
154
155 @kaminario_logger
156 def _build_initiator_target_map(self, connector, all_target_wwns):
157 """Build the target_wwns and the initiator target map."""
158 target_wwns = []
159 init_targ_map = {}
160
161 if self.lookup_service is not None:
162 # use FC san lookup.
163 dev_map = self.lookup_service.get_device_mapping_from_network(
164 connector.get('wwpns'),
165 all_target_wwns)
166
167 for fabric_name in dev_map:
168 fabric = dev_map[fabric_name]
169 target_wwns += fabric['target_port_wwn_list']
170 for initiator in fabric['initiator_port_wwn_list']:
171 if initiator not in init_targ_map:
172 init_targ_map[initiator] = []
173 init_targ_map[initiator] += fabric['target_port_wwn_list']
174 init_targ_map[initiator] = list(set(
175 init_targ_map[initiator]))
176 target_wwns = list(set(target_wwns))
177 else:
178 initiator_wwns = connector.get('wwpns', [])
179 target_wwns = all_target_wwns
180
181 for initiator in initiator_wwns:
182 init_targ_map[initiator] = target_wwns
183
184 return target_wwns, init_targ_map
diff --git a/deployment_scripts/puppet/modules/kaminario/files/kaminario_iscsi.py b/deployment_scripts/puppet/modules/kaminario/files/kaminario_iscsi.py
new file mode 100644
index 0000000..7a5190e
--- /dev/null
+++ b/deployment_scripts/puppet/modules/kaminario/files/kaminario_iscsi.py
@@ -0,0 +1,127 @@
1# Copyright (c) 2016 by Kaminario Technologies, Ltd.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15"""Volume driver for Kaminario K2 all-flash arrays."""
16import six
17
18from oslo_log import log as logging
19
20from cinder import coordination
21from cinder import exception
22from cinder.i18n import _, _LE
23from cinder import interface
24from cinder.objects import fields
25from cinder.volume.drivers.kaminario import kaminario_common as common
26
27ISCSI_TCP_PORT = "3260"
28LOG = logging.getLogger(__name__)
29kaminario_logger = common.kaminario_logger
30
31
32@interface.volumedriver
33class KaminarioISCSIDriver(common.KaminarioCinderDriver):
34 """Kaminario K2 iSCSI Volume Driver.
35
36 Version history:
37 1.0 - Initial driver
38 1.1 - Added manage/unmanage and extra-specs support for nodedup
39 1.2 - Added replication support
40 1.3 - Added retype support
41 """
42
43 VERSION = '1.3'
44
45 # ThirdPartySystems wiki page name
46 CI_WIKI_NAME = "Kaminario_K2_CI"
47
48 @kaminario_logger
49 def __init__(self, *args, **kwargs):
50 super(KaminarioISCSIDriver, self).__init__(*args, **kwargs)
51 self._protocol = 'iSCSI'
52
53 @kaminario_logger
54 @coordination.synchronized('{self.k2_lock_name}')
55 def initialize_connection(self, volume, connector):
56 """Attach K2 volume to host."""
57 # Get target_portal and target iqn.
58 iscsi_portal, target_iqn = self.get_target_info(volume)
59 # Map volume.
60 lun = self.k2_initialize_connection(volume, connector)
61 # Return target volume information.
62 return {"driver_volume_type": "iscsi",
63 "data": {"target_iqn": target_iqn,
64 "target_portal": iscsi_portal,
65 "target_lun": lun,
66 "target_discovered": True}}
67
68 @kaminario_logger
69 @coordination.synchronized('{self.k2_lock_name}')
70 def terminate_connection(self, volume, connector, **kwargs):
71 super(KaminarioISCSIDriver, self).terminate_connection(volume,
72 connector)
73
74 @kaminario_logger
75 def get_target_info(self, volume):
76 rep_status = fields.ReplicationStatus.FAILED_OVER
77 if (hasattr(volume, 'replication_status') and
78 volume.replication_status == rep_status):
79 self.client = self.target
80 LOG.debug("Searching first iscsi port ip without wan in K2.")
81 iscsi_ip_rs = self.client.search("system/net_ips", wan_port="")
82 iscsi_ip = target_iqn = None
83 if hasattr(iscsi_ip_rs, 'hits') and iscsi_ip_rs.total != 0:
84 iscsi_ip = iscsi_ip_rs.hits[0].ip_address
85 if not iscsi_ip:
86 msg = _("Unable to get ISCSI IP address from K2.")
87 LOG.error(msg)
88 raise exception.KaminarioCinderDriverException(reason=msg)
89 iscsi_portal = "{0}:{1}".format(iscsi_ip, ISCSI_TCP_PORT)
90 LOG.debug("Searching system state for target iqn in K2.")
91 sys_state_rs = self.client.search("system/state")
92
93 if hasattr(sys_state_rs, 'hits') and sys_state_rs.total != 0:
94 target_iqn = sys_state_rs.hits[0].iscsi_qualified_target_name
95
96 if not target_iqn:
97 msg = _("Unable to get target iqn from K2.")
98 LOG.error(msg)
99 raise exception.KaminarioCinderDriverException(reason=msg)
100 return iscsi_portal, target_iqn
101
102 @kaminario_logger
103 def _get_host_object(self, connector):
104 host_name = self.get_initiator_host_name(connector)
105 LOG.debug("Searching initiator hostname: %s in K2.", host_name)
106 host_rs = self.client.search("hosts", name=host_name)
107 """Create a host if not exists."""
108 if host_rs.total == 0:
109 try:
110 LOG.debug("Creating initiator hostname: %s in K2.", host_name)
111 host = self.client.new("hosts", name=host_name,
112 type="Linux").save()
113 LOG.debug("Adding iqn: %(iqn)s to host: %(host)s in K2.",
114 {'iqn': connector['initiator'], 'host': host_name})
115 iqn = self.client.new("host_iqns", iqn=connector['initiator'],
116 host=host)
117 iqn.save()
118 except Exception as ex:
119 self._delete_host_by_name(host_name)
120 LOG.exception(_LE("Unable to create host: %s in K2."),
121 host_name)
122 raise exception.KaminarioCinderDriverException(
123 reason=six.text_type(ex.message))
124 else:
125 LOG.debug("Use existing initiator hostname: %s in K2.", host_name)
126 host = host_rs.hits[0]
127 return host, host_rs, host_name
diff --git a/deployment_scripts/puppet/modules/kaminario/lib/puppet/parser/functions/get_replication_device.rb b/deployment_scripts/puppet/modules/kaminario/lib/puppet/parser/functions/get_replication_device.rb
new file mode 100644
index 0000000..6837844
--- /dev/null
+++ b/deployment_scripts/puppet/modules/kaminario/lib/puppet/parser/functions/get_replication_device.rb
@@ -0,0 +1,11 @@
1module Puppet::Parser::Functions
2 newfunction(:get_replication_device, :type => :rvalue) do |args|
3 ip = args[0].to_s
4 login = args[1]
5 password = args[2]
6 rpo = args[3]
7 replication_device = 'backend_id' + ':' + ip + "," + 'login' + ':' + login + "," + 'password' + ':' + password + "," + 'rpo' + ':' + rpo
8 return replication_device
9 end
10end
11
diff --git a/deployment_scripts/puppet/modules/kaminario/lib/puppet/parser/functions/section_name.rb b/deployment_scripts/puppet/modules/kaminario/lib/puppet/parser/functions/section_name.rb
new file mode 100644
index 0000000..366f001
--- /dev/null
+++ b/deployment_scripts/puppet/modules/kaminario/lib/puppet/parser/functions/section_name.rb
@@ -0,0 +1,9 @@
1module Puppet::Parser::Functions
2 newfunction(:section_name, :type => :rvalue) do |args|
3 ip = args[0]
4 str = args[1]
5 sec_name = str + '_' + ip
6 return sec_name
7 end
8end
9
diff --git a/deployment_scripts/puppet/modules/kaminario/manifests/driver.pp b/deployment_scripts/puppet/modules/kaminario/manifests/driver.pp
new file mode 100644
index 0000000..ef583e7
--- /dev/null
+++ b/deployment_scripts/puppet/modules/kaminario/manifests/driver.pp
@@ -0,0 +1,39 @@
1class kaminario::driver{
2
3file { '/usr/lib/python2.7/dist-packages/cinder/volume/drivers/kaminario':
4 ensure => 'directory',
5 owner => 'root',
6 group => 'root',
7 mode => '0755',}
8
9file { '/usr/lib/python2.7/dist-packages/cinder/volume/drivers/kaminario/__init__.py':
10 mode => '0644',
11 owner => root,
12 group => root,
13 source => 'puppet:///modules/kaminario/__init__.py'}
14
15file { '/usr/lib/python2.7/dist-packages/cinder/volume/drivers/kaminario/kaminario_common.py':
16 mode => '0644',
17 owner => root,
18 group => root,
19 source => 'puppet:///modules/kaminario/kaminario_common.py'}
20
21file { '/usr/lib/python2.7/dist-packages/cinder/volume/drivers/kaminario/kaminario_fc.py':
22 mode => '0644',
23 owner => root,
24 group => root,
25 source => 'puppet:///modules/kaminario/kaminario_fc.py'}
26
27file { '/usr/lib/python2.7/dist-packages/cinder/volume/drivers/kaminario/kaminario_iscsi.py':
28 mode => '0644',
29 owner => root,
30 group => root,
31 source => 'puppet:///modules/kaminario/kaminario_iscsi.py'}
32
33file { '/usr/lib/python2.7/dist-packages/cinder/exception.py':
34 mode => '0644',
35 owner => root,
36 group => root,
37 source => 'puppet:///modules/kaminario/exception.py'}
38
39}
diff --git a/deployment_scripts/puppet/modules/kaminario/manifests/init.pp b/deployment_scripts/puppet/modules/kaminario/manifests/init.pp
new file mode 100644
index 0000000..84ab0b1
--- /dev/null
+++ b/deployment_scripts/puppet/modules/kaminario/manifests/init.pp
@@ -0,0 +1,82 @@
1class kaminario::config {
2$num = [ '0', '1', '2', '3', '4', '5' ]
3$plugin_settings = hiera('cinder_kaminario')
4each($num) |$value| {
5config {"plugin_${value}":
6 cinder_node => $plugin_settings["cinder_node_${value}"],
7 storage_protocol => $plugin_settings["storage_protocol_${value}"],
8 backend_name => $plugin_settings["backend_name_${value}"],
9 storage_user => $plugin_settings["storage_user_${value}"],
10 storage_password => $plugin_settings["storage_password_${value}"],
11 storage_ip => $plugin_settings["storage_ip_${value}"],
12 enable_replication => $plugin_settings["enable_replication_${value}"],
13 replication_ip => $plugin_settings["replication_ip_${value}"],
14 replication_login => $plugin_settings["replication_login_${value}"],
15 replication_rpo => $plugin_settings["replication_rpo_${value}"],
16 replication_password => $plugin_settings["replication_password_${value}"],
17 num => $value
18 }
19}
20}
21
22define config($storage_protocol,$backend_name,$storage_user,$storage_password,$storage_ip,$num,$cinder_node,$enable_replication,$replication_ip,$replication_login,$replication_rpo,$replication_password) {
23
24 $sec_name = section_name( $storage_ip , $backend_name )
25 $config_file = "/etc/cinder/cinder.conf"
26 if $cinder_node == hiera(user_node_name) {
27 if $storage_protocol == 'FC'{
28 ini_subsetting {"enable_backend_${num}":
29 ensure => present,
30 section => 'DEFAULT',
31 key_val_separator => '=',
32 path => $config_file,
33 setting => 'enabled_backends',
34 subsetting => $backend_name,
35 subsetting_separator => ',',
36 }->
37 cinder_config {
38 "$sec_name/volume_driver" : value => "cinder.volume.drivers.kaminario.kaminario_fc.KaminarioFCDriver";
39 "$sec_name/volume_backend_name" : value => $backend_name;
40 "$sec_name/san_ip" : value => $storage_ip;
41 "$sec_name/san_login" : value => $storage_user;
42 "$sec_name/san_password" : value => $storage_password;
43 }
44
45 if $enable_replication == true {
46 $replication_device = get_replication_device($replication_ip, $replication_login , $replication_password , $replication_rpo)
47 cinder_config {
48 "$sec_name/replication_device" : value => $replication_device;
49 }
50
51 }
52}
53 if $storage_protocol == 'ISCSI'{
54 ini_subsetting {"enable_backend_${num}":
55 ensure => present,
56 section => 'DEFAULT',
57 key_val_separator => '=',
58 path => $config_file,
59 setting => 'enabled_backends',
60 subsetting => $backend_name,
61 subsetting_separator => ',',
62 }->
63 cinder_config {
64 "$sec_name/volume_driver" : value => "cinder.volume.drivers.kaminario.kaminario_iscsi.KaminarioISCSIDriver";
65 "$sec_name/volume_backend_name" : value => $backend_name;
66 "$sec_name/san_ip" : value => $storage_ip;
67 "$sec_name/san_login" : value => $storage_user;
68 "$sec_name/san_password" : value => $storage_password;
69 }
70
71 if $enable_replication == true {
72 $replication_device = get_replication_device($replication_ip, $replication_login , $replication_password , $replication_rpo)
73 cinder_config {
74 "$sec_name/replication_device" : value => $replication_device;
75 }
76
77 }
78}
79}
80}
81
82
diff --git a/deployment_scripts/puppet/modules/kaminario/manifests/krest.pp b/deployment_scripts/puppet/modules/kaminario/manifests/krest.pp
new file mode 100644
index 0000000..6fcb046
--- /dev/null
+++ b/deployment_scripts/puppet/modules/kaminario/manifests/krest.pp
@@ -0,0 +1,8 @@
1class kaminario::krest{
2 package { 'python-pip':
3 ensure => installed,}
4package { 'krest':
5 ensure => installed,
6 provider => pip,
7 require => Package['python-pip'],}
8}
diff --git a/deployment_scripts/puppet/modules/kaminario/manifests/type.pp b/deployment_scripts/puppet/modules/kaminario/manifests/type.pp
new file mode 100644
index 0000000..1d62fb0
--- /dev/null
+++ b/deployment_scripts/puppet/modules/kaminario/manifests/type.pp
@@ -0,0 +1,45 @@
1class kaminario::type {
2$num = [ '0', '1', '2', '3', '4', '5' ]
3$plugin_settings = hiera('cinder_kaminario')
4each($num) |$value| {
5kaminario_type {"plugin_${value}":
6 create_type => $plugin_settings["create_type_${value}"],
7 options => $plugin_settings["options_${value}"],
8 backend_name => $plugin_settings["backend_name_${value}"]
9 }
10}
11}
12
13define kaminario_type ($create_type,$options,$backend_name) {
14if $create_type == true {
15case $options {
16 "enable_replication_type": {
17 cinder_type {$backend_name:
18 ensure => present,
19 properties => ["volume_backend_name=${backend_name}",'kaminario:replication=enabled'],
20 }
21 }
22 "enable_dedup": {
23 cinder_type {$backend_name:
24 ensure => present,
25 properties => ["volume_backend_name=${backend_name}",'kaminario:thin_prov_type=nodedup'],
26 }
27 }
28 "replication_dedup": {
29 cinder_type {$backend_name:
30 ensure => present,
31 properties => ["volume_backend_name=${backend_name}",'kaminario:thin_prov_type=nodedup','kaminario:thin_prov_type=nodedup'],
32 }
33 }
34 "default": {
35 cinder_type {$backend_name:
36 ensure => present,
37 properties => ["volume_backend_name=${backend_name}"],
38 }
39 }
40
41}
42
43}
44
45}
diff --git a/deployment_tasks.yaml b/deployment_tasks.yaml
new file mode 100644
index 0000000..78df384
--- /dev/null
+++ b/deployment_tasks.yaml
@@ -0,0 +1,39 @@
1- id: kaminario_parser
2 type: puppet
3 version: 2.1.0
4 groups: [cinder,primary-controller,controller]
5 requires: [top-role-cinder]
6 required_for: [kaminario_cinder]
7 condition:
8 yaql_exp: "changedAny($.storage, $.cinder_kaminario)"
9 parameters:
10 puppet_manifest: puppet/manifests/cinder_parser.pp
11 puppet_modules: puppet/modules:/etc/puppet/modules
12 timeout: 360
13
14- id: kaminario_cinder
15 type: puppet
16 version: 2.1.0
17 groups: [cinder]
18 requires: [kaminario_parser]
19 required_for: [deploy_end]
20 condition:
21 yaql_exp: "changedAny($.storage, $.cinder_kaminario)"
22 parameters:
23 puppet_manifest: puppet/manifests/cinder_kaminario.pp
24 puppet_modules: puppet/modules:/etc/puppet/modules
25 timeout: 360
26
27- id: kaminario_types
28 type: puppet
29 version: 2.1.0
30 groups: [primary-controller]
31 requires: [openstack-cinder]
32 required_for: [deploy_end]
33 condition:
34 yaql_exp: "changedAny($.storage, $.cinder_kaminario)"
35 parameters:
36 puppet_manifest: puppet/manifests/cinder_type.pp
37 puppet_modules: puppet/modules:/etc/puppet/modules
38 timeout: 360
39
diff --git a/environment_config.yaml b/environment_config.yaml
new file mode 100644
index 0000000..18d5d57
--- /dev/null
+++ b/environment_config.yaml
@@ -0,0 +1,1078 @@
1attributes:
2 metadata:
3 group: 'storage'
4 restrictions:
5 - condition: "cluster:status == 'operational'"
6 action: "none"
7 message: |
8 WARNING: Make changes carefully for deployed plugin.
9 Lifecycle management is in EXPERIMENTAL mode.
10 INFO: HOT PLUG is fully supported.
11
12 storage_protocol_0:
13 type: "radio"
14 weight: 10
15 value: "FC"
16 label: "Kaminario Storage Protocol"
17 values:
18 - data: "ISCSI"
19 label: "ISCSI"
20 - data: "FC"
21 label: "Fiber Channel"
22
23 cinder_node_0:
24 value: ""
25 label: 'Cinder Node'
26 description: 'Name of the cinder node in which the backend must be configured'
27 weight: 15
28 type: "text"
29
30 backend_name_0:
31 value: ""
32 label: 'Backend Name'
33 description: 'Name of the Backend'
34 weight: 20
35 type: "text"
36
37 storage_ip_0:
38 value: ""
39 label: 'Kaminario Storage Hostname/IP'
40 description: 'IP address of Kaminario Storage Array'
41 weight: 20
42 type: "text"
43 regex:
44 source: '^\w[\w\-\s.]+$'
45 error: 'Invalid IP ranges'
46
47 storage_user_0:
48 value: ""
49 label: 'Username'
50 description: 'user name of Kaminario Storage Array'
51 weight: 25
52 type: "text"
53 regex:
54 source: '\S'
55 error: "Username field cannot be empty"
56
57 storage_password_0:
58 value: ""
59 label: 'Password'
60 description: 'password of Kaminario Storage Array'
61 weight: 30
62 type: "password"
63
64 add_backend_0:
65 value: true
66 label: 'Add a new kaminario backend or new kaminario Array'
67 description: 'Add a new kaminario backend or scale an existing backend'
68 weight: 35
69 type: 'checkbox'
70 restrictions:
71 - condition: "settings:cinder_kaminario.add_backend_0.value == true"
72 action: 'hide'
73
74 enable_replication_0:
75 value: false
76 label: 'Enable Replication'
77 description: Enable replication for Kaminario Array
78 weight: 40
79 type: 'checkbox'
80
81 replication_ip_0:
82 value: ""
83 label: 'Ipaddress'
84 description: 'Ipaddress of Kaminario replication array'
85 weight: 45
86 type: "text"
87 restrictions:
88 - condition: "settings:cinder_kaminario.enable_replication_0.value == false"
89 action: 'hide'
90 regex:
91 source: '^\w[\w\-\s.]+$'
92 error: 'Invalid IP ranges'
93
94 replication_login_0:
95 value: ""
96 label: 'Username'
97 description: 'user name of Kaminario replication device'
98 weight: 50
99 type: "text"
100 restrictions:
101 - condition: "settings:cinder_kaminario.enable_replication_0.value == false"
102 action: 'hide'
103
104 replication_password_0:
105 value: ""
106 label: 'Password'
107 description: 'password of Kaminario replication device'
108 weight: 55
109 type: "password"
110 restrictions:
111 - condition: "settings:cinder_kaminario.enable_replication_0.value == false"
112 action: 'hide'
113
114 replication_rpo_0:
115 value: ""
116 label: 'RPO'
117 description: 'Value (in seconds) should be either 1 minute or multiple of 5 minutes'
118 weight: 60
119 type: "text"
120 restrictions:
121 - condition: "settings:cinder_kaminario.enable_replication_0.value == false"
122 action: 'hide'
123 regex:
124 source: '^\d+$'
125 error: "You must provide a number"
126
127 create_type_0:
128 value: false
129 label: 'Create Volume Type'
130 description: 'Create volume type for the backend'
131 weight: 65
132 type: 'checkbox'
133
134 type_name_0:
135 value: ""
136 label: 'Volume Type Name'
137 description: 'Name of the volume type'
138 weight: 70
139 type: "text"
140 restrictions:
141 - condition: "settings:cinder_kaminario.create_type_0.value == false"
142 action: 'hide'
143
144 options_0:
145 type: "radio"
146 weight: 75
147 value: ""
148 label: "Advance options for volume type"
149 values:
150 - data: "enable_replication_type"
151 label: "Enable Replication"
152 - data: "enable_dedup"
153 label: "Enable Deduplication"
154 - data: "replication_dedup"
155 label: "Enable both Replication and Deduplication"
156 restrictions:
157 - condition: "settings:cinder_kaminario.create_type_0.value == false"
158 action: 'hide'
159
160 default_type_0:
161 value: false
162 label: 'Default Type'
163 description: 'Make this type as default'