summaryrefslogtreecommitdiffstats
path: root/src/mds/ScrubStack.h
blob: 3586daf236b202d51ccf1ee472002f0a471d14e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
/*
 * Ceph - scalable distributed file system
 *
 * Copyright (C) 2014 Red Hat
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1, as published by the Free Software
 * Foundation.  See file COPYING.
 *
 */

#ifndef SCRUBSTACK_H_
#define SCRUBSTACK_H_

#include "CDir.h"
#include "CDentry.h"
#include "CInode.h"
#include "MDSContext.h"
#include "ScrubHeader.h"

#include "common/LogClient.h"
#include "include/elist.h"

class MDCache;
class Finisher;

class ScrubStack {
protected:
  // reference to global cluster log client
  LogChannelRef &clog;

  /// A finisher needed so that we don't re-enter kick_off_scrubs
  Finisher *finisher;

  /// The stack of inodes we want to scrub
  elist<CInode*> inode_stack;
  /// current number of dentries we're actually scrubbing
  int scrubs_in_progress;
  ScrubStack *scrubstack; // hack for dout
  int stack_size;

  class C_KickOffScrubs : public MDSInternalContext {
    ScrubStack *stack;
  public:
    C_KickOffScrubs(MDCache *mdcache, ScrubStack *s);
    void finish(int r) override { }
    void complete(int r) override {
      if (r == -ECANCELED) {
        return;
      }

      stack->scrubs_in_progress--;
      stack->kick_off_scrubs();
      // don't delete self
    }
  };
  C_KickOffScrubs scrub_kick;

public:
  MDCache *mdcache;
  ScrubStack(MDCache *mdc, LogChannelRef &clog, Finisher *finisher_) :
    clog(clog),
    finisher(finisher_),
    inode_stack(member_offset(CInode, item_scrub)),
    scrubs_in_progress(0),
    scrubstack(this),
    stack_size(0),
    scrub_kick(mdc, this),
    mdcache(mdc) {}
  ~ScrubStack() {
    ceph_assert(inode_stack.empty());
    ceph_assert(!scrubs_in_progress);
  }
  /**
   * Put a inode on the top of the scrub stack, so it is the highest priority.
   * If there are other scrubs in progress, they will not continue scrubbing new
   * entries until this one is completed.
   * @param in The inodey to scrub
   * @param header The ScrubHeader propagated from wherever this scrub
   *               was initiated
   */
  void enqueue_inode_top(CInode *in, ScrubHeaderRef& header,
			 MDSContext *on_finish) {
    enqueue_inode(in, header, on_finish, true);
    scrub_origins.emplace(in);
    clog_scrub_summary(in);
  }
  /** Like enqueue_inode_top, but we wait for all pending scrubs before
   * starting this one.
   */
  void enqueue_inode_bottom(CInode *in, ScrubHeaderRef& header,
			    MDSContext *on_finish) {
    enqueue_inode(in, header, on_finish, false);
    scrub_origins.emplace(in);
    clog_scrub_summary(in);
  }

  /**
   * Abort an ongoing scrub operation. The abort operation could be
   * delayed if there are in-progress scrub operations on going. The
   * caller should provide a context which is completed after all
   * in-progress scrub operations are completed and pending inodes
   * are removed from the scrub stack (with the context callbacks for
   * inodes completed with -ECANCELED).
   * @param on_finish Context callback to invoke after abort
   */
  void scrub_abort(Context *on_finish);

  /**
   * Pause scrub operations. Similar to abort, pause is delayed if
   * there are in-progress scrub operations on going. The caller
   * should provide a context which is completed after all in-progress
   * scrub operations are completed. Subsequent scrub operations are
   * queued until scrub is resumed.
   * @param on_finish Context callback to invoke after pause
   */
  void scrub_pause(Context *on_finish);

  /**
   * Resume a paused scrub. Unlike abort or pause, this is instantaneous.
   * Pending pause operations are cancelled (context callbacks are
   * invoked with -ECANCELED).
   * @returns 0 (success) if resumed, -EINVAL if an abort is in-progress.
   */
  bool scrub_resume();

  /**
   * Get the current scrub status as human readable string. Some basic
   * information is returned such as number of inodes pending abort/pause.
   */
  void scrub_status(Formatter *f);

  bool is_scrubbing() const { return !inode_stack.empty(); }

  /**
   * Get a high level scrub status summary such as current scrub state
   * and scrub paths.
   */
  std::string_view scrub_summary();

private:
  // scrub abort is _not_ a state, rather it's an operation that's
  // performed after in-progress scrubs are finished.
  enum State {
    STATE_RUNNING = 0,
    STATE_IDLE,
    STATE_PAUSING,
    STATE_PAUSED,
  };
  friend std::ostream &operator<<(std::ostream &os, const State &state);

  State state = STATE_IDLE;
  bool clear_inode_stack = false;

  // list of pending context completions for asynchronous scrub
  // control operations.
  std::list<Context *> control_ctxs;

  // list of inodes for which scrub operations are running -- used
  // to diplay out in `scrub status`.
  std::set<CInode *> scrub_origins;

  /**
   * Put the inode at either the top or bottom of the stack, with
   * the given scrub params, and then try and kick off more scrubbing.
   */
  void enqueue_inode(CInode *in, ScrubHeaderRef& header,
                      MDSContext *on_finish, bool top);
  void _enqueue_inode(CInode *in, CDentry *parent, ScrubHeaderRef& header,
                      MDSContext *on_finish, bool top);
  /**
   * Kick off as many scrubs as are appropriate, based on the current
   * state of the stack.
   */
  void kick_off_scrubs();
  /**
   * Push a inode on top of the stack.
   */
  inline void push_inode(CInode *in);
  /**
   * Push a inode to the bottom of the stack.
   */
  inline void push_inode_bottom(CInode *in);
  /**
   * Pop the given inode off the stack.
   */
  inline void pop_inode(CInode *in);

  /**
   * Scrub a file inode.
   * @param in The inode to scrub
   */
  void scrub_file_inode(CInode *in);

  /**
   * Callback from completion of CInode::validate_disk_state
   * @param in The inode we were validating
   * @param r The return status from validate_disk_state
   * @param result Populated results from validate_disk_state
   */
  void _validate_inode_done(CInode *in, int r,
			    const CInode::validated_data &result);
  friend class C_InodeValidated;

  /**
   * Make progress on scrubbing a directory-representing dirfrag and
   * its children..
   *
   * 1) Select the next dirfrag which hasn't been scrubbed, and make progress
   * on it if possible.
   *
   * 2) If not, move on to the next dirfrag and start it up, if any.
   *
   * 3) If waiting for results from dirfrag scrubs, do nothing.
   *
   * 4) If all dirfrags have been scrubbed, scrub my inode.
   *
   * @param in The CInode to scrub as a directory
   * @param added_children set to true if we pushed some of our children
   * onto the ScrubStack
   * @param is_terminal set to true if there are no descendant dentries
   * remaining to start scrubbing.
   * @param done set to true if we and all our children have finished scrubbing
   */
  void scrub_dir_inode(CInode *in, bool *added_children, bool *is_terminal,
                       bool *done);
  /**
   * Make progress on scrubbing a dirfrag. It may return after each of the
   * following steps, but will report making progress on each one.
   *
   * 1) enqueues the next unscrubbed child directory dentry at the
   * top of the stack.
   *
   * 2) Initiates a scrub on the next unscrubbed file dentry
   *
   * If there are scrubs currently in progress on child dentries, no more child
   * dentries to scrub, and this function is invoked, it will report no
   * progress. Try again later.
   *
   */
  void scrub_dirfrag(CDir *dir, ScrubHeaderRef& header,
		     bool *added_children, bool *is_terminal, bool *done);
  /**
   * Scrub a directory-representing dentry.
   *
   * @param in The directory inode we're doing final scrub on.
   */
  void scrub_dir_inode_final(CInode *in);

  /**
   * Get a CDir into memory, and return it if it's already complete.
   * Otherwise, fetch it and kick off scrubbing when done.
   *
   * @param in The Inode to get the next directory from
   * @param new_dir The CDir we're returning to you. NULL if
   * not ready yet or there aren't any.
   * @returns false if you have to wait, true if there's no work
   * left to do (we returned it, or there are none left in this inode).
   */
  bool get_next_cdir(CInode *in, CDir **new_dir);

  /**
   * Set scrub state
   * @param next_state State to move the scrub to.
   */
  void set_state(State next_state);

  /**
   * Is scrub in one of transition states (running, pausing)
   */
  bool scrub_in_transition_state();

  /**
   * complete queued up contexts
   * @param r return value to complete contexts.
   */
  void complete_control_contexts(int r);

  /**
   * Abort pending scrubs for inodes waiting in the inode stack.
   * Completion context is complete with -ECANCELED.
   */
  void abort_pending_scrubs();

  /**
   * Return path for a given inode.
   * @param in inode to make path entry.
   */
  std::string scrub_inode_path(CInode *in) {
    std::string path;
    in->make_path_string(path, true);
    return (path.empty() ? "/" : path.c_str());
  }

  /**
   * Send scrub information (queued/finished scrub path and summary)
   * to cluster log.
   * @param in inode for which scrub has been queued or finished.
   */
  void clog_scrub_summary(CInode *in=nullptr);
};

#endif /* SCRUBSTACK_H_ */