]>
Commit | Line | Data |
---|---|---|
d2e6a577 | 1 | import errno |
b32b8144 | 2 | import logging |
d2e6a577 FG |
3 | import os |
4 | import pwd | |
5 | import platform | |
3efd9988 | 6 | import tempfile |
d2e6a577 | 7 | import uuid |
94b18763 | 8 | from ceph_volume import process, terminal |
d2e6a577 FG |
9 | from . import as_string |
10 | ||
b32b8144 | 11 | logger = logging.getLogger(__name__) |
94b18763 | 12 | mlogger = terminal.MultiLogger(__name__) |
d2e6a577 FG |
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 | ||
94b18763 FG |
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 | ||
d2e6a577 FG |
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 | path = os.path.realpath(path) | |
91 | if recursive: | |
92 | process.run(['chown', '-R', 'ceph:ceph', path]) | |
93 | else: | |
94 | os.chown(path, uid, gid) | |
95 | ||
96 | ||
3efd9988 | 97 | def is_binary(path): |
d2e6a577 | 98 | """ |
3efd9988 FG |
99 | Detect if a file path is a binary or not. Will falsely report as binary |
100 | when utf-16 encoded. In the ceph universe there is no such risk (yet) | |
101 | """ | |
102 | with open(path, 'rb') as fp: | |
103 | contents = fp.read(8192) | |
104 | if b'\x00' in contents: # a null byte may signal binary | |
105 | return True | |
106 | return False | |
107 | ||
108 | ||
109 | class tmp_mount(object): | |
110 | """ | |
111 | Temporarily mount a device on a temporary directory, | |
112 | and unmount it upon exit | |
b32b8144 FG |
113 | |
114 | When ``encrypted`` is set to ``True``, the exit method will call out to | |
115 | close the device so that it doesn't remain open after mounting. It is | |
116 | assumed that it will be open because otherwise it wouldn't be possible to | |
117 | mount in the first place | |
3efd9988 FG |
118 | """ |
119 | ||
b32b8144 | 120 | def __init__(self, device, encrypted=False): |
3efd9988 FG |
121 | self.device = device |
122 | self.path = None | |
b32b8144 | 123 | self.encrypted = encrypted |
3efd9988 FG |
124 | |
125 | def __enter__(self): | |
126 | self.path = tempfile.mkdtemp() | |
127 | process.run([ | |
3efd9988 FG |
128 | 'mount', |
129 | '-v', | |
130 | self.device, | |
131 | self.path | |
132 | ]) | |
133 | return self.path | |
134 | ||
135 | def __exit__(self, exc_type, exc_val, exc_tb): | |
136 | process.run([ | |
3efd9988 FG |
137 | 'umount', |
138 | '-v', | |
139 | self.path | |
140 | ]) | |
b32b8144 FG |
141 | if self.encrypted: |
142 | # avoid a circular import from the encryption module | |
143 | from ceph_volume.util import encryption | |
144 | encryption.dmcrypt_close(self.device) | |
145 | ||
146 | ||
147 | def unmount(path): | |
148 | """ | |
149 | Removes mounts at the given path | |
150 | """ | |
151 | process.run([ | |
152 | 'umount', | |
153 | '-v', | |
154 | path, | |
155 | ]) | |
3efd9988 FG |
156 | |
157 | ||
158 | def path_is_mounted(path, destination=None): | |
159 | """ | |
160 | Check if the given path is mounted | |
161 | """ | |
162 | mounts = get_mounts(paths=True) | |
163 | realpath = os.path.realpath(path) | |
164 | mounted_locations = mounts.get(realpath, []) | |
165 | ||
166 | if destination: | |
3efd9988 FG |
167 | return destination in mounted_locations |
168 | return mounted_locations != [] | |
169 | ||
d2e6a577 | 170 | |
3efd9988 FG |
171 | def device_is_mounted(dev, destination=None): |
172 | """ | |
173 | Check if the given device is mounted, optionally validating that a | |
174 | destination exists | |
175 | """ | |
b32b8144 FG |
176 | plain_mounts = get_mounts(devices=True) |
177 | realpath_mounts = get_mounts(devices=True, realpath=True) | |
178 | realpath_dev = os.path.realpath(dev) if dev.startswith('/') else dev | |
3efd9988 | 179 | destination = os.path.realpath(destination) if destination else None |
b32b8144 FG |
180 | # plain mounts |
181 | plain_dev_mounts = plain_mounts.get(dev, []) | |
182 | realpath_dev_mounts = plain_mounts.get(realpath_dev, []) | |
183 | # realpath mounts | |
184 | plain_dev_real_mounts = realpath_mounts.get(dev, []) | |
185 | realpath_dev_real_mounts = realpath_mounts.get(realpath_dev, []) | |
186 | ||
187 | mount_locations = [ | |
188 | plain_dev_mounts, | |
189 | realpath_dev_mounts, | |
190 | plain_dev_real_mounts, | |
191 | realpath_dev_real_mounts | |
192 | ] | |
193 | ||
194 | for mounts in mount_locations: | |
195 | if mounts: # we have a matching mount | |
196 | if destination: | |
197 | if destination in mounts: | |
198 | logger.info( | |
199 | '%s detected as mounted, exists at destination: %s', dev, destination | |
200 | ) | |
201 | return True | |
202 | else: | |
203 | logger.info('%s was found as mounted') | |
204 | return True | |
205 | logger.info('%s was not found as mounted') | |
206 | return False | |
3efd9988 FG |
207 | |
208 | ||
b32b8144 | 209 | def get_mounts(devices=False, paths=False, realpath=False): |
3efd9988 FG |
210 | """ |
211 | Create a mapping of all available system mounts so that other helpers can | |
212 | detect nicely what path or device is mounted | |
d2e6a577 | 213 | |
3efd9988 FG |
214 | It ignores (most of) non existing devices, but since some setups might need |
215 | some extra device information, it will make an exception for: | |
d2e6a577 | 216 | |
3efd9988 FG |
217 | - tmpfs |
218 | - devtmpfs | |
d2e6a577 | 219 | |
3efd9988 FG |
220 | If ``devices`` is set to ``True`` the mapping will be a device-to-path(s), |
221 | if ``paths`` is set to ``True`` then the mapping will be | |
222 | a path-to-device(s) | |
b32b8144 FG |
223 | |
224 | :param realpath: Resolve devices to use their realpaths. This is useful for | |
225 | paths like LVM where more than one path can point to the same device | |
d2e6a577 | 226 | """ |
3efd9988 FG |
227 | devices_mounted = {} |
228 | paths_mounted = {} | |
229 | do_not_skip = ['tmpfs', 'devtmpfs'] | |
230 | default_to_devices = devices is False and paths is False | |
231 | ||
232 | with open(PROCDIR + '/mounts', 'rb') as mounts: | |
233 | proc_mounts = mounts.readlines() | |
234 | ||
235 | for line in proc_mounts: | |
236 | fields = [as_string(f) for f in line.split()] | |
237 | if len(fields) < 3: | |
238 | continue | |
b32b8144 FG |
239 | if realpath: |
240 | device = os.path.realpath(fields[0]) if fields[0].startswith('/') else fields[0] | |
241 | else: | |
242 | device = fields[0] | |
3efd9988 FG |
243 | path = os.path.realpath(fields[1]) |
244 | # only care about actual existing devices | |
245 | if not os.path.exists(device) or not device.startswith('/'): | |
246 | if device not in do_not_skip: | |
d2e6a577 | 247 | continue |
3efd9988 FG |
248 | if device in devices_mounted.keys(): |
249 | devices_mounted[device].append(path) | |
250 | else: | |
251 | devices_mounted[device] = [path] | |
252 | if path in paths_mounted.keys(): | |
253 | paths_mounted[path].append(device) | |
254 | else: | |
255 | paths_mounted[path] = [device] | |
256 | ||
257 | # Default to returning information for devices if | |
258 | if devices is True or default_to_devices: | |
259 | return devices_mounted | |
260 | else: | |
261 | return paths_mounted |