]>
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 FG |
7 | import uuid |
8 | from ceph_volume import process | |
9 | from . import as_string | |
10 | ||
b32b8144 | 11 | logger = logging.getLogger(__name__) |
d2e6a577 FG |
12 | |
13 | # TODO: get these out of here and into a common area for others to consume | |
14 | if platform.system() == 'FreeBSD': | |
15 | FREEBSD = True | |
16 | DEFAULT_FS_TYPE = 'zfs' | |
17 | PROCDIR = '/compat/linux/proc' | |
18 | # FreeBSD does not have blockdevices any more | |
19 | BLOCKDIR = '/dev' | |
20 | ROOTGROUP = 'wheel' | |
21 | else: | |
22 | FREEBSD = False | |
23 | DEFAULT_FS_TYPE = 'xfs' | |
24 | PROCDIR = '/proc' | |
25 | BLOCKDIR = '/sys/block' | |
26 | ROOTGROUP = 'root' | |
27 | ||
28 | ||
29 | def generate_uuid(): | |
30 | return str(uuid.uuid4()) | |
31 | ||
32 | ||
33 | def get_ceph_user_ids(): | |
34 | """ | |
35 | Return the id and gid of the ceph user | |
36 | """ | |
37 | try: | |
38 | user = pwd.getpwnam('ceph') | |
39 | except KeyError: | |
40 | # is this even possible? | |
41 | raise RuntimeError('"ceph" user is not available in the current system') | |
42 | return user[2], user[3] | |
43 | ||
44 | ||
45 | def mkdir_p(path, chown=True): | |
46 | """ | |
47 | A `mkdir -p` that defaults to chown the path to the ceph user | |
48 | """ | |
49 | try: | |
50 | os.mkdir(path) | |
51 | except OSError as e: | |
52 | if e.errno == errno.EEXIST: | |
53 | pass | |
54 | else: | |
55 | raise | |
56 | if chown: | |
57 | uid, gid = get_ceph_user_ids() | |
58 | os.chown(path, uid, gid) | |
59 | ||
60 | ||
61 | def chown(path, recursive=True): | |
62 | """ | |
63 | ``chown`` a path to the ceph user (uid and guid fetched at runtime) | |
64 | """ | |
65 | uid, gid = get_ceph_user_ids() | |
66 | if os.path.islink(path): | |
67 | path = os.path.realpath(path) | |
68 | if recursive: | |
69 | process.run(['chown', '-R', 'ceph:ceph', path]) | |
70 | else: | |
71 | os.chown(path, uid, gid) | |
72 | ||
73 | ||
3efd9988 | 74 | def is_binary(path): |
d2e6a577 | 75 | """ |
3efd9988 FG |
76 | Detect if a file path is a binary or not. Will falsely report as binary |
77 | when utf-16 encoded. In the ceph universe there is no such risk (yet) | |
78 | """ | |
79 | with open(path, 'rb') as fp: | |
80 | contents = fp.read(8192) | |
81 | if b'\x00' in contents: # a null byte may signal binary | |
82 | return True | |
83 | return False | |
84 | ||
85 | ||
86 | class tmp_mount(object): | |
87 | """ | |
88 | Temporarily mount a device on a temporary directory, | |
89 | and unmount it upon exit | |
b32b8144 FG |
90 | |
91 | When ``encrypted`` is set to ``True``, the exit method will call out to | |
92 | close the device so that it doesn't remain open after mounting. It is | |
93 | assumed that it will be open because otherwise it wouldn't be possible to | |
94 | mount in the first place | |
3efd9988 FG |
95 | """ |
96 | ||
b32b8144 | 97 | def __init__(self, device, encrypted=False): |
3efd9988 FG |
98 | self.device = device |
99 | self.path = None | |
b32b8144 | 100 | self.encrypted = encrypted |
3efd9988 FG |
101 | |
102 | def __enter__(self): | |
103 | self.path = tempfile.mkdtemp() | |
104 | process.run([ | |
3efd9988 FG |
105 | 'mount', |
106 | '-v', | |
107 | self.device, | |
108 | self.path | |
109 | ]) | |
110 | return self.path | |
111 | ||
112 | def __exit__(self, exc_type, exc_val, exc_tb): | |
113 | process.run([ | |
3efd9988 FG |
114 | 'umount', |
115 | '-v', | |
116 | self.path | |
117 | ]) | |
b32b8144 FG |
118 | if self.encrypted: |
119 | # avoid a circular import from the encryption module | |
120 | from ceph_volume.util import encryption | |
121 | encryption.dmcrypt_close(self.device) | |
122 | ||
123 | ||
124 | def unmount(path): | |
125 | """ | |
126 | Removes mounts at the given path | |
127 | """ | |
128 | process.run([ | |
129 | 'umount', | |
130 | '-v', | |
131 | path, | |
132 | ]) | |
3efd9988 FG |
133 | |
134 | ||
135 | def path_is_mounted(path, destination=None): | |
136 | """ | |
137 | Check if the given path is mounted | |
138 | """ | |
139 | mounts = get_mounts(paths=True) | |
140 | realpath = os.path.realpath(path) | |
141 | mounted_locations = mounts.get(realpath, []) | |
142 | ||
143 | if destination: | |
3efd9988 FG |
144 | return destination in mounted_locations |
145 | return mounted_locations != [] | |
146 | ||
d2e6a577 | 147 | |
3efd9988 FG |
148 | def device_is_mounted(dev, destination=None): |
149 | """ | |
150 | Check if the given device is mounted, optionally validating that a | |
151 | destination exists | |
152 | """ | |
b32b8144 FG |
153 | plain_mounts = get_mounts(devices=True) |
154 | realpath_mounts = get_mounts(devices=True, realpath=True) | |
155 | realpath_dev = os.path.realpath(dev) if dev.startswith('/') else dev | |
3efd9988 | 156 | destination = os.path.realpath(destination) if destination else None |
b32b8144 FG |
157 | # plain mounts |
158 | plain_dev_mounts = plain_mounts.get(dev, []) | |
159 | realpath_dev_mounts = plain_mounts.get(realpath_dev, []) | |
160 | # realpath mounts | |
161 | plain_dev_real_mounts = realpath_mounts.get(dev, []) | |
162 | realpath_dev_real_mounts = realpath_mounts.get(realpath_dev, []) | |
163 | ||
164 | mount_locations = [ | |
165 | plain_dev_mounts, | |
166 | realpath_dev_mounts, | |
167 | plain_dev_real_mounts, | |
168 | realpath_dev_real_mounts | |
169 | ] | |
170 | ||
171 | for mounts in mount_locations: | |
172 | if mounts: # we have a matching mount | |
173 | if destination: | |
174 | if destination in mounts: | |
175 | logger.info( | |
176 | '%s detected as mounted, exists at destination: %s', dev, destination | |
177 | ) | |
178 | return True | |
179 | else: | |
180 | logger.info('%s was found as mounted') | |
181 | return True | |
182 | logger.info('%s was not found as mounted') | |
183 | return False | |
3efd9988 FG |
184 | |
185 | ||
b32b8144 | 186 | def get_mounts(devices=False, paths=False, realpath=False): |
3efd9988 FG |
187 | """ |
188 | Create a mapping of all available system mounts so that other helpers can | |
189 | detect nicely what path or device is mounted | |
d2e6a577 | 190 | |
3efd9988 FG |
191 | It ignores (most of) non existing devices, but since some setups might need |
192 | some extra device information, it will make an exception for: | |
d2e6a577 | 193 | |
3efd9988 FG |
194 | - tmpfs |
195 | - devtmpfs | |
d2e6a577 | 196 | |
3efd9988 FG |
197 | If ``devices`` is set to ``True`` the mapping will be a device-to-path(s), |
198 | if ``paths`` is set to ``True`` then the mapping will be | |
199 | a path-to-device(s) | |
b32b8144 FG |
200 | |
201 | :param realpath: Resolve devices to use their realpaths. This is useful for | |
202 | paths like LVM where more than one path can point to the same device | |
d2e6a577 | 203 | """ |
3efd9988 FG |
204 | devices_mounted = {} |
205 | paths_mounted = {} | |
206 | do_not_skip = ['tmpfs', 'devtmpfs'] | |
207 | default_to_devices = devices is False and paths is False | |
208 | ||
209 | with open(PROCDIR + '/mounts', 'rb') as mounts: | |
210 | proc_mounts = mounts.readlines() | |
211 | ||
212 | for line in proc_mounts: | |
213 | fields = [as_string(f) for f in line.split()] | |
214 | if len(fields) < 3: | |
215 | continue | |
b32b8144 FG |
216 | if realpath: |
217 | device = os.path.realpath(fields[0]) if fields[0].startswith('/') else fields[0] | |
218 | else: | |
219 | device = fields[0] | |
3efd9988 FG |
220 | path = os.path.realpath(fields[1]) |
221 | # only care about actual existing devices | |
222 | if not os.path.exists(device) or not device.startswith('/'): | |
223 | if device not in do_not_skip: | |
d2e6a577 | 224 | continue |
3efd9988 FG |
225 | if device in devices_mounted.keys(): |
226 | devices_mounted[device].append(path) | |
227 | else: | |
228 | devices_mounted[device] = [path] | |
229 | if path in paths_mounted.keys(): | |
230 | paths_mounted[path].append(device) | |
231 | else: | |
232 | paths_mounted[path] = [device] | |
233 | ||
234 | # Default to returning information for devices if | |
235 | if devices is True or default_to_devices: | |
236 | return devices_mounted | |
237 | else: | |
238 | return paths_mounted |