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