]> git.proxmox.com Git - ceph.git/blame - ceph/src/ceph-volume/ceph_volume/util/system.py
update sources to 12.2.7
[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):
28e407b8 90 process.run(['chown', '-h', 'ceph:ceph', path])
d2e6a577
FG
91 path = os.path.realpath(path)
92 if recursive:
93 process.run(['chown', '-R', 'ceph:ceph', path])
94 else:
95 os.chown(path, uid, gid)
96
97
3efd9988 98def is_binary(path):
d2e6a577 99 """
3efd9988
FG
100 Detect if a file path is a binary or not. Will falsely report as binary
101 when utf-16 encoded. In the ceph universe there is no such risk (yet)
102 """
103 with open(path, 'rb') as fp:
104 contents = fp.read(8192)
105 if b'\x00' in contents: # a null byte may signal binary
106 return True
107 return False
108
109
110class tmp_mount(object):
111 """
112 Temporarily mount a device on a temporary directory,
113 and unmount it upon exit
b32b8144
FG
114
115 When ``encrypted`` is set to ``True``, the exit method will call out to
116 close the device so that it doesn't remain open after mounting. It is
117 assumed that it will be open because otherwise it wouldn't be possible to
118 mount in the first place
3efd9988
FG
119 """
120
b32b8144 121 def __init__(self, device, encrypted=False):
3efd9988
FG
122 self.device = device
123 self.path = None
b32b8144 124 self.encrypted = encrypted
3efd9988
FG
125
126 def __enter__(self):
127 self.path = tempfile.mkdtemp()
128 process.run([
3efd9988
FG
129 'mount',
130 '-v',
131 self.device,
132 self.path
133 ])
134 return self.path
135
136 def __exit__(self, exc_type, exc_val, exc_tb):
137 process.run([
3efd9988
FG
138 'umount',
139 '-v',
140 self.path
141 ])
b32b8144
FG
142 if self.encrypted:
143 # avoid a circular import from the encryption module
144 from ceph_volume.util import encryption
145 encryption.dmcrypt_close(self.device)
146
147
148def unmount(path):
149 """
150 Removes mounts at the given path
151 """
152 process.run([
153 'umount',
154 '-v',
155 path,
156 ])
3efd9988
FG
157
158
159def path_is_mounted(path, destination=None):
160 """
161 Check if the given path is mounted
162 """
163 mounts = get_mounts(paths=True)
164 realpath = os.path.realpath(path)
165 mounted_locations = mounts.get(realpath, [])
166
167 if destination:
3efd9988
FG
168 return destination in mounted_locations
169 return mounted_locations != []
170
d2e6a577 171
3efd9988
FG
172def device_is_mounted(dev, destination=None):
173 """
174 Check if the given device is mounted, optionally validating that a
175 destination exists
176 """
b32b8144
FG
177 plain_mounts = get_mounts(devices=True)
178 realpath_mounts = get_mounts(devices=True, realpath=True)
179 realpath_dev = os.path.realpath(dev) if dev.startswith('/') else dev
3efd9988 180 destination = os.path.realpath(destination) if destination else None
b32b8144
FG
181 # plain mounts
182 plain_dev_mounts = plain_mounts.get(dev, [])
183 realpath_dev_mounts = plain_mounts.get(realpath_dev, [])
184 # realpath mounts
185 plain_dev_real_mounts = realpath_mounts.get(dev, [])
186 realpath_dev_real_mounts = realpath_mounts.get(realpath_dev, [])
187
188 mount_locations = [
189 plain_dev_mounts,
190 realpath_dev_mounts,
191 plain_dev_real_mounts,
192 realpath_dev_real_mounts
193 ]
194
195 for mounts in mount_locations:
196 if mounts: # we have a matching mount
197 if destination:
198 if destination in mounts:
199 logger.info(
200 '%s detected as mounted, exists at destination: %s', dev, destination
201 )
202 return True
203 else:
204 logger.info('%s was found as mounted')
205 return True
206 logger.info('%s was not found as mounted')
207 return False
3efd9988
FG
208
209
b32b8144 210def get_mounts(devices=False, paths=False, realpath=False):
3efd9988
FG
211 """
212 Create a mapping of all available system mounts so that other helpers can
213 detect nicely what path or device is mounted
d2e6a577 214
3efd9988
FG
215 It ignores (most of) non existing devices, but since some setups might need
216 some extra device information, it will make an exception for:
d2e6a577 217
3efd9988
FG
218 - tmpfs
219 - devtmpfs
d2e6a577 220
3efd9988
FG
221 If ``devices`` is set to ``True`` the mapping will be a device-to-path(s),
222 if ``paths`` is set to ``True`` then the mapping will be
223 a path-to-device(s)
b32b8144
FG
224
225 :param realpath: Resolve devices to use their realpaths. This is useful for
226 paths like LVM where more than one path can point to the same device
d2e6a577 227 """
3efd9988
FG
228 devices_mounted = {}
229 paths_mounted = {}
230 do_not_skip = ['tmpfs', 'devtmpfs']
231 default_to_devices = devices is False and paths is False
232
233 with open(PROCDIR + '/mounts', 'rb') as mounts:
234 proc_mounts = mounts.readlines()
235
236 for line in proc_mounts:
237 fields = [as_string(f) for f in line.split()]
238 if len(fields) < 3:
239 continue
b32b8144
FG
240 if realpath:
241 device = os.path.realpath(fields[0]) if fields[0].startswith('/') else fields[0]
242 else:
243 device = fields[0]
3efd9988
FG
244 path = os.path.realpath(fields[1])
245 # only care about actual existing devices
246 if not os.path.exists(device) or not device.startswith('/'):
247 if device not in do_not_skip:
d2e6a577 248 continue
3efd9988
FG
249 if device in devices_mounted.keys():
250 devices_mounted[device].append(path)
251 else:
252 devices_mounted[device] = [path]
253 if path in paths_mounted.keys():
254 paths_mounted[path].append(device)
255 else:
256 paths_mounted[path] = [device]
257
258 # Default to returning information for devices if
259 if devices is True or default_to_devices:
260 return devices_mounted
261 else:
262 return paths_mounted