1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab ft=cpp
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2020 Red Hat, Inc.
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.
18 #include <type_traits>
19 #include <boost/type_traits/has_equal_to.hpp>
20 #include <boost/type_traits/has_left_shift.hpp>
22 #include "include/ceph_assert.h"
23 #include "common/ceph_time.h"
24 #include "common/dout.h"
28 /// A failure type that aborts the process with a failed assertion.
29 struct InjectAbort
{};
31 /// A failure type that injects an error code and optionally logs a message.
33 /// error code to inject
35 /// an optional log channel to print an error message
36 const DoutPrefixProvider
* dpp
= nullptr;
39 /// Injects a delay before returning success.
41 /// duration of the delay
42 ceph::timespan duration
;
43 /// an optional log channel to print a message
44 const DoutPrefixProvider
* dpp
= nullptr;
47 /** @class FaultInjector
48 * @brief Used to instrument a code path with deterministic fault injection
49 * by making one or more calls to check().
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.
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
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.
65 template <typename Key
>
68 /// Default-construct with no injected failure.
69 constexpr FaultInjector() noexcept
: location() {}
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
) {}
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
) {}
79 /// Construct with an injected delay at the given location.
80 constexpr FaultInjector(Key location
, InjectDelay d
)
81 : location(std::move(location
)), failure(d
) {}
83 /// Inject an assertion failure at the given location.
84 void inject(Key location
, InjectAbort a
) {
85 this->location
= std::move(location
);
89 /// Inject an error at the given location.
90 void inject(Key location
, InjectError e
) {
91 this->location
= std::move(location
);
95 /// Injecte a delay at the given location.
96 void inject(Key location
, InjectDelay d
) {
97 this->location
= std::move(location
);
101 /// Clear any injected failure.
103 this->failure
= Empty
{};
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
109 /// @returns 0 or InjectError::error if the location matches an InjectError
111 [[nodiscard
]] constexpr int check(const Key
& location
) const {
113 const Key
& check_location
;
114 const Key
& this_location
;
115 constexpr int operator()(const std::monostate
&) const {
118 int operator()(const InjectAbort
&) const {
119 if (check_location
== this_location
) {
120 ceph_assert_always(!"FaultInjector");
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
;
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
);
141 return std::visit(visitor
{location
, this->location
}, failure
);
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<<");
157 Key location
; // location of the check that should fail
159 using Empty
= std::monostate
; // empty state for std::variant
161 std::variant
<Empty
, InjectAbort
, InjectError
, InjectDelay
> failure
;