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