]> git.proxmox.com Git - ceph.git/blob - ceph/src/common/fault_injector.h
update ceph source to reef 18.2.0
[ceph.git] / ceph / src / common / fault_injector.h
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab ft=cpp
3 /*
4 * Ceph - scalable distributed file system
5 *
6 * Copyright (C) 2020 Red Hat, Inc.
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 #pragma once
16
17 #include <thread>
18 #include <type_traits>
19 #include <boost/type_traits/has_equal_to.hpp>
20 #include <boost/type_traits/has_left_shift.hpp>
21 #include <variant>
22 #include "include/ceph_assert.h"
23 #include "common/ceph_time.h"
24 #include "common/dout.h"
25
26 /// @file
27
28 /// A failure type that aborts the process with a failed assertion.
29 struct InjectAbort {};
30
31 /// A failure type that injects an error code and optionally logs a message.
32 struct InjectError {
33 /// error code to inject
34 int error;
35 /// an optional log channel to print an error message
36 const DoutPrefixProvider* dpp = nullptr;
37 };
38
39 /// Injects a delay before returning success.
40 struct InjectDelay {
41 /// duration of the delay
42 ceph::timespan duration;
43 /// an optional log channel to print a message
44 const DoutPrefixProvider* dpp = nullptr;
45 };
46
47 /** @class FaultInjector
48 * @brief Used to instrument a code path with deterministic fault injection
49 * by making one or more calls to check().
50 *
51 * A default-constructed FaultInjector contains no failure. It can also be
52 * constructed with a failure type and a location to inject that failure.
53 *
54 * The contained failure can be overwritten with a call to inject() or clear().
55 * This is not thread-safe with respect to other member functions on the same
56 * instance.
57 *
58 * @tparam Key The location can be represented by any Key type that is
59 * movable, default-constructible, inequality-comparable and stream-outputable.
60 * A string or string_view Key may be preferable when the location comes from
61 * user input, or to describe the steps like "before-foo" and "after-foo".
62 * An integer Key may be preferable for a code path with many steps, where you
63 * just want to check 1, 2, 3, etc. without inventing names for each.
64 */
65 template <typename Key>
66 class FaultInjector {
67 public:
68 /// Default-construct with no injected failure.
69 constexpr FaultInjector() noexcept : location() {}
70
71 /// Construct with an injected assertion failure at the given location.
72 constexpr FaultInjector(Key location, InjectAbort a)
73 : location(std::move(location)), failure(a) {}
74
75 /// Construct with an injected error code at the given location.
76 constexpr FaultInjector(Key location, InjectError e)
77 : location(std::move(location)), failure(e) {}
78
79 /// Construct with an injected delay at the given location.
80 constexpr FaultInjector(Key location, InjectDelay d)
81 : location(std::move(location)), failure(d) {}
82
83 /// Inject an assertion failure at the given location.
84 void inject(Key location, InjectAbort a) {
85 this->location = std::move(location);
86 this->failure = a;
87 }
88
89 /// Inject an error at the given location.
90 void inject(Key location, InjectError e) {
91 this->location = std::move(location);
92 this->failure = e;
93 }
94
95 /// Injecte a delay at the given location.
96 void inject(Key location, InjectDelay d) {
97 this->location = std::move(location);
98 this->failure = d;
99 }
100
101 /// Clear any injected failure.
102 void clear() {
103 this->failure = Empty{};
104 }
105
106 /// Check for an injected failure at the given location. If the location
107 /// matches an InjectAbort failure, the process aborts here with an assertion
108 /// failure.
109 /// @returns 0 or InjectError::error if the location matches an InjectError
110 /// failure
111 [[nodiscard]] constexpr int check(const Key& location) const {
112 struct visitor {
113 const Key& check_location;
114 const Key& this_location;
115 constexpr int operator()(const std::monostate&) const {
116 return 0;
117 }
118 int operator()(const InjectAbort&) const {
119 if (check_location == this_location) {
120 ceph_assert_always(!"FaultInjector");
121 }
122 return 0;
123 }
124 int operator()(const InjectError& e) const {
125 if (check_location == this_location) {
126 ldpp_dout(e.dpp, -1) << "Injecting error=" << e.error
127 << " at location=" << this_location << dendl;
128 return e.error;
129 }
130 return 0;
131 }
132 int operator()(const InjectDelay& e) const {
133 if (check_location == this_location) {
134 ldpp_dout(e.dpp, -1) << "Injecting delay=" << e.duration
135 << " at location=" << this_location << dendl;
136 std::this_thread::sleep_for(e.duration);
137 }
138 return 0;
139 }
140 };
141 return std::visit(visitor{location, this->location}, failure);
142 }
143
144 private:
145 // Key requirements:
146 static_assert(std::is_default_constructible_v<Key>,
147 "Key must be default-constrible");
148 static_assert(std::is_move_constructible_v<Key>,
149 "Key must be move-constructible");
150 static_assert(std::is_move_assignable_v<Key>,
151 "Key must be move-assignable");
152 static_assert(boost::has_equal_to<Key, Key, bool>::value,
153 "Key must be equality-comparable");
154 static_assert(boost::has_left_shift<std::ostream, Key, std::ostream&>::value,
155 "Key must have an ostream operator<<");
156
157 Key location; // location of the check that should fail
158
159 using Empty = std::monostate; // empty state for std::variant
160
161 std::variant<Empty, InjectAbort, InjectError, InjectDelay> failure;
162 };