1 from __future__ import print_function
6 from textwrap import dedent
7 from ceph_volume import decorators, terminal, conf
8 from ceph_volume.api import lvm
9 from ceph_volume.util import arg_validators, system, disk
12 logger = logging.getLogger(__name__)
17 help = 'Capture metadata from an OSD data partition or directory'
19 def __init__(self, argv):
21 self._etc_path = '/etc/ceph/osd/'
25 if os.path.isdir(self._etc_path):
28 if not os.path.exists(self._etc_path):
29 os.mkdir(self._etc_path)
32 error = "OSD Configuration path (%s) needs to be a directory" % self._etc_path
33 raise RuntimeError(error)
35 def get_contents(self, path):
36 with open(path, 'r') as fp:
37 contents = fp.readlines()
39 return ''.join(contents)
40 return ''.join(contents).strip().strip('\n')
42 def scan_device(self, path):
43 device_metadata = {'path': None, 'uuid': None}
45 return device_metadata
46 # cannot read the symlink if this is tmpfs
47 if os.path.islink(path):
48 device = os.readlink(path)
51 lvm_device = lvm.get_lv_from_argument(device)
53 device_uuid = lvm_device.lv_uuid
55 device_uuid = disk.get_partuuid(device)
57 device_metadata['uuid'] = device_uuid
58 device_metadata['path'] = device
60 return device_metadata
62 def scan_directory(self, path):
63 osd_metadata = {'cluster_name': conf.cluster}
64 path_mounts = system.get_mounts(paths=True)
65 for _file in os.listdir(path):
66 file_path = os.path.join(path, _file)
67 if os.path.islink(file_path):
68 osd_metadata[_file] = self.scan_device(file_path)
69 if os.path.isdir(file_path):
71 # the check for binary needs to go before the file, to avoid
72 # capturing data from binary files but still be able to capture
73 # contents from actual files later
74 if system.is_binary(file_path):
76 if os.path.isfile(file_path):
77 osd_metadata[_file] = self.get_contents(file_path)
79 device = path_mounts.get(path)
80 # it is possible to have more than one device, pick the first one, and
81 # warn that it is possible that more than one device is 'data'
83 terminal.error('Unable to detect device mounted for path: %s' % path)
84 raise RuntimeError('Cannot activate OSD')
85 osd_metadata['data'] = self.scan_device(device[0] if len(device) else None)
89 @decorators.needs_root
91 osd_metadata = {'cluster_name': conf.cluster}
92 device_mounts = system.get_mounts(devices=True)
94 logger.info('detecting if argument is a device or a directory: %s', args.osd_path)
95 if os.path.isdir(args.osd_path):
96 logger.info('will scan directly, path is a directory')
97 osd_path = args.osd_path
99 # assume this is a device, check if it is mounted and use that path
100 logger.info('path is not a directory, will check if mounted')
101 if system.device_is_mounted(args.osd_path):
102 logger.info('argument is a device, which is mounted')
103 mounted_osd_paths = device_mounts.get(args.osd_path)
104 osd_path = mounted_osd_paths[0] if len(mounted_osd_paths) else None
106 # argument is not a directory, and it is not a device that is mounted
107 # somewhere so temporarily mount it to poke inside, otherwise, scan
110 logger.info('device is not mounted, will mount it temporarily to scan')
111 with system.tmp_mount(args.osd_path) as osd_path:
112 osd_metadata = self.scan_directory(osd_path)
114 logger.info('will scan OSD directory at path: %s', osd_path)
115 osd_metadata = self.scan_directory(osd_path)
117 osd_id = osd_metadata['whoami']
118 osd_fsid = osd_metadata['fsid']
119 filename = '%s-%s.json' % (osd_id, osd_fsid)
120 json_path = os.path.join(self.etc_path, filename)
121 if os.path.exists(json_path) and not args.stdout:
124 '--force was not used and OSD metadata file exists: %s' % json_path
128 print(json.dumps(osd_metadata, indent=4, sort_keys=True, ensure_ascii=False))
130 with open(json_path, 'w') as fp:
131 json.dump(osd_metadata, fp, indent=4, sort_keys=True, ensure_ascii=False)
133 'OSD %s got scanned and metadata persisted to file: %s' % (
139 'To take over managment of this scanned OSD, and disable ceph-disk and udev, run:'
141 terminal.success(' ceph-volume simple activate %s %s' % (osd_id, osd_fsid))
143 if not osd_metadata.get('data'):
144 msg = 'Unable to determine device mounted on %s' % args.osd_path
146 terminal.warning(msg)
147 terminal.warning('OSD will not be able to start without this information:')
148 terminal.warning(' "data": "/path/to/device",')
149 logger.warning('Unable to determine device mounted on %s' % args.osd_path)
152 sub_command_help = dedent("""
153 Scan an OSD directory for files and configurations that will allow to
154 take over the management of the OSD.
156 Scanned OSDs will get their configurations stored in
157 /etc/ceph/osd/<id>-<fsid>.json
159 For an OSD ID of 0 with fsid of ``a9d50838-e823-43d6-b01f-2f8d0a77afc2``
160 that could mean a scan command that looks like::
162 ceph-volume lvm scan /var/lib/ceph/osd/ceph-0
164 Which would store the metadata in a JSON file at::
166 /etc/ceph/osd/0-a9d50838-e823-43d6-b01f-2f8d0a77afc2.json
168 To a scan an existing, running, OSD:
170 ceph-volume simple scan /var/lib/ceph/osd/{cluster}-{osd id}
172 And to scan a device (mounted or unmounted) that has OSD data in it, for example /dev/sda1
174 ceph-volume simple scan /dev/sda1
176 parser = argparse.ArgumentParser(
177 prog='ceph-volume simple scan',
178 formatter_class=argparse.RawDescriptionHelpFormatter,
179 description=sub_command_help,
185 help='If OSD has already been scanned, the JSON file will be overwritten'
191 help='Do not save to a file, output metadata to stdout'
197 type=arg_validators.OSDPath(),
199 help='Path to an existing OSD directory or OSD data partition'
202 if len(self.argv) == 0:
203 print(sub_command_help)
205 args = parser.parse_args(self.argv)