Merge lp:~ifeoktistov/cloud-init/remotedisk-setup into lp:~cloud-init-dev/cloud-init/trunk
- remotedisk-setup
- Merge into trunk
Status: | Rejected |
---|---|
Rejected by: | Scott Moser |
Proposed branch: | lp:~ifeoktistov/cloud-init/remotedisk-setup |
Merge into: | lp:~cloud-init-dev/cloud-init/trunk |
Diff against target: |
828 lines (+803/-0) 4 files modified
cloudinit/config/cc_remotedisk_setup.py (+678/-0) config/cloud.cfg (+1/-0) doc/examples/cloud-config-remotedisk-setup.txt (+95/-0) templates/multipath.conf.tmpl (+29/-0) |
To merge this branch: | bzr merge lp:~ifeoktistov/cloud-init/remotedisk-setup |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
cloud-init Commiters | Pending | ||
Review via email: mp+294055@code.launchpad.net |
Commit message
Description of the change
Added remotedisk-setup config module which provides a simple and uniform way to handle remote disks such as:
- iSCSI LUN's:
- configures Open iSCSI initiator;
- configures device multipath;
- enables necessary services;
- attaches iSCSI LUN;
- discovers multipath device;
- creates logical volume;
- creates filesystem;
- mounts filesystem;
- configures /etc/fstab
- Hypervisor disks (OpenStack Cinder volumes, AWS EBS, etc):
- creates logical volume;
- creates filesystem;
- mounts filesystem;
- configures /etc/fstab
- NFS shares:
- mounts NFS share;
- configures /etc/fstab
The module was extensively tested on RHEL/CentOS 6.7, RHEL/CentOS 7.2, and Ubuntu 14.04 in OpenStack/KVM and AWS.
iSCSI is tested against NetApp cDOT storage.
Scott Moser (smoser) wrote : | # |
Scott Moser (smoser) wrote : | # |
Hello,
Thank you for taking the time to contribute to cloud-init. Cloud-init has moved its revision control system to git. As a result, we are marking all bzr merge proposals as 'rejected'. If you would like to re-submit this proposal for review, please do so by following the current HACKING documentation at http://
This functionality does look interesting.
Long term, we are likely to utilize curtin's storage configuration to accomplish things like this (http://
Unmerged revisions
- 1217. By ifeoktistov
-
Added remotedisk_setup config module
- 1216. By ifeoktistov
-
Added remotedisk_setup config module and updated relevant configurations
Preview Diff
1 | === added file 'cloudinit/config/cc_remotedisk_setup.py' |
2 | --- cloudinit/config/cc_remotedisk_setup.py 1970-01-01 00:00:00 +0000 |
3 | +++ cloudinit/config/cc_remotedisk_setup.py 2016-05-06 21:33:46 +0000 |
4 | @@ -0,0 +1,678 @@ |
5 | +# vi: ts=4 expandtab |
6 | +# |
7 | +# Copyright (C) 2009-2010 Canonical Ltd. |
8 | +# Copyright (C) 2012 Hewlett-Packard Development Company, L.P. |
9 | +# |
10 | +# Author: Igor Feoktistov <Igor.Feoktistov@netapp.com> |
11 | +# |
12 | +# This program is free software: you can redistribute it and/or modify |
13 | +# it under the terms of the GNU General Public License version 3, as |
14 | +# published by the Free Software Foundation. |
15 | +# |
16 | +# This program is distributed in the hope that it will be useful, |
17 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
18 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
19 | +# GNU General Public License for more details. |
20 | +# |
21 | +# You should have received a copy of the GNU General Public License |
22 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
23 | + |
24 | +import logging |
25 | +import os |
26 | +import time |
27 | +import shlex |
28 | +import fnmatch |
29 | +import subprocess |
30 | +import re |
31 | +from string import whitespace |
32 | + |
33 | +from cloudinit.settings import PER_INSTANCE |
34 | +from cloudinit import type_utils |
35 | +from cloudinit import util |
36 | +from cloudinit import templater |
37 | + |
38 | +frequency = PER_INSTANCE |
39 | + |
40 | +WAIT_4_BLOCKDEV_MAPPING_ITER = 60 |
41 | +WAIT_4_BLOCKDEV_MAPPING_SLEEP = 5 |
42 | +WAIT_4_BLOCKDEV_DEVICE_ITER = 12 |
43 | +WAIT_4_BLOCKDEV_DEVICE_SLEEP = 5 |
44 | + |
45 | +LVM_CMD = util.which("lvm") |
46 | +ISCSIADM_CMD = util.which("iscsiadm") |
47 | +MULTIPATH_CMD = util.which("multipath") |
48 | +SYSTEMCTL_CMD = util.which("systemctl") |
49 | +CHKCONFIG_CMD = util.which("chkconfig") |
50 | +SERVICE_CMD = util.which("service") |
51 | +FSTAB_PATH = "/etc/fstab" |
52 | +ISCSI_INITIATOR_PATH = "/etc/iscsi/initiatorname.iscsi" |
53 | + |
54 | + |
55 | +def handle(_name, cfg, cloud, log, _args): |
56 | + if "remotedisk_setup" not in cfg: |
57 | + log.debug("Skipping module named %s, no configuration found" % _name) |
58 | + return |
59 | + remotedisk_setup = cfg.get("remotedisk_setup") |
60 | + log.debug("setting up remote disk: %s", str(remotedisk_setup)) |
61 | + for definition in remotedisk_setup: |
62 | + try: |
63 | + device = definition.get("device") |
64 | + if device: |
65 | + if device.startswith("iscsi"): |
66 | + handle_iscsi(cfg, cloud, log, definition) |
67 | + elif device.startswith("nfs"): |
68 | + handle_nfs(cfg, cloud, log, definition) |
69 | + elif device.startswith("ebs"): |
70 | + handle_ebs(cfg, cloud, log, definition) |
71 | + elif device.startswith("ephemeral"): |
72 | + handle_ebs(cfg, cloud, log, definition) |
73 | + else: |
74 | + if "fs_type" in definition: |
75 | + fs_type = definition.get("fs_type") |
76 | + if fs_type == "nfs": |
77 | + handle_nfs(cfg, cloud, log, definition) |
78 | + else: |
79 | + handle_ebs(cfg, cloud, log, definition) |
80 | + else: |
81 | + util.logexc(log, "Expexted \"fs_type\" parameter") |
82 | + else: |
83 | + util.logexc(log, "Expexted \"device\" parameter") |
84 | + except Exception as e: |
85 | + util.logexc(log, "Failed during remote disk operation\n" |
86 | + "Exception: %s" % e) |
87 | + |
88 | + |
89 | +def handle_iscsi(cfg, cloud, log, definition): |
90 | + # Handle iSCSI LUN |
91 | + device = definition.get("device") |
92 | + try: |
93 | + (iscsi_host, |
94 | + iscsi_proto, |
95 | + iscsi_port, |
96 | + iscsi_lun, |
97 | + iscsi_target) = device.split(":", 5)[1:] |
98 | + except Exception as e: |
99 | + util.logexc(log, |
100 | + "handle_iscsi: " |
101 | + "expected \"device\" attribute in the format: " |
102 | + "\"iscsi:<iSCSI host>:<protocol>:<port>:<LUN>:" |
103 | + "<iSCSI target name>\": %s" % e) |
104 | + return |
105 | + (hostname, fqdn) = util.get_hostname_fqdn(cfg, cloud) |
106 | + if "initiator_name" in definition: |
107 | + initiator_name = definition.get("initiator_name") |
108 | + else: |
109 | + initiator_name = "iqn.2005-02.com.open-iscsi:%s" % hostname |
110 | + util.write_file(ISCSI_INITIATOR_PATH, "InitiatorName=%s" % initiator_name) |
111 | + multipath_tmpl_fn = cloud.get_template_filename("multipath.conf") |
112 | + if not multipath_tmpl_fn: |
113 | + util.logexc(log, "handle_iscsi: template multipath.conf not found") |
114 | + return |
115 | + templater.render_to_file(multipath_tmpl_fn, "/etc/multipath.conf", {}) |
116 | + if cloud.distro.osfamily == "redhat": |
117 | + iscsi_services = ["iscsi", "iscsid"] |
118 | + multipath_services = ["multipathd"] |
119 | + elif cloud.distro.osfamily == 'debian': |
120 | + iscsi_services = ["open-iscsi"] |
121 | + multipath_services = ["multipath-tools"] |
122 | + else: |
123 | + util.logexc(log, |
124 | + "handle_iscsi: " |
125 | + "unsupported osfamily \"%s\"" % cloud.distro.osfamily) |
126 | + return |
127 | + for service in iscsi_services: |
128 | + _service_wrapper(cloud, log, service, "enable") |
129 | + _service_wrapper(cloud, log, service, "restart") |
130 | + for service in multipath_services: |
131 | + _service_wrapper(cloud, log, service, "enable") |
132 | + _service_wrapper(cloud, log, service, "restart") |
133 | + blockdev = _iscsi_lun_discover(log, |
134 | + iscsi_host, |
135 | + iscsi_port, |
136 | + iscsi_lun, |
137 | + iscsi_target) |
138 | + if blockdev: |
139 | + lvm_group = definition.get("lvm_group") |
140 | + lvm_volume = definition.get("lvm_volume") |
141 | + fs_type = definition.get("fs_type") |
142 | + fs_opts = definition.get("fs_opts") |
143 | + mount_point = definition.get("mount_point") |
144 | + mount_opts = definition.get("mount_opts") |
145 | + if not mount_opts: |
146 | + mount_opts = 'defaults,_netdev' |
147 | + else: |
148 | + if mount_opts.find("_netdev") == -1: |
149 | + mount_opts = "%s,_netdev" % (mount_opts) |
150 | + fs_freq = definition.get("fs_freq") |
151 | + if not fs_freq: |
152 | + fs_freq = "1" |
153 | + fs_passno = definition.get("fs_passno") |
154 | + if not fs_passno: |
155 | + fs_passno = "2" |
156 | + if lvm_group and lvm_volume: |
157 | + for vg_name in _list_vg_names(): |
158 | + if vg_name == lvm_group: |
159 | + util.logexc(log, |
160 | + "handle_iscsi: " |
161 | + "logical volume group '%s' exists already" |
162 | + % lvm_group) |
163 | + return |
164 | + for lv_name in _list_lv_names(): |
165 | + if lv_name == lvm_volume: |
166 | + util.logexc(log, |
167 | + "handle_iscsi: " |
168 | + "logical volume '%s' exists already" |
169 | + % lvm_volume) |
170 | + return |
171 | + blockdev = _create_lv(log, blockdev, lvm_group, lvm_volume) |
172 | + if blockdev: |
173 | + if mount_point and fs_type: |
174 | + _create_fs(log, blockdev, fs_type, fs_opts) |
175 | + _add_fstab_entry(log, |
176 | + blockdev, |
177 | + mount_point, |
178 | + fs_type, |
179 | + mount_opts, |
180 | + fs_freq, |
181 | + fs_passno) |
182 | + _mount_fs(log, mount_point) |
183 | + else: |
184 | + util.logexc(log, |
185 | + "handle_iscsi: " |
186 | + "expexted \"mount_point\" " |
187 | + "and \"fs_type\" parameters") |
188 | + |
189 | + |
190 | +def handle_nfs(cfg, cloud, log, definition): |
191 | + # Handle NFS share mounts |
192 | + device = definition.get("device") |
193 | + if device.startswith("nfs"): |
194 | + (proto, share_path) = device.split(":", 1) |
195 | + else: |
196 | + share_path = device |
197 | + fs_type = definition.get("fs_type") |
198 | + mount_point = definition.get("mount_point") |
199 | + mount_opts = definition.get("mount_opts") |
200 | + if not mount_opts: |
201 | + mount_opts = "defaults" |
202 | + fs_freq = definition.get("fs_freq") |
203 | + if not fs_freq: |
204 | + fs_freq = "0" |
205 | + fs_passno = definition.get("fs_passno") |
206 | + if not fs_passno: |
207 | + fs_passno = "0" |
208 | + if mount_point and fs_type: |
209 | + _add_fstab_entry(log, |
210 | + share_path, |
211 | + mount_point, |
212 | + fs_type, |
213 | + mount_opts, |
214 | + fs_freq, |
215 | + fs_passno) |
216 | + _mount_fs(log, mount_point) |
217 | + else: |
218 | + util.logexc(log, |
219 | + "handle_nfs: " |
220 | + "expexted \"mount_point\" and \"fs_type\" parameters") |
221 | + |
222 | + |
223 | +def handle_ebs(cfg, cloud, log, definition): |
224 | + # Handle block device either explicitly provided via device path or |
225 | + # via device name mapping (Amazon/OpenStack) |
226 | + device = definition.get("device") |
227 | + blockdev = _cloud_device_2_os_device(cloud, log, device) |
228 | + if blockdev: |
229 | + lvm_group = definition.get("lvm_group") |
230 | + lvm_volume = definition.get("lvm_volume") |
231 | + fs_type = definition.get("fs_type") |
232 | + fs_opts = definition.get("fs_opts") |
233 | + mount_point = definition.get("mount_point") |
234 | + mount_opts = definition.get("mount_opts") |
235 | + if not mount_opts: |
236 | + mount_opts = "defaults" |
237 | + fs_freq = definition.get("fs_freq") |
238 | + if not fs_freq: |
239 | + fs_freq = "1" |
240 | + fs_passno = definition.get("fs_passno") |
241 | + if not fs_passno: |
242 | + fs_passno = "2" |
243 | + if lvm_group and lvm_volume: |
244 | + for vg_name in _list_vg_names(): |
245 | + if vg_name == lvm_group: |
246 | + util.logexc(log, |
247 | + "handle_ebs: " |
248 | + "logical volume group '%s' exists already" |
249 | + % lvm_group) |
250 | + return |
251 | + for lv_name in _list_lv_names(): |
252 | + if lv_name == lvm_volume: |
253 | + util.logexc(log, |
254 | + "handle_ebs: " |
255 | + "logical volume '%s' exists already" |
256 | + % lvm_volume) |
257 | + return |
258 | + blockdev = _create_lv(log, blockdev, lvm_group, lvm_volume) |
259 | + if blockdev: |
260 | + if mount_point and fs_type: |
261 | + _create_fs(log, blockdev, fs_type, fs_opts) |
262 | + _add_fstab_entry(log, |
263 | + blockdev, |
264 | + mount_point, |
265 | + fs_type, |
266 | + mount_opts, |
267 | + fs_freq, |
268 | + fs_passno) |
269 | + _mount_fs(log, mount_point) |
270 | + else: |
271 | + util.logexc(log, |
272 | + "handle_ebs: " |
273 | + "expexted \"mount_point\" and " |
274 | + "\"fs_type\" parameters") |
275 | + |
276 | + |
277 | +def _cloud_device_2_os_device(cloud, log, name): |
278 | + # Translate cloud device (ebs# and ephemaral#) to OS block device path |
279 | + blockdev = None |
280 | + for i in range(WAIT_4_BLOCKDEV_MAPPING_ITER): |
281 | + if (cloud.datasource.metadata and |
282 | + "block-device-mapping" in cloud.datasource.metadata): |
283 | + metadata = cloud.datasource.metadata |
284 | + else: |
285 | + if (cloud.datasource.ec2_metadata and |
286 | + "block-device-mapping" in cloud.datasource.ec2_metadata): |
287 | + metadata = cloud.datasource.ec2_metadata |
288 | + else: |
289 | + util.logexc(log, |
290 | + "_cloud_device_2_os_device: " |
291 | + "metadata item block-device-mapping not found") |
292 | + return None |
293 | + blockdev_items = metadata["block-device-mapping"].iteritems() |
294 | + for (map_name, device) in blockdev_items: |
295 | + if map_name == name: |
296 | + blockdev = device |
297 | + break |
298 | + if blockdev is None: |
299 | + cloud.datasource.get_data() |
300 | + time.sleep(WAIT_4_BLOCKDEV_MAPPING_SLEEP) |
301 | + continue |
302 | + if blockdev is None: |
303 | + util.logexc(log, |
304 | + "_cloud_device_2_os_device: " |
305 | + "unable to convert %s to a device" % name) |
306 | + return None |
307 | + if not blockdev.startswith("/"): |
308 | + blockdev_path = "/dev/%s" % blockdev |
309 | + else: |
310 | + blockdev_path = blockdev |
311 | + for i in range(WAIT_4_BLOCKDEV_DEVICE_ITER): |
312 | + if os.path.exists(blockdev_path): |
313 | + return blockdev_path |
314 | + time.sleep(WAIT_4_BLOCKDEV_DEVICE_SLEEP) |
315 | + util.logexc(log, |
316 | + "_cloud_device_2_os_device: " |
317 | + "device %s does not exist" % blockdev_path) |
318 | + return None |
319 | + |
320 | + |
321 | +def _list_vg_names(): |
322 | + # List all LVM volume groups |
323 | + p = subprocess.Popen([LVM_CMD, "vgs", "-o", "vg_name"], |
324 | + stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
325 | + err = p.wait() |
326 | + if err: |
327 | + return [] |
328 | + output = p.communicate()[0] |
329 | + output = output.split("\n") |
330 | + if not output: |
331 | + return [] |
332 | + header = output[0].strip() |
333 | + if header != "VG": |
334 | + return [] |
335 | + names = [] |
336 | + for name in output[1:]: |
337 | + if not name: |
338 | + break |
339 | + names.append(name.strip()) |
340 | + return names |
341 | + |
342 | + |
343 | +def _list_lv_names(): |
344 | + # List all LVM logical volumes |
345 | + p = subprocess.Popen([LVM_CMD, "lvs", "-o", "lv_name"], |
346 | + stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
347 | + err = p.wait() |
348 | + if err: |
349 | + return [] |
350 | + output = p.communicate()[0] |
351 | + output = output.split("\n") |
352 | + if not output: |
353 | + return [] |
354 | + header = output[0].strip() |
355 | + if header != "LV": |
356 | + return [] |
357 | + names = [] |
358 | + for name in output[1:]: |
359 | + if not name: |
360 | + break |
361 | + names.append(name.strip()) |
362 | + return names |
363 | + |
364 | + |
365 | +def _create_lv(log, device, vg_name, lv_name): |
366 | + # Create volume group |
367 | + pvcreate_cmd = [LVM_CMD, "pvcreate", device] |
368 | + vgcreate_cmd = [LVM_CMD, "vgcreate", vg_name, device] |
369 | + lvcreate_cmd = [LVM_CMD, |
370 | + "lvcreate", "-l", "100%FREE", "--name", lv_name, vg_name] |
371 | + try: |
372 | + util.subp(pvcreate_cmd) |
373 | + util.subp(vgcreate_cmd) |
374 | + util.subp(lvcreate_cmd) |
375 | + return "/dev/mapper/%s-%s" % (vg_name, lv_name) |
376 | + except Exception as e: |
377 | + util.logexc(log, |
378 | + "_create_lv: " |
379 | + "failed to create LVM volume '%s' for device '%s': %s" |
380 | + % (lv_name, device, e)) |
381 | + return None |
382 | + |
383 | + |
384 | +def _create_fs(log, device, fs_type, fs_opts=None): |
385 | + # Create filesystem |
386 | + mkfs_cmd = util.which("mkfs.%s" % fs_type) |
387 | + if not mkfs_cmd: |
388 | + mkfs_cmd = util.which("mk%s" % fs_type) |
389 | + if not mkfs_cmd: |
390 | + util.logexc(log, |
391 | + "_create_fs: " |
392 | + "cannot create filesystem type '%s': " |
393 | + "failed to find mkfs.%s command" % (fs_type, fs_type)) |
394 | + return |
395 | + try: |
396 | + if fs_opts: |
397 | + util.subp([mkfs_cmd, fs_opts, device]) |
398 | + else: |
399 | + util.subp([mkfs_cmd, device]) |
400 | + except Exception as e: |
401 | + util.logexc(log, |
402 | + "_create_fs: " |
403 | + "failed to create filesystem type '%s': %s" % (fs_type, e)) |
404 | + |
405 | + |
406 | +def _add_fstab_entry(log, |
407 | + device, |
408 | + mount_point, |
409 | + fs_type, |
410 | + mount_opts, |
411 | + fs_freq, |
412 | + fs_passno): |
413 | + # Create fstab entry |
414 | + fstab_lines = [] |
415 | + for line in util.load_file(FSTAB_PATH).splitlines(): |
416 | + try: |
417 | + toks = re.compile("[%s]+" % (whitespace)).split(line) |
418 | + except: |
419 | + pass |
420 | + if len(toks) > 0 and toks[0] == device: |
421 | + util.logexc(log, |
422 | + "_add_fstab_entry: " |
423 | + "file %s has device %s already" % (FSTAB_PATH, device)) |
424 | + return |
425 | + if len(toks) > 1 and toks[1] == mount_point: |
426 | + util.logexc(log, |
427 | + "_add_fstab_entry: " |
428 | + "file %s has mount point %s already" |
429 | + % (FSTAB_PATH, mount_point)) |
430 | + return |
431 | + fstab_lines.append(line) |
432 | + fstab_lines.extend(["%s\t%s\t%s\t%s\t%s\t%s" % |
433 | + (device, |
434 | + mount_point, |
435 | + fs_type, |
436 | + mount_opts, |
437 | + fs_freq, |
438 | + fs_passno)]) |
439 | + contents = "%s\n" % ('\n'.join(fstab_lines)) |
440 | + util.write_file(FSTAB_PATH, contents) |
441 | + |
442 | + |
443 | +def _mount_fs(log, mount_point): |
444 | + # Mount filesystem according to fstab entry |
445 | + try: |
446 | + util.ensure_dir(mount_point) |
447 | + except Exception as e: |
448 | + util.logexc(log, |
449 | + "_mount_fs: " |
450 | + "failed to make '%s' mount point directory: %s" |
451 | + % (mount_point, e)) |
452 | + return |
453 | + try: |
454 | + util.subp(["mount", mount_point]) |
455 | + except Exception as e: |
456 | + util.logexc(log, |
457 | + "_mount_fs: " |
458 | + "activating mounts via 'mount %s' failed: %s" |
459 | + % (mount_point, e)) |
460 | + |
461 | + |
462 | +def _service_wrapper(cloud, log, service, command): |
463 | + # Wrapper for service related commands |
464 | + if cloud.distro.osfamily == "redhat": |
465 | + if SYSTEMCTL_CMD: |
466 | + svc_cmd = [SYSTEMCTL_CMD, command, service] |
467 | + else: |
468 | + if command == "enable" or command == "disable": |
469 | + if CHKCONFIG_CMD: |
470 | + if command == "enable": |
471 | + svc_cmd = [CHKCONFIG_CMD, service, "on"] |
472 | + else: |
473 | + svc_cmd = [CHKCONFIG_CMD, service, "off"] |
474 | + else: |
475 | + util.logexc(log, |
476 | + "_handle_service: " |
477 | + "service config command \"chkconfig\" " |
478 | + "not found") |
479 | + return |
480 | + else: |
481 | + svc_cmd = [SERVICE_CMD, service, command] |
482 | + elif cloud.distro.osfamily == "debian": |
483 | + if SYSTEMCTL_CMD: |
484 | + svc_cmd = [SYSTEMCTL_CMD, command, service] |
485 | + else: |
486 | + if command == 'enable' or command == "disable": |
487 | + if os.path.exists('/usr/sbin/update-rc.d'): |
488 | + svc_cmd = ['/usr/sbin/update-rc.d', service, "defaults"] |
489 | + else: |
490 | + util.logexc(log, |
491 | + "_handle_service: " |
492 | + "command \"/usr/sbin/update-rc.d\" not found") |
493 | + return |
494 | + else: |
495 | + svc_cmd = [SERVICE_CMD, service, command] |
496 | + else: |
497 | + util.logexc(log, |
498 | + "_handle_service: " |
499 | + "unsupported osfamily \"%s\"" % cloud.distro.osfamily) |
500 | + return |
501 | + try: |
502 | + util.subp(svc_cmd, capture=False) |
503 | + except Exception as e: |
504 | + util.logexc(log, |
505 | + "_handle_service: " |
506 | + "failure to \"%s\" \"%s\": %s" % (command, service, e)) |
507 | + |
508 | + |
509 | +def _iscsi_lun_discover(log, iscsi_host, iscsi_port, iscsi_lun, iscsi_target): |
510 | + # Discover iSCSI target and map LUN ID to multipath device path |
511 | + blockdev = None |
512 | + for i in range(WAIT_4_BLOCKDEV_MAPPING_ITER): |
513 | + try: |
514 | + util.subp([ISCSIADM_CMD, |
515 | + "--mode", |
516 | + "discoverydb", |
517 | + "--type", |
518 | + "sendtargets", |
519 | + "--portal", |
520 | + "%s:%s" % (iscsi_host, iscsi_port), |
521 | + "--discover", |
522 | + "--login", |
523 | + "all"], |
524 | + capture=False) |
525 | + except Exception as e: |
526 | + util.logexc(log, |
527 | + "_iscsi_lun_discover: " |
528 | + "failure in attempt to discover iSCSI LUN for target " |
529 | + "\"%s\": %s" % (iscsi_target, e)) |
530 | + return None |
531 | + p = subprocess.Popen([ISCSIADM_CMD, "-m", "node"], |
532 | + stdout=subprocess.PIPE, |
533 | + stderr=subprocess.PIPE) |
534 | + err = p.wait() |
535 | + if err: |
536 | + util.logexc(log, |
537 | + "_iscsi_lun_discover: " |
538 | + "failure from \"%s -m node\" command" % ISCSIADM_CMD) |
539 | + return None |
540 | + output = p.communicate()[0] |
541 | + output = output.split("\n") |
542 | + if not output: |
543 | + util.logexc(log, |
544 | + "_iscsi_lun_discover: " |
545 | + "no iSCSI nodes discovered for target \"%s\"" |
546 | + % iscsi_target) |
547 | + time.sleep(WAIT_4_BLOCKDEV_MAPPING_SLEEP) |
548 | + continue |
549 | + for node in output: |
550 | + iscsi_portal = node.split(",", 1)[0] |
551 | + if iscsi_portal: |
552 | + try: |
553 | + util.subp([ISCSIADM_CMD, |
554 | + "-m", |
555 | + "node", |
556 | + "-T", |
557 | + iscsi_target, |
558 | + "-p", |
559 | + iscsi_portal, |
560 | + "--op", |
561 | + "update", |
562 | + "-n", |
563 | + "node.startup", |
564 | + "-v", |
565 | + "automatic"], |
566 | + capture=False) |
567 | + except Exception as e: |
568 | + util.logexc(log, |
569 | + "_iscsi_lun_discover: " |
570 | + "failure in attempt to set automatic binding " |
571 | + "for target portal \"%s\": %s" |
572 | + % (iscsi_portal, e)) |
573 | + return None |
574 | + p = subprocess.Popen([ISCSIADM_CMD, "-m", "session", "-P3"], |
575 | + stdout=subprocess.PIPE, |
576 | + stderr=subprocess.PIPE) |
577 | + err = p.wait() |
578 | + if err: |
579 | + util.logexc(log, |
580 | + "_iscsi_lun_discover: " |
581 | + "failure from \"%s -m session -P3\" command" |
582 | + % ISCSIADM_CMD) |
583 | + return None |
584 | + output = p.communicate()[0] |
585 | + output = output.split("\n") |
586 | + if not output: |
587 | + util.logexc(log, |
588 | + "_iscsi_lun_discover: " |
589 | + "no iSCSI sessions discovered for target \"%s\"" |
590 | + % iscsi_target) |
591 | + else: |
592 | + current_iscsi_target = None |
593 | + current_iscsi_sid = None |
594 | + current_iscsi_lun = None |
595 | + for line in output: |
596 | + m = re.search("^Target: ([a-z0-9\.:-]*)", line) |
597 | + if m: |
598 | + current_iscsi_target = m.group(1) |
599 | + continue |
600 | + else: |
601 | + if (current_iscsi_target and |
602 | + current_iscsi_target == iscsi_target): |
603 | + m = re.search("SID: ([0-9]*)", line) |
604 | + if m: |
605 | + if current_iscsi_sid and not current_iscsi_lun: |
606 | + try: |
607 | + util.subp([ISCSIADM_CMD, |
608 | + "-m", |
609 | + "session", |
610 | + "-r", |
611 | + current_iscsi_sid, |
612 | + "-u"], |
613 | + capture=False) |
614 | + except: |
615 | + pass |
616 | + current_iscsi_sid = m.group(1) |
617 | + current_iscsi_lun = None |
618 | + continue |
619 | + m = re.search("scsi[0-9]* Channel [0-9]* " |
620 | + "Id [0-9]* Lun: ([0-9]*)", line) |
621 | + if m: |
622 | + current_iscsi_lun = m.group(1) |
623 | + continue |
624 | + if (current_iscsi_lun and |
625 | + current_iscsi_lun == iscsi_lun): |
626 | + m = re.search("Attached scsi disk (sd[a-z]*)", |
627 | + line) |
628 | + if m: |
629 | + attached_scsi_disk = m.group(1) |
630 | + p = subprocess.Popen(["/lib/udev/scsi_id", |
631 | + "-g", "-u", "-d", |
632 | + "/dev/%s" |
633 | + % attached_scsi_disk], |
634 | + stdout=subprocess.PIPE, |
635 | + stderr=subprocess.PIPE) |
636 | + err = p.wait() |
637 | + if err: |
638 | + util.logexc(log, |
639 | + "_iscsi_lun_discover: " |
640 | + "failure from " |
641 | + "\"/lib/udev/scsi_id\" " |
642 | + "command") |
643 | + return None |
644 | + output2 = p.communicate()[0] |
645 | + output2 = output2.split('\n') |
646 | + if not output2: |
647 | + util.logexc(log, |
648 | + "_iscsi_lun_discover: " |
649 | + "no wwid returned for device " |
650 | + "\"/dev/%s\"" |
651 | + % attached_scsi_disk) |
652 | + else: |
653 | + blockdev = "/dev/mapper/%s" % output2[0] |
654 | + if current_iscsi_sid and not current_iscsi_lun: |
655 | + try: |
656 | + util.subp([ISCSIADM_CMD, |
657 | + "-m", |
658 | + "session", |
659 | + "-r", |
660 | + current_iscsi_sid, |
661 | + "-u"], |
662 | + capture=False) |
663 | + except: |
664 | + pass |
665 | + if blockdev: |
666 | + break |
667 | + else: |
668 | + time.sleep(WAIT_4_BLOCKDEV_MAPPING_SLEEP) |
669 | + if blockdev: |
670 | + for i in range(WAIT_4_BLOCKDEV_DEVICE_ITER): |
671 | + if os.path.exists(blockdev): |
672 | + return blockdev |
673 | + try: |
674 | + util.subp([MULTIPATH_CMD], capture=False) |
675 | + except Exception as e: |
676 | + util.logexc(log, |
677 | + "_iscsi_lun_discover: " |
678 | + "failure to run \"%s\": %s" % (MULTIPATH_CMD, e)) |
679 | + return None |
680 | + time.sleep(WAIT_4_BLOCKDEV_DEVICE_SLEEP) |
681 | + else: |
682 | + return None |
683 | |
684 | === modified file 'config/cloud.cfg' |
685 | --- config/cloud.cfg 2016-03-09 22:34:11 +0000 |
686 | +++ config/cloud.cfg 2016-05-06 21:33:46 +0000 |
687 | @@ -44,6 +44,7 @@ |
688 | # this can be used by upstart jobs for 'start on cloud-config'. |
689 | - emit_upstart |
690 | - disk_setup |
691 | + - remotedisk_setup |
692 | - mounts |
693 | - ssh-import-id |
694 | - locale |
695 | |
696 | === added file 'doc/examples/cloud-config-remotedisk-setup.txt' |
697 | --- doc/examples/cloud-config-remotedisk-setup.txt 1970-01-01 00:00:00 +0000 |
698 | +++ doc/examples/cloud-config-remotedisk-setup.txt 2016-05-06 21:33:46 +0000 |
699 | @@ -0,0 +1,95 @@ |
700 | +#cloud-config |
701 | +# |
702 | +# The module remotedisk_setup provides a simple and uniform way |
703 | +# to handle remote disks such as: |
704 | +# - iSCSI LUN's: |
705 | +# - configures Open iSCSI initiator; |
706 | +# - configures device multipath; |
707 | +# - enables necessary services; |
708 | +# - attaches iSCSI LUN; |
709 | +# - discovers multipath device; |
710 | +# - creates logical volume; |
711 | +# - creates filesystem; |
712 | +# - mounts filesystem; |
713 | +# - configures /etc/fstab |
714 | +# - Hypervisor disks (OpenStack Cinder volumes, AWS EBS, etc): |
715 | +# - creates logical volume; |
716 | +# - creates filesystem; |
717 | +# - mounts filesystem; |
718 | +# - configures /etc/fstab |
719 | +# - NFS shares: |
720 | +# - mounts NFS share; |
721 | +# - configures /etc/fstab |
722 | +# |
723 | +remotedisk_setup: |
724 | + |
725 | +############################################ |
726 | +# Example configuration to handle iSCSI LUN: |
727 | +############################################ |
728 | + - device: 'iscsi:192.168.1.1:6:3260:1:iqn.1992-08.com.netapp:sn.62546b567fbf11e4811590e2ba6cc3b4:vs.10' |
729 | + lvm_group: 'vg_data1' |
730 | + lvm_volume: 'lv_data1' |
731 | + fs_type: 'ext4' |
732 | + mount_point: '/apps/data1' |
733 | + |
734 | +############################################ |
735 | +# Parameters: |
736 | +# mandatory: |
737 | +# device: 'iscsi:<iSCSI target host/LIF>:<transport protocol>:<port>:<LUN ID>:<iSCSI target name>' |
738 | +# fs_type: '<filesystem type>' |
739 | +# mount_point: '<mount point dir path>' |
740 | +# optional: |
741 | +# initiator_name: '<iSCSI initiator name, default is iqn.2005-02.com.open-iscsi:<hostname>>' |
742 | +# mount_opts: '<filesystem mount options, default is "defaults,_netdev">' |
743 | +# lvm_group: '<LVM logical group name>' |
744 | +# lvm_volume: '<LVM logical volume name>' |
745 | +# fs_opts: '<filesystem create options specific to mkfs.fs_type>' |
746 | +# fs_freq: '<fstab fs freq, default is "1">' |
747 | +# fs_passno: '<fstab fs passno, default is "2">' |
748 | +# notes: |
749 | +# missing lvm_group and lvm_volume will cause filesystem creation on top of multipath device |
750 | +# |
751 | + |
752 | +########################################################## |
753 | +# Example configuration to handle OpenStack Cinder volume: |
754 | +########################################################## |
755 | + - device: 'ebs0' |
756 | + lvm_group: 'vg_data1' |
757 | + lvm_volume: 'lv_data1' |
758 | + fs_type: 'ext4' |
759 | + mount_point: '/apps/data1' |
760 | + |
761 | +########################################################## |
762 | +# Parameters: |
763 | +# mandatory: |
764 | +# device: 'ebs<0-9> or block device path /dev/vd<b-z>' |
765 | +# fs_type: '<filesystem type>' |
766 | +# mount_point: '<mount point dir path>' |
767 | +# optional: |
768 | +# mount_opts: '<filesystem mount options, default is "defaults">' |
769 | +# lvm_group: '<LVM logical group name>' |
770 | +# lvm_volume: '<LVM logical volume name>' |
771 | +# fs_opts: '<filesystem create options specific to mkfs.fs_type>' |
772 | +# fs_freq: '<fstab fs freq, default is "1">' |
773 | +# fs_passno: '<fstab fs passno, default is "2">' |
774 | +# notes: |
775 | +# missing lvm_group and lvm_volume will cause filesystem creation on top of block device |
776 | +# |
777 | + |
778 | +############################################# |
779 | +# Example configuration to handle NFS shares: |
780 | +############################################# |
781 | + - device: 'nfs:192.168.1.1:/myshare' |
782 | + mount_point: '/apps/data1' |
783 | + mount_opts: 'tcp,rw,rsize=65536,wsize=65536' |
784 | + |
785 | +############################################# |
786 | +# Parameters: |
787 | +# mandatory: |
788 | +# device: 'nfs:<NFS host>:<NFS share path>' |
789 | +# mount_point: '<mount point dir path>' |
790 | +# optional: |
791 | +# mount_opts: '<NFS share mount options, default is "defaults">' |
792 | +# fs_type: 'nfs' |
793 | +# fs_freq: '<fstab fs freq, default is "0">' |
794 | +# fs_passno: '<fstab fs passno, default is "0">' |
795 | |
796 | === added file 'templates/multipath.conf.tmpl' |
797 | --- templates/multipath.conf.tmpl 1970-01-01 00:00:00 +0000 |
798 | +++ templates/multipath.conf.tmpl 2016-05-06 21:33:46 +0000 |
799 | @@ -0,0 +1,29 @@ |
800 | +defaults { |
801 | + find_multipaths yes |
802 | + user_friendly_names no |
803 | + no_path_retry queue |
804 | + queue_without_daemon no |
805 | + flush_on_last_del yes |
806 | + max_fds max |
807 | +} |
808 | +blacklist { |
809 | + devnode "^hd[a-z]" |
810 | + devnode "^vd[a-z]" |
811 | + devnode "^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*" |
812 | + devnode "^cciss.*" |
813 | +} |
814 | +devices { |
815 | + device { |
816 | + vendor "NETAPP" |
817 | + product "LUN" |
818 | + path_grouping_policy group_by_prio |
819 | + features "3 queue_if_no_path pg_init_retries 50" |
820 | + prio "alua" |
821 | + path_checker tur |
822 | + failback immediate |
823 | + path_selector "round-robin 0" |
824 | + hardware_handler "1 alua" |
825 | + rr_weight uniform |
826 | + rr_min_io 128 |
827 | + } |
828 | +} |
Hi, thanks for the merge proposal. sorry for the long lag in looking at it.
This looks interesting, I'll try to take a more deep look at it next week.