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