]>
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:
264 If ``devices`` is set to ``True`` the mapping will be a device-to-path(s),
265 if ``paths`` is set to ``True`` then the mapping will be
268 :param realpath: Resolve devices to use their realpaths. This is useful for
269 paths like LVM where more than one path can point to the same device
273 do_not_skip
= ['tmpfs', 'devtmpfs']
274 default_to_devices
= devices
is False and paths
is False
276 with
open(PROCDIR
+ '/mounts', 'rb') as mounts
:
277 proc_mounts
= mounts
.readlines()
279 for line
in proc_mounts
:
280 fields
= [as_string(f
) for f
in line
.split()]
284 device
= os
.path
.realpath(fields
[0]) if fields
[0].startswith('/') else fields
[0]
287 path
= os
.path
.realpath(fields
[1])
288 # only care about actual existing devices
289 if not os
.path
.exists(device
) or not device
.startswith('/'):
290 if device
not in do_not_skip
:
292 if device
in devices_mounted
.keys():
293 devices_mounted
[device
].append(path
)
295 devices_mounted
[device
] = [path
]
296 if path
in paths_mounted
.keys():
297 paths_mounted
[path
].append(device
)
299 paths_mounted
[path
] = [device
]
301 # Default to returning information for devices if
302 if devices
is True or default_to_devices
:
303 return devices_mounted
308 def set_context(path
, recursive
=False):
310 Calls ``restorecon`` to set the proper context on SELinux systems. Only if
311 the ``restorecon`` executable is found anywhere in the path it will get
314 If the ``CEPH_VOLUME_SKIP_RESTORECON`` environment variable is set to
315 any of: "1", "true", "yes" the call will be skipped as well.
317 Finally, if SELinux is not enabled, or not available in the system,
318 ``restorecon`` will not be called. This is checked by calling out to the
319 ``selinuxenabled`` executable. If that tool is not installed or returns
320 a non-zero exit status then no further action is taken and this function
323 skip
= os
.environ
.get('CEPH_VOLUME_SKIP_RESTORECON', '')
324 if skip
.lower() in ['1', 'true', 'yes']:
326 'CEPH_VOLUME_SKIP_RESTORECON environ is set, will not call restorecon'
331 stdout
, stderr
, code
= process
.call(['selinuxenabled'],
332 verbose_on_failure
=False)
333 except FileNotFoundError
:
334 logger
.info('No SELinux found, skipping call to restorecon')
338 logger
.info('SELinux is not enabled, will not call restorecon')
341 # restore selinux context to default policy values
342 if which('restorecon').startswith('/'):
344 process
.run(['restorecon', '-R', path
])
346 process
.run(['restorecon', path
])