]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | /* | |
4 | * Ceph - scalable distributed file system | |
5 | * | |
6 | * Copyright (C) 2015 John Spray <john.spray@redhat.com> | |
7 | * | |
8 | * This is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU Lesser General Public | |
10 | * License version 2.1, as published by the Free Software | |
11 | * Foundation. See file COPYING. | |
12 | */ | |
13 | ||
14 | ||
15 | #include "common/ceph_argparse.h" | |
16 | #include "common/errno.h" | |
17 | ||
18 | #include "mds/SessionMap.h" | |
19 | #include "mds/InoTable.h" | |
20 | #include "mds/SnapServer.h" | |
21 | ||
22 | #include "TableTool.h" | |
23 | ||
24 | ||
25 | #define dout_context g_ceph_context | |
26 | #define dout_subsys ceph_subsys_mds | |
27 | #undef dout_prefix | |
28 | #define dout_prefix *_dout << __func__ << ": " | |
29 | ||
20effc67 TL |
30 | using namespace std; |
31 | ||
7c673cae FG |
32 | void TableTool::usage() |
33 | { | |
34 | std::cout << "Usage: \n" | |
35 | << " cephfs-table-tool <all|[mds rank]> <reset|show> <session|snap|inode>" | |
36 | << " cephfs-table-tool <all|[mds rank]> <take_inos> <max_ino>" | |
37 | << std::endl; | |
38 | ||
39 | generic_client_usage(); | |
40 | } | |
41 | ||
42 | ||
43 | /** | |
44 | * For a function that takes an MDS role as an argument and | |
45 | * returns an error code, execute it on the roles specified | |
46 | * by `role_selector`. | |
47 | */ | |
48 | int TableTool::apply_role_fn(std::function<int(mds_role_t, Formatter *)> fptr, Formatter *f) | |
49 | { | |
11fdf7f2 | 50 | ceph_assert(f != NULL); |
7c673cae FG |
51 | |
52 | int r = 0; | |
53 | ||
54 | f->open_object_section("ranks"); | |
55 | ||
56 | for (auto role : role_selector.get_roles()) { | |
57 | std::ostringstream rank_str; | |
58 | rank_str << role.rank; | |
59 | f->open_object_section(rank_str.str().c_str()); | |
60 | ||
61 | f->open_object_section("data"); | |
62 | int rank_r = fptr(role, f); | |
63 | f->close_section(); | |
64 | r = r ? r : rank_r; | |
65 | ||
66 | f->dump_int("result", rank_r); | |
67 | f->close_section(); | |
68 | ||
69 | ||
70 | } | |
71 | ||
72 | f->close_section(); | |
73 | ||
74 | return r; | |
75 | } | |
76 | ||
77 | ||
78 | /** | |
79 | * This class wraps an MDS table class (SessionMap, SnapServer, InoTable) | |
80 | * with offline load/store code such that we can do offline dumps and resets | |
81 | * on those tables. | |
82 | */ | |
83 | template <typename A> | |
84 | class TableHandler | |
85 | { | |
86 | protected: | |
87 | // The RADOS object ID for the table | |
88 | std::string object_name; | |
89 | ||
90 | // The role in question (may be NONE) | |
91 | mds_role_t role; | |
92 | ||
93 | // Whether this is an MDSTable subclass (i.e. has leading version field to decode) | |
94 | bool mds_table; | |
95 | ||
96 | public: | |
97 | TableHandler(mds_role_t r, std::string const &name, bool mds_table_) | |
98 | : role(r), mds_table(mds_table_) | |
99 | { | |
100 | // Compose object name of the table we will dump | |
101 | std::ostringstream oss; | |
102 | oss << "mds"; | |
103 | if (!role.is_none()) { | |
104 | oss << role.rank; | |
105 | } | |
106 | oss << "_" << name; | |
107 | object_name = oss.str(); | |
108 | } | |
109 | ||
110 | int load_and_dump(librados::IoCtx *io, Formatter *f) | |
111 | { | |
11fdf7f2 TL |
112 | ceph_assert(io != NULL); |
113 | ceph_assert(f != NULL); | |
7c673cae FG |
114 | |
115 | // Attempt read | |
116 | bufferlist table_bl; | |
117 | int read_r = io->read(object_name, table_bl, 0, 0); | |
118 | if (read_r >= 0) { | |
11fdf7f2 | 119 | auto q = table_bl.cbegin(); |
7c673cae FG |
120 | try { |
121 | if (mds_table) { | |
122 | version_t version; | |
11fdf7f2 | 123 | decode(version, q); |
7c673cae FG |
124 | f->dump_int("version", version); |
125 | } | |
126 | A table_inst; | |
127 | table_inst.set_rank(role.rank); | |
128 | table_inst.decode(q); | |
129 | table_inst.dump(f); | |
130 | ||
131 | return 0; | |
132 | } catch (buffer::error &e) { | |
133 | derr << "table " << object_name << " is corrupt" << dendl; | |
134 | return -EIO; | |
135 | } | |
136 | } else { | |
137 | derr << "error reading table object " << object_name | |
138 | << ": " << cpp_strerror(read_r) << dendl; | |
139 | return read_r; | |
140 | } | |
141 | } | |
142 | ||
143 | int reset(librados::IoCtx *io) | |
144 | { | |
145 | A table_inst; | |
146 | // Compose new (blank) table | |
147 | table_inst.set_rank(role.rank); | |
148 | table_inst.reset_state(); | |
149 | // Write the table out | |
150 | return write(table_inst, io); | |
151 | } | |
152 | ||
153 | protected: | |
154 | ||
155 | int write(const A &table_inst, librados::IoCtx *io) | |
156 | { | |
157 | bufferlist new_bl; | |
158 | if (mds_table) { | |
159 | version_t version = 1; | |
11fdf7f2 | 160 | encode(version, new_bl); |
7c673cae FG |
161 | } |
162 | table_inst.encode_state(new_bl); | |
163 | ||
164 | // Write out new table | |
165 | int r = io->write_full(object_name, new_bl); | |
166 | if (r != 0) { | |
167 | derr << "error writing table object " << object_name | |
168 | << ": " << cpp_strerror(r) << dendl; | |
169 | return r; | |
170 | } | |
171 | ||
172 | return r; | |
173 | } | |
174 | }; | |
175 | ||
176 | template <typename A> | |
177 | class TableHandlerOmap | |
178 | { | |
179 | private: | |
180 | // The RADOS object ID for the table | |
181 | std::string object_name; | |
182 | ||
183 | // The role (rank may be NONE) | |
184 | mds_role_t role; | |
185 | ||
186 | // Whether this is an MDSTable subclass (i.e. has leading version field to decode) | |
187 | bool mds_table; | |
188 | ||
189 | public: | |
190 | TableHandlerOmap(mds_role_t r, std::string const &name, bool mds_table_) | |
191 | : role(r), mds_table(mds_table_) | |
192 | { | |
193 | // Compose object name of the table we will dump | |
194 | std::ostringstream oss; | |
195 | oss << "mds"; | |
196 | if (!role.is_none()) { | |
197 | oss << role.rank; | |
198 | } | |
199 | oss << "_" << name; | |
200 | object_name = oss.str(); | |
201 | } | |
202 | ||
203 | int load_and_dump(librados::IoCtx *io, Formatter *f) | |
204 | { | |
11fdf7f2 TL |
205 | ceph_assert(io != NULL); |
206 | ceph_assert(f != NULL); | |
7c673cae FG |
207 | |
208 | // Read in the header | |
209 | bufferlist header_bl; | |
210 | int r = io->omap_get_header(object_name, &header_bl); | |
211 | if (r != 0) { | |
212 | derr << "error reading header on '" << object_name << "': " | |
213 | << cpp_strerror(r) << dendl; | |
214 | return r; | |
215 | } | |
216 | ||
217 | // Decode the header | |
218 | A table_inst; | |
219 | table_inst.set_rank(role.rank); | |
220 | try { | |
221 | table_inst.decode_header(header_bl); | |
222 | } catch (buffer::error &e) { | |
223 | derr << "table " << object_name << " is corrupt" << dendl; | |
224 | return -EIO; | |
225 | } | |
226 | ||
227 | // Read and decode OMAP values in chunks | |
228 | std::string last_key = ""; | |
229 | while(true) { | |
230 | std::map<std::string, bufferlist> values; | |
231 | int r = io->omap_get_vals(object_name, last_key, | |
11fdf7f2 | 232 | g_conf()->mds_sessionmap_keys_per_op, &values); |
7c673cae FG |
233 | |
234 | if (r != 0) { | |
235 | derr << "error reading values: " << cpp_strerror(r) << dendl; | |
236 | return r; | |
237 | } | |
238 | ||
239 | if (values.empty()) { | |
240 | break; | |
241 | } | |
242 | ||
243 | try { | |
244 | table_inst.decode_values(values); | |
245 | } catch (buffer::error &e) { | |
246 | derr << "table " << object_name << " is corrupt" << dendl; | |
247 | return -EIO; | |
248 | } | |
249 | last_key = values.rbegin()->first; | |
250 | } | |
251 | ||
252 | table_inst.dump(f); | |
253 | ||
254 | return 0; | |
255 | } | |
256 | ||
257 | int reset(librados::IoCtx *io) | |
258 | { | |
259 | A table_inst; | |
260 | table_inst.set_rank(role.rank); | |
261 | table_inst.reset_state(); | |
262 | bufferlist header_bl; | |
263 | table_inst.encode_header(&header_bl); | |
264 | ||
265 | // Compose a transaction to clear and write header | |
266 | librados::ObjectWriteOperation op; | |
267 | op.omap_clear(); | |
268 | op.set_op_flags2(LIBRADOS_OP_FLAG_FAILOK); | |
269 | op.omap_set_header(header_bl); | |
270 | ||
271 | return io->operate(object_name, &op); | |
272 | } | |
273 | }; | |
274 | ||
275 | class InoTableHandler : public TableHandler<InoTable> | |
276 | { | |
277 | public: | |
278 | explicit InoTableHandler(mds_role_t r) | |
279 | : TableHandler(r, "inotable", true) | |
280 | {} | |
281 | ||
282 | int take_inos(librados::IoCtx *io, inodeno_t max, Formatter *f) | |
283 | { | |
284 | InoTable inst; | |
285 | inst.set_rank(role.rank); | |
286 | inst.reset_state(); | |
287 | ||
288 | int r = 0; | |
289 | if (inst.force_consume_to(max)) { | |
290 | r = write(inst, io); | |
291 | } | |
292 | ||
293 | f->dump_int("version", inst.get_version()); | |
294 | inst.dump(f); | |
295 | ||
296 | return r; | |
297 | } | |
298 | }; | |
299 | ||
300 | ||
301 | int TableTool::main(std::vector<const char*> &argv) | |
302 | { | |
303 | int r; | |
304 | ||
305 | dout(10) << __func__ << dendl; | |
306 | ||
307 | // RADOS init | |
308 | // ========== | |
309 | r = rados.init_with_context(g_ceph_context); | |
310 | if (r < 0) { | |
311 | derr << "RADOS unavailable, cannot scan filesystem journal" << dendl; | |
312 | return r; | |
313 | } | |
314 | ||
315 | dout(4) << "connecting to RADOS..." << dendl; | |
316 | r = rados.connect(); | |
317 | if (r < 0) { | |
318 | derr << "couldn't connect to cluster: " << cpp_strerror(r) << dendl; | |
319 | return r; | |
320 | } | |
321 | ||
322 | // Require at least 3 args <rank> <mode> <arg> [args...] | |
323 | if (argv.size() < 3) { | |
11fdf7f2 | 324 | cerr << "missing required 3 arguments" << std::endl; |
7c673cae FG |
325 | return -EINVAL; |
326 | } | |
327 | ||
328 | const std::string role_str = std::string(argv[0]); | |
329 | const std::string mode = std::string(argv[1]); | |
330 | const std::string table = std::string(argv[2]); | |
331 | ||
332 | r = role_selector.parse(*fsmap, role_str); | |
333 | if (r < 0) { | |
334 | derr << "Bad rank selection: " << role_str << "'" << dendl; | |
335 | return r; | |
336 | } | |
337 | ||
338 | auto fs = fsmap->get_filesystem(role_selector.get_ns()); | |
11fdf7f2 | 339 | ceph_assert(fs != nullptr); |
7c673cae FG |
340 | int64_t const pool_id = fs->mds_map.get_metadata_pool(); |
341 | dout(4) << "resolving pool " << pool_id << dendl; | |
342 | std::string pool_name; | |
343 | r = rados.pool_reverse_lookup(pool_id, &pool_name); | |
344 | if (r < 0) { | |
345 | derr << "Pool " << pool_id << " identified in MDS map not found in RADOS!" | |
346 | << dendl; | |
347 | return r; | |
348 | } | |
349 | ||
350 | dout(4) << "creating IoCtx.." << dendl; | |
351 | r = rados.ioctx_create(pool_name.c_str(), io); | |
352 | if (r != 0) { | |
353 | return r; | |
354 | } | |
355 | ||
356 | JSONFormatter jf(true); | |
357 | if (mode == "reset") { | |
358 | const std::string table = std::string(argv[2]); | |
359 | if (table == "session") { | |
360 | r = apply_role_fn([this](mds_role_t rank, Formatter *f) -> int { | |
361 | return TableHandlerOmap<SessionMapStore>(rank, "sessionmap", false).reset(&io); | |
362 | }, &jf); | |
363 | } else if (table == "inode") { | |
364 | r = apply_role_fn([this](mds_role_t rank, Formatter *f) -> int { | |
365 | return TableHandler<InoTable>(rank, "inotable", true).reset(&io); | |
366 | }, &jf); | |
367 | } else if (table == "snap") { | |
368 | r = TableHandler<SnapServer>(mds_role_t(), "snaptable", true).reset(&io); | |
369 | jf.open_object_section("reset_snap_status"); | |
370 | jf.dump_int("result", r); | |
371 | jf.close_section(); | |
372 | } else { | |
11fdf7f2 | 373 | cerr << "Invalid table '" << table << "'" << std::endl; |
7c673cae FG |
374 | return -EINVAL; |
375 | } | |
376 | } else if (mode == "show") { | |
377 | const std::string table = std::string(argv[2]); | |
378 | if (table == "session") { | |
379 | r = apply_role_fn([this](mds_role_t rank, Formatter *f) -> int { | |
380 | return TableHandlerOmap<SessionMapStore>(rank, "sessionmap", false).load_and_dump(&io, f); | |
381 | }, &jf); | |
382 | } else if (table == "inode") { | |
383 | r = apply_role_fn([this](mds_role_t rank, Formatter *f) -> int { | |
384 | return TableHandler<InoTable>(rank, "inotable", true).load_and_dump(&io, f);; | |
385 | }, &jf); | |
386 | } else if (table == "snap") { | |
387 | jf.open_object_section("show_snap_table"); | |
388 | { | |
389 | r = TableHandler<SnapServer>( | |
390 | mds_role_t(), "snaptable", true).load_and_dump(&io, &jf); | |
391 | jf.dump_int("result", r); | |
392 | } | |
393 | jf.close_section(); | |
394 | } else { | |
11fdf7f2 | 395 | cerr << "Invalid table '" << table << "'" << std::endl; |
7c673cae FG |
396 | return -EINVAL; |
397 | } | |
398 | } else if (mode == "take_inos") { | |
399 | const std::string ino_str = std::string(argv[2]); | |
400 | std::string ino_err; | |
401 | inodeno_t ino = strict_strtoll(ino_str.c_str(), 10, &ino_err); | |
402 | if (!ino_err.empty()) { | |
403 | derr << "Bad ino '" << ino_str << "'" << dendl; | |
404 | return -EINVAL; | |
405 | } | |
406 | r = apply_role_fn([this, ino](mds_role_t rank, Formatter *f) -> int { | |
407 | return InoTableHandler(rank).take_inos(&io, ino, f); | |
408 | }, &jf); | |
409 | } else { | |
11fdf7f2 | 410 | cerr << "Invalid mode '" << mode << "'" << std::endl; |
7c673cae FG |
411 | return -EINVAL; |
412 | } | |
413 | ||
414 | // Subcommand should have written to formatter, flush it | |
415 | jf.flush(std::cout); | |
416 | std::cout << std::endl; | |
417 | return r; | |
418 | } | |
419 |