]> git.proxmox.com Git - ceph.git/blob - 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
1 import errno
2 import logging
3 import os
4 import pwd
5 import platform
6 import tempfile
7 import uuid
8 from ceph_volume import process, terminal
9 from . import as_string
10
11 logger = logging.getLogger(__name__)
12 mlogger = terminal.MultiLogger(__name__)
13
14 # TODO: get these out of here and into a common area for others to consume
15 if 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'
22 else:
23 FREEBSD = False
24 DEFAULT_FS_TYPE = 'xfs'
25 PROCDIR = '/proc'
26 BLOCKDIR = '/sys/block'
27 ROOTGROUP = 'root'
28
29
30 def generate_uuid():
31 return str(uuid.uuid4())
32
33
34 def 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
56 def 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
68 def 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
84 def 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 process.run(['chown', '-h', 'ceph:ceph', path])
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
98 def is_binary(path):
99 """
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
110 class tmp_mount(object):
111 """
112 Temporarily mount a device on a temporary directory,
113 and unmount it upon exit
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
119 """
120
121 def __init__(self, device, encrypted=False):
122 self.device = device
123 self.path = None
124 self.encrypted = encrypted
125
126 def __enter__(self):
127 self.path = tempfile.mkdtemp()
128 process.run([
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([
138 'umount',
139 '-v',
140 self.path
141 ])
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
148 def unmount(path):
149 """
150 Removes mounts at the given path
151 """
152 process.run([
153 'umount',
154 '-v',
155 path,
156 ])
157
158
159 def 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:
168 return destination in mounted_locations
169 return mounted_locations != []
170
171
172 def device_is_mounted(dev, destination=None):
173 """
174 Check if the given device is mounted, optionally validating that a
175 destination exists
176 """
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
180 destination = os.path.realpath(destination) if destination else None
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
208
209
210 def get_mounts(devices=False, paths=False, realpath=False):
211 """
212 Create a mapping of all available system mounts so that other helpers can
213 detect nicely what path or device is mounted
214
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:
217
218 - tmpfs
219 - devtmpfs
220
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)
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
227 """
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
240 if realpath:
241 device = os.path.realpath(fields[0]) if fields[0].startswith('/') else fields[0]
242 else:
243 device = fields[0]
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:
248 continue
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