]>
git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/util/system.py
8 from ceph_volume
import process
, terminal
9 from . import as_string
11 # python2 has no FileNotFoundError
15 FileNotFoundError
= OSError
17 logger
= logging
.getLogger(__name__
)
18 mlogger
= terminal
.MultiLogger(__name__
)
20 # TODO: get these out of here and into a common area for others to consume
21 if platform
.system() == 'FreeBSD':
23 DEFAULT_FS_TYPE
= 'zfs'
24 PROCDIR
= '/compat/linux/proc'
25 # FreeBSD does not have blockdevices any more
30 DEFAULT_FS_TYPE
= 'xfs'
32 BLOCKDIR
= '/sys/block'
37 return str(uuid
.uuid4())
40 def which(executable
):
41 """find the location of an executable"""
42 def _get_path(executable
, locations
):
43 for location
in locations
:
44 executable_path
= os
.path
.join(location
, executable
)
45 if os
.path
.exists(executable_path
) and os
.path
.isfile(executable_path
):
46 return executable_path
49 path
= os
.getenv('PATH', '')
50 path_locations
= path
.split(':')
51 exec_in_path
= _get_path(executable
, path_locations
)
54 mlogger
.warning('Executable {} not in PATH: {}'.format(executable
, path
))
64 exec_in_static_locations
= _get_path(executable
, static_locations
)
65 if exec_in_static_locations
:
66 mlogger
.warning('Found executable under {}, please ensure $PATH is set correctly!'.format(exec_in_static_locations
))
67 return exec_in_static_locations
68 # fallback to just returning the argument as-is, to prevent a hard fail,
69 # and hoping that the system might have the executable somewhere custom
73 def get_ceph_user_ids():
75 Return the id and gid of the ceph user
78 user
= pwd
.getpwnam('ceph')
80 # is this even possible?
81 raise RuntimeError('"ceph" user is not available in the current system')
82 return user
[2], user
[3]
85 def get_file_contents(path
, default
=''):
87 if not os
.path
.exists(path
):
90 with
open(path
, 'r') as open_file
:
91 contents
= open_file
.read().strip()
93 logger
.exception('Failed to read contents from: %s' % path
)
98 def mkdir_p(path
, chown
=True):
100 A `mkdir -p` that defaults to chown the path to the ceph user
105 if e
.errno
== errno
.EEXIST
:
110 uid
, gid
= get_ceph_user_ids()
111 os
.chown(path
, uid
, gid
)
114 def chown(path
, recursive
=True):
116 ``chown`` a path to the ceph user (uid and guid fetched at runtime)
118 uid
, gid
= get_ceph_user_ids()
119 if os
.path
.islink(path
):
120 process
.run(['chown', '-h', 'ceph:ceph', path
])
121 path
= os
.path
.realpath(path
)
123 process
.run(['chown', '-R', 'ceph:ceph', path
])
125 os
.chown(path
, uid
, gid
)
130 Detect if a file path is a binary or not. Will falsely report as binary
131 when utf-16 encoded. In the ceph universe there is no such risk (yet)
133 with
open(path
, 'rb') as fp
:
134 contents
= fp
.read(8192)
135 if b
'\x00' in contents
: # a null byte may signal binary
140 class tmp_mount(object):
142 Temporarily mount a device on a temporary directory,
143 and unmount it upon exit
145 When ``encrypted`` is set to ``True``, the exit method will call out to
146 close the device so that it doesn't remain open after mounting. It is
147 assumed that it will be open because otherwise it wouldn't be possible to
148 mount in the first place
151 def __init__(self
, device
, encrypted
=False):
154 self
.encrypted
= encrypted
157 self
.path
= tempfile
.mkdtemp()
166 def __exit__(self
, exc_type
, exc_val
, exc_tb
):
173 # avoid a circular import from the encryption module
174 from ceph_volume
.util
import encryption
175 encryption
.dmcrypt_close(self
.device
)
178 def unmount_tmpfs(path
):
180 Removes the mount at the given path iff the path is a tmpfs mount point.
181 Otherwise no action is taken.
183 _out
, _err
, rc
= process
.call(['findmnt', '-t', 'tmpfs', '-M', path
])
185 logger
.info('{} does not appear to be a tmpfs mount'.format(path
))
187 logger
.info('Unmounting tmpfs path at {}'.format( path
))
193 Removes mounts at the given path
202 def path_is_mounted(path
, destination
=None):
204 Check if the given path is mounted
206 mounts
= get_mounts(paths
=True)
207 realpath
= os
.path
.realpath(path
)
208 mounted_locations
= mounts
.get(realpath
, [])
211 return destination
in mounted_locations
212 return mounted_locations
!= []
215 def device_is_mounted(dev
, destination
=None):
217 Check if the given device is mounted, optionally validating that a
220 plain_mounts
= get_mounts(devices
=True)
221 realpath_mounts
= get_mounts(devices
=True, realpath
=True)
222 realpath_dev
= os
.path
.realpath(dev
) if dev
.startswith('/') else dev
223 destination
= os
.path
.realpath(destination
) if destination
else None
225 plain_dev_mounts
= plain_mounts
.get(dev
, [])
226 realpath_dev_mounts
= plain_mounts
.get(realpath_dev
, [])
228 plain_dev_real_mounts
= realpath_mounts
.get(dev
, [])
229 realpath_dev_real_mounts
= realpath_mounts
.get(realpath_dev
, [])
234 plain_dev_real_mounts
,
235 realpath_dev_real_mounts
238 for mounts
in mount_locations
:
239 if mounts
: # we have a matching mount
241 if destination
in mounts
:
243 '%s detected as mounted, exists at destination: %s', dev
, destination
247 logger
.info('%s was found as mounted', dev
)
249 logger
.info('%s was not found as mounted', dev
)
253 def get_mounts(devices
=False, paths
=False, realpath
=False):
255 Create a mapping of all available system mounts so that other helpers can
256 detect nicely what path or device is mounted
258 It ignores (most of) non existing devices, but since some setups might need
259 some extra device information, it will make an exception for:
265 If ``devices`` is set to ``True`` the mapping will be a device-to-path(s),
266 if ``paths`` is set to ``True`` then the mapping will be
269 :param realpath: Resolve devices to use their realpaths. This is useful for
270 paths like LVM where more than one path can point to the same device
274 do_not_skip
= ['tmpfs', 'devtmpfs', '/dev/root']
275 default_to_devices
= devices
is False and paths
is False
277 with
open(PROCDIR
+ '/mounts', 'rb') as mounts
:
278 proc_mounts
= mounts
.readlines()
280 for line
in proc_mounts
:
281 fields
= [as_string(f
) for f
in line
.split()]
285 device
= os
.path
.realpath(fields
[0]) if fields
[0].startswith('/') else fields
[0]
288 path
= os
.path
.realpath(fields
[1])
289 # only care about actual existing devices
290 if not os
.path
.exists(device
) or not device
.startswith('/'):
291 if device
not in do_not_skip
:
293 if device
in devices_mounted
.keys():
294 devices_mounted
[device
].append(path
)
296 devices_mounted
[device
] = [path
]
297 if path
in paths_mounted
.keys():
298 paths_mounted
[path
].append(device
)
300 paths_mounted
[path
] = [device
]
302 # Default to returning information for devices if
303 if devices
is True or default_to_devices
:
304 return devices_mounted
309 def set_context(path
, recursive
=False):
311 Calls ``restorecon`` to set the proper context on SELinux systems. Only if
312 the ``restorecon`` executable is found anywhere in the path it will get
315 If the ``CEPH_VOLUME_SKIP_RESTORECON`` environment variable is set to
316 any of: "1", "true", "yes" the call will be skipped as well.
318 Finally, if SELinux is not enabled, or not available in the system,
319 ``restorecon`` will not be called. This is checked by calling out to the
320 ``selinuxenabled`` executable. If that tool is not installed or returns
321 a non-zero exit status then no further action is taken and this function
324 skip
= os
.environ
.get('CEPH_VOLUME_SKIP_RESTORECON', '')
325 if skip
.lower() in ['1', 'true', 'yes']:
327 'CEPH_VOLUME_SKIP_RESTORECON environ is set, will not call restorecon'
332 stdout
, stderr
, code
= process
.call(['selinuxenabled'],
333 verbose_on_failure
=False)
334 except FileNotFoundError
:
335 logger
.info('No SELinux found, skipping call to restorecon')
339 logger
.info('SELinux is not enabled, will not call restorecon')
342 # restore selinux context to default policy values
343 if which('restorecon').startswith('/'):
345 process
.run(['restorecon', '-R', path
])
347 process
.run(['restorecon', path
])