]> git.proxmox.com Git - ceph.git/blame - ceph/src/ceph-volume/ceph_volume/devices/simple/scan.py
bump version to 12.2.2-pve1
[ceph.git] / ceph / src / ceph-volume / ceph_volume / devices / simple / scan.py
CommitLineData
3efd9988
FG
1from __future__ import print_function
2import argparse
3import json
4import logging
5import os
6from textwrap import dedent
7from ceph_volume import decorators, terminal, conf
8from ceph_volume.api import lvm
9from ceph_volume.util import arg_validators, system, disk
10
11
12logger = logging.getLogger(__name__)
13
14
15class Scan(object):
16
17 help = 'Capture metadata from an OSD data partition or directory'
18
19 def __init__(self, argv):
20 self.argv = argv
21 self._etc_path = '/etc/ceph/osd/'
22
23 @property
24 def etc_path(self):
25 if os.path.isdir(self._etc_path):
26 return self._etc_path
27
28 if not os.path.exists(self._etc_path):
29 os.mkdir(self._etc_path)
30 return self._etc_path
31
32 error = "OSD Configuration path (%s) needs to be a directory" % self._etc_path
33 raise RuntimeError(error)
34
35 def get_contents(self, path):
36 with open(path, 'r') as fp:
37 contents = fp.readlines()
38 if len(contents) > 1:
39 return ''.join(contents)
40 return ''.join(contents).strip().strip('\n')
41
42 def scan_device(self, path):
43 device_metadata = {'path': None, 'uuid': None}
44 if not path:
45 return device_metadata
46 # cannot read the symlink if this is tmpfs
47 if os.path.islink(path):
48 device = os.readlink(path)
49 else:
50 device = path
51 lvm_device = lvm.get_lv_from_argument(device)
52 if lvm_device:
53 device_uuid = lvm_device.lv_uuid
54 else:
55 device_uuid = disk.get_partuuid(device)
56
57 device_metadata['uuid'] = device_uuid
58 device_metadata['path'] = device
59
60 return device_metadata
61
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):
70 continue
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):
75 continue
76 if os.path.isfile(file_path):
77 osd_metadata[_file] = self.get_contents(file_path)
78
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'
82 if not device:
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)
86
87 return osd_metadata
88
89 @decorators.needs_root
90 def scan(self, args):
91 osd_metadata = {'cluster_name': conf.cluster}
92 device_mounts = system.get_mounts(devices=True)
93 osd_path = None
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
98 else:
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
105
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
108 # directly
109 if not osd_path:
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)
113 else:
114 logger.info('will scan OSD directory at path: %s', osd_path)
115 osd_metadata = self.scan_directory(osd_path)
116
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:
122 if not args.force:
123 raise RuntimeError(
124 '--force was not used and OSD metadata file exists: %s' % json_path
125 )
126
127 if args.stdout:
128 print(json.dumps(osd_metadata, indent=4, sort_keys=True, ensure_ascii=False))
129 else:
130 with open(json_path, 'w') as fp:
131 json.dump(osd_metadata, fp, indent=4, sort_keys=True, ensure_ascii=False)
132 terminal.success(
133 'OSD %s got scanned and metadata persisted to file: %s' % (
134 osd_id,
135 json_path
136 )
137 )
138 terminal.success(
139 'To take over managment of this scanned OSD, and disable ceph-disk and udev, run:'
140 )
141 terminal.success(' ceph-volume simple activate %s %s' % (osd_id, osd_fsid))
142
143 if not osd_metadata.get('data'):
144 msg = 'Unable to determine device mounted on %s' % args.osd_path
145 logger.warning(msg)
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)
150
151 def main(self):
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.
155
156 Scanned OSDs will get their configurations stored in
157 /etc/ceph/osd/<id>-<fsid>.json
158
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::
161
162 ceph-volume lvm scan /var/lib/ceph/osd/ceph-0
163
164 Which would store the metadata in a JSON file at::
165
166 /etc/ceph/osd/0-a9d50838-e823-43d6-b01f-2f8d0a77afc2.json
167
168 To a scan an existing, running, OSD:
169
170 ceph-volume simple scan /var/lib/ceph/osd/{cluster}-{osd id}
171
172 And to scan a device (mounted or unmounted) that has OSD data in it, for example /dev/sda1
173
174 ceph-volume simple scan /dev/sda1
175 """)
176 parser = argparse.ArgumentParser(
177 prog='ceph-volume simple scan',
178 formatter_class=argparse.RawDescriptionHelpFormatter,
179 description=sub_command_help,
180 )
181
182 parser.add_argument(
183 '-f', '--force',
184 action='store_true',
185 help='If OSD has already been scanned, the JSON file will be overwritten'
186 )
187
188 parser.add_argument(
189 '--stdout',
190 action='store_true',
191 help='Do not save to a file, output metadata to stdout'
192 )
193
194 parser.add_argument(
195 'osd_path',
196 metavar='OSD_PATH',
197 type=arg_validators.OSDPath(),
198 nargs='?',
199 help='Path to an existing OSD directory or OSD data partition'
200 )
201
202 if len(self.argv) == 0:
203 print(sub_command_help)
204 return
205 args = parser.parse_args(self.argv)
206 self.scan(args)