]> git.proxmox.com Git - ceph.git/blame - ceph/src/ceph-volume/ceph_volume/util/system.py
update sources to v12.2.5
[ceph.git] / ceph / src / ceph-volume / ceph_volume / util / system.py
CommitLineData
d2e6a577 1import errno
b32b8144 2import logging
d2e6a577
FG
3import os
4import pwd
5import platform
3efd9988 6import tempfile
d2e6a577 7import uuid
94b18763 8from ceph_volume import process, terminal
d2e6a577
FG
9from . import as_string
10
b32b8144 11logger = logging.getLogger(__name__)
94b18763 12mlogger = terminal.MultiLogger(__name__)
d2e6a577
FG
13
14# TODO: get these out of here and into a common area for others to consume
15if platform.system() == 'FreeBSD':
16 FREEBSD = True
17 DEFAULT_FS_TYPE = 'zfs'
18 PROCDIR = '/compat/linux/proc'
19 # FreeBSD does not have blockdevices any more
20 BLOCKDIR = '/dev'
21 ROOTGROUP = 'wheel'
22else:
23 FREEBSD = False
24 DEFAULT_FS_TYPE = 'xfs'
25 PROCDIR = '/proc'
26 BLOCKDIR = '/sys/block'
27 ROOTGROUP = 'root'
28
29
30def generate_uuid():
31 return str(uuid.uuid4())
32
33
94b18763
FG
34def which(executable):
35 """find the location of an executable"""
36 locations = (
37 '/usr/local/bin',
38 '/bin',
39 '/usr/bin',
40 '/usr/local/sbin',
41 '/usr/sbin',
42 '/sbin',
43 )
44
45 for location in locations:
46 executable_path = os.path.join(location, executable)
47 if os.path.exists(executable_path) and os.path.isfile(executable_path):
48 return executable_path
49 mlogger.warning('Absolute path not found for executable: %s', executable)
50 mlogger.warning('Ensure $PATH environment variable contains common executable locations')
51 # fallback to just returning the argument as-is, to prevent a hard fail,
52 # and hoping that the system might have the executable somewhere custom
53 return executable
54
55
d2e6a577
FG
56def get_ceph_user_ids():
57 """
58 Return the id and gid of the ceph user
59 """
60 try:
61 user = pwd.getpwnam('ceph')
62 except KeyError:
63 # is this even possible?
64 raise RuntimeError('"ceph" user is not available in the current system')
65 return user[2], user[3]
66
67
68def mkdir_p(path, chown=True):
69 """
70 A `mkdir -p` that defaults to chown the path to the ceph user
71 """
72 try:
73 os.mkdir(path)
74 except OSError as e:
75 if e.errno == errno.EEXIST:
76 pass
77 else:
78 raise
79 if chown:
80 uid, gid = get_ceph_user_ids()
81 os.chown(path, uid, gid)
82
83
84def chown(path, recursive=True):
85 """
86 ``chown`` a path to the ceph user (uid and guid fetched at runtime)
87 """
88 uid, gid = get_ceph_user_ids()
89 if os.path.islink(path):
90 path = os.path.realpath(path)
91 if recursive:
92 process.run(['chown', '-R', 'ceph:ceph', path])
93 else:
94 os.chown(path, uid, gid)
95
96
3efd9988 97def is_binary(path):
d2e6a577 98 """
3efd9988
FG
99 Detect if a file path is a binary or not. Will falsely report as binary
100 when utf-16 encoded. In the ceph universe there is no such risk (yet)
101 """
102 with open(path, 'rb') as fp:
103 contents = fp.read(8192)
104 if b'\x00' in contents: # a null byte may signal binary
105 return True
106 return False
107
108
109class tmp_mount(object):
110 """
111 Temporarily mount a device on a temporary directory,
112 and unmount it upon exit
b32b8144
FG
113
114 When ``encrypted`` is set to ``True``, the exit method will call out to
115 close the device so that it doesn't remain open after mounting. It is
116 assumed that it will be open because otherwise it wouldn't be possible to
117 mount in the first place
3efd9988
FG
118 """
119
b32b8144 120 def __init__(self, device, encrypted=False):
3efd9988
FG
121 self.device = device
122 self.path = None
b32b8144 123 self.encrypted = encrypted
3efd9988
FG
124
125 def __enter__(self):
126 self.path = tempfile.mkdtemp()
127 process.run([
3efd9988
FG
128 'mount',
129 '-v',
130 self.device,
131 self.path
132 ])
133 return self.path
134
135 def __exit__(self, exc_type, exc_val, exc_tb):
136 process.run([
3efd9988
FG
137 'umount',
138 '-v',
139 self.path
140 ])
b32b8144
FG
141 if self.encrypted:
142 # avoid a circular import from the encryption module
143 from ceph_volume.util import encryption
144 encryption.dmcrypt_close(self.device)
145
146
147def unmount(path):
148 """
149 Removes mounts at the given path
150 """
151 process.run([
152 'umount',
153 '-v',
154 path,
155 ])
3efd9988
FG
156
157
158def path_is_mounted(path, destination=None):
159 """
160 Check if the given path is mounted
161 """
162 mounts = get_mounts(paths=True)
163 realpath = os.path.realpath(path)
164 mounted_locations = mounts.get(realpath, [])
165
166 if destination:
3efd9988
FG
167 return destination in mounted_locations
168 return mounted_locations != []
169
d2e6a577 170
3efd9988
FG
171def device_is_mounted(dev, destination=None):
172 """
173 Check if the given device is mounted, optionally validating that a
174 destination exists
175 """
b32b8144
FG
176 plain_mounts = get_mounts(devices=True)
177 realpath_mounts = get_mounts(devices=True, realpath=True)
178 realpath_dev = os.path.realpath(dev) if dev.startswith('/') else dev
3efd9988 179 destination = os.path.realpath(destination) if destination else None
b32b8144
FG
180 # plain mounts
181 plain_dev_mounts = plain_mounts.get(dev, [])
182 realpath_dev_mounts = plain_mounts.get(realpath_dev, [])
183 # realpath mounts
184 plain_dev_real_mounts = realpath_mounts.get(dev, [])
185 realpath_dev_real_mounts = realpath_mounts.get(realpath_dev, [])
186
187 mount_locations = [
188 plain_dev_mounts,
189 realpath_dev_mounts,
190 plain_dev_real_mounts,
191 realpath_dev_real_mounts
192 ]
193
194 for mounts in mount_locations:
195 if mounts: # we have a matching mount
196 if destination:
197 if destination in mounts:
198 logger.info(
199 '%s detected as mounted, exists at destination: %s', dev, destination
200 )
201 return True
202 else:
203 logger.info('%s was found as mounted')
204 return True
205 logger.info('%s was not found as mounted')
206 return False
3efd9988
FG
207
208
b32b8144 209def get_mounts(devices=False, paths=False, realpath=False):
3efd9988
FG
210 """
211 Create a mapping of all available system mounts so that other helpers can
212 detect nicely what path or device is mounted
d2e6a577 213
3efd9988
FG
214 It ignores (most of) non existing devices, but since some setups might need
215 some extra device information, it will make an exception for:
d2e6a577 216
3efd9988
FG
217 - tmpfs
218 - devtmpfs
d2e6a577 219
3efd9988
FG
220 If ``devices`` is set to ``True`` the mapping will be a device-to-path(s),
221 if ``paths`` is set to ``True`` then the mapping will be
222 a path-to-device(s)
b32b8144
FG
223
224 :param realpath: Resolve devices to use their realpaths. This is useful for
225 paths like LVM where more than one path can point to the same device
d2e6a577 226 """
3efd9988
FG
227 devices_mounted = {}
228 paths_mounted = {}
229 do_not_skip = ['tmpfs', 'devtmpfs']
230 default_to_devices = devices is False and paths is False
231
232 with open(PROCDIR + '/mounts', 'rb') as mounts:
233 proc_mounts = mounts.readlines()
234
235 for line in proc_mounts:
236 fields = [as_string(f) for f in line.split()]
237 if len(fields) < 3:
238 continue
b32b8144
FG
239 if realpath:
240 device = os.path.realpath(fields[0]) if fields[0].startswith('/') else fields[0]
241 else:
242 device = fields[0]
3efd9988
FG
243 path = os.path.realpath(fields[1])
244 # only care about actual existing devices
245 if not os.path.exists(device) or not device.startswith('/'):
246 if device not in do_not_skip:
d2e6a577 247 continue
3efd9988
FG
248 if device in devices_mounted.keys():
249 devices_mounted[device].append(path)
250 else:
251 devices_mounted[device] = [path]
252 if path in paths_mounted.keys():
253 paths_mounted[path].append(device)
254 else:
255 paths_mounted[path] = [device]
256
257 # Default to returning information for devices if
258 if devices is True or default_to_devices:
259 return devices_mounted
260 else:
261 return paths_mounted