Skip to content

Commit 7ea5560

Browse files
debug_bundle: Reloading metadata on restart
Added functionality that will reload metadata from the kvstore after the service restarts. Signed-off-by: Michael Boquard <michael@redpanda.com>
1 parent 3006986 commit 7ea5560

4 files changed

Lines changed: 222 additions & 5 deletions

File tree

‎src/v/debug_bundle/debug_bundle_service.cc‎

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ ss::future<bytes> calculate_sha256_sum(std::string_view path) {
131131

132132
co_return std::move(ctx).final();
133133
}
134+
135+
ss::future<bool>
136+
validate_sha256_checksum(std::string_view path, bytes_view checksum) {
137+
auto sum = co_await calculate_sha256_sum(path);
138+
co_return sum == checksum;
139+
}
134140
} // namespace
135141

136142
struct service::output_handler {
@@ -159,11 +165,19 @@ class service::debug_bundle_process {
159165
, _output_file_path(std::move(output_file_path))
160166
, _created_time(clock::now()) {
161167
_rpk_process->set_stdout_consumer(
162-
output_handler{.output_buffer = _cout});
168+
output_handler{.output_buffer = std::ref(_cout)});
163169
_rpk_process->set_stderr_consumer(
164-
output_handler{.output_buffer = _cerr});
170+
output_handler{.output_buffer = std::ref(_cerr)});
165171
}
166172

173+
explicit debug_bundle_process(metadata md)
174+
: _job_id(md.job_id)
175+
, _wait_result(md.get_wait_status())
176+
, _output_file_path(md.debug_bundle_file_path)
177+
, _cout(std::move(md.cout))
178+
, _cerr(std::move(md.cerr))
179+
, _created_time(md.get_created_at()) {}
180+
167181
debug_bundle_process() = delete;
168182
debug_bundle_process(debug_bundle_process&&) = default;
169183
debug_bundle_process& operator=(debug_bundle_process&&) = default;
@@ -272,12 +286,15 @@ ss::future<> service::start() {
272286
_rpk_path_binding().native());
273287
}
274288

289+
co_await maybe_reload_previous_run();
290+
275291
lg.debug("Service started");
276292
}
277293

278294
ss::future<> service::stop() {
279295
lg.debug("Service stopping");
280296
if (ss::this_shard_id() == service_shard) {
297+
auto units = co_await _process_control_mutex.get_units();
281298
if (is_running()) {
282299
try {
283300
co_await _rpk_process->terminate(1s);
@@ -290,6 +307,7 @@ ss::future<> service::stop() {
290307
}
291308
}
292309
co_await _gate.close();
310+
_rpk_process.reset(nullptr);
293311
}
294312

295313
ss::future<result<void>> service::initiate_rpk_debug_bundle_collection(
@@ -615,6 +633,10 @@ ss::future<> service::cleanup_previous_run() const {
615633
co_await ss::remove_file(debug_bundle_file.native());
616634
}
617635

636+
co_await remove_kvstore_entry();
637+
}
638+
639+
ss::future<> service::remove_kvstore_entry() const {
618640
co_await _kvstore->remove(
619641
storage::kvstore::key_space::debug_bundle,
620642
bytes::from_string(debug_bundle_metadata_key));
@@ -705,5 +727,56 @@ ss::future<> service::handle_wait_result(
705727
co_return;
706728
}
707729
}
730+
ss::future<> service::maybe_reload_previous_run() {
731+
auto md_buf = _kvstore->get(
732+
storage::kvstore::key_space::debug_bundle,
733+
bytes::from_string(debug_bundle_metadata_key));
734+
735+
if (!md_buf) {
736+
vlog(lg.debug, "No previous run detected");
737+
co_return;
738+
}
739+
740+
iobuf_parser p(std::move(*md_buf));
741+
auto md = serde::read<metadata>(p);
742+
743+
auto run_was_successful = [&md]() {
744+
auto wait_status = md.get_wait_status();
745+
return std::holds_alternative<ss::experimental::process::wait_exited>(
746+
wait_status)
747+
&& std::get<ss::experimental::process::wait_exited>(wait_status)
748+
.exit_code
749+
== 0;
750+
}();
751+
752+
if (
753+
run_was_successful
754+
&& !co_await ss::file_exists(md.debug_bundle_file_path)) {
755+
vlog(
756+
lg.debug,
757+
"Debug bundle file {} does not exist, cannot reload metadata",
758+
md.debug_bundle_file_path);
759+
co_return co_await remove_kvstore_entry();
760+
}
761+
762+
if (
763+
run_was_successful
764+
&& !co_await validate_sha256_checksum(
765+
md.debug_bundle_file_path, md.sha256_checksum)) {
766+
vlog(
767+
lg.debug,
768+
"Debug bundle file {} checksum mismatch",
769+
md.debug_bundle_file_path);
770+
co_await ss::remove_file(md.debug_bundle_file_path);
771+
co_return co_await remove_kvstore_entry();
772+
}
773+
774+
vlog(
775+
lg.info,
776+
"Detected a valid previous run that was {}successful",
777+
run_was_successful ? "" : "un");
778+
779+
_rpk_process = std::make_unique<debug_bundle_process>(std::move(md));
780+
}
708781

709782
} // namespace debug_bundle

‎src/v/debug_bundle/debug_bundle_service.h‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ class service final : public ss::peering_sharded_service<service> {
143143
*/
144144
ss::future<> cleanup_previous_run() const;
145145

146+
/**
147+
* @brief Removes debug bundle entry from kvstore
148+
*/
149+
ss::future<> remove_kvstore_entry() const;
150+
146151
/**
147152
* @brief Set the metadata object within the kvstore
148153
*
@@ -172,6 +177,15 @@ class service final : public ss::peering_sharded_service<service> {
172177
ss::future<> handle_wait_result(
173178
job_id_t job_id, ss::experimental::process::wait_status result);
174179

180+
/**
181+
* @brief Attempts to reload a previous run
182+
*
183+
* If a previous run exists in the kvstore upon service start, will attempt
184+
* to reload the metadata and make that debug bundle available to the user
185+
*
186+
*/
187+
ss::future<> maybe_reload_previous_run();
188+
175189
private:
176190
/// Handler used to emplace stdout/stderr into a buffer
177191
struct output_handler;

‎src/v/debug_bundle/tests/debug_bundle_service_test.cc‎

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ struct debug_bundle_service_fixture : public seastar_test {
8383
}
8484

8585
ss::future<debug_bundle::result<debug_bundle::debug_bundle_status_data>>
86-
wait_for_process_to_finish(const std::chrono::seconds timeout) {
86+
wait_for_process_to_finish(
87+
const std::chrono::seconds timeout = std::chrono::seconds{10}) {
8788
const auto start_time = debug_bundle::clock::now();
8889
while (debug_bundle::clock::now() - start_time <= timeout) {
8990
auto status = co_await _service.local().rpk_debug_bundle_status();
@@ -190,7 +191,7 @@ TEST_F_CORO(debug_bundle_service_started_fixture, run_process) {
190191
EXPECT_EQ(status.assume_value().job_id, job_id);
191192
EXPECT_EQ(status.assume_value().file_name, fmt::format("{}.zip", job_id));
192193

193-
ASSERT_NO_THROW_CORO(status = co_await wait_for_process_to_finish(10s));
194+
ASSERT_NO_THROW_CORO(status = co_await wait_for_process_to_finish());
194195

195196
ASSERT_TRUE_CORO(status.has_value()) << res.assume_error().message();
196197

@@ -512,7 +513,7 @@ TEST_F_CORO(
512513
auto term_res = co_await _service.local().cancel_rpk_debug_bundle(job_id);
513514
ASSERT_TRUE_CORO(term_res.has_value()) << term_res.assume_error().message();
514515

515-
ASSERT_NO_THROW_CORO(status = co_await wait_for_process_to_finish(10s));
516+
ASSERT_NO_THROW_CORO(status = co_await wait_for_process_to_finish());
516517

517518
ASSERT_TRUE_CORO(status.has_value()) << res.assume_error().message();
518519

@@ -725,3 +726,128 @@ TEST_F_CORO(debug_bundle_service_started_fixture, validate_metadata) {
725726
EXPECT_EQ(
726727
metadata.sha256_checksum, co_await calculate_sha256_sum(job_file));
727728
}
729+
730+
TEST_F_CORO(debug_bundle_service_started_fixture, validate_restart) {
731+
debug_bundle::job_id_t job_id(uuid_t::create());
732+
733+
co_await run_bundle(job_id);
734+
735+
auto status = co_await _service.local().rpk_debug_bundle_status();
736+
ASSERT_TRUE_CORO(status.has_value()) << status.assume_error().message();
737+
738+
co_await restart_service();
739+
740+
auto status_restart = co_await _service.local().rpk_debug_bundle_status();
741+
ASSERT_TRUE_CORO(status_restart.has_value())
742+
<< status_restart.assume_error().message();
743+
744+
EXPECT_EQ(status.assume_value(), status_restart.assume_value());
745+
}
746+
747+
TEST_F_CORO(
748+
debug_bundle_service_started_fixture, validate_restart_remove_file) {
749+
debug_bundle::job_id_t job_id(uuid_t::create());
750+
751+
co_await run_bundle(job_id);
752+
753+
auto file_path = co_await _service.local().rpk_debug_bundle_path(job_id);
754+
ASSERT_TRUE_CORO(file_path.has_value())
755+
<< file_path.assume_error().message();
756+
757+
// Remove the file and restart the service - the metadata should be removed
758+
co_await ss::remove_file(file_path.assume_value().native());
759+
760+
co_await restart_service();
761+
762+
auto status_restart = co_await _service.local().rpk_debug_bundle_status();
763+
ASSERT_FALSE_CORO(status_restart.has_value());
764+
EXPECT_EQ(
765+
status_restart.assume_error().code(),
766+
debug_bundle::error_code::debug_bundle_process_never_started);
767+
768+
EXPECT_FALSE(
769+
_kvstore
770+
->get(
771+
storage::kvstore::key_space::debug_bundle,
772+
bytes::from_string(debug_bundle::service::debug_bundle_metadata_key))
773+
.has_value());
774+
}
775+
776+
TEST_F_CORO(debug_bundle_service_started_fixture, validate_invalid_sha) {
777+
debug_bundle::job_id_t job_id(uuid_t::create());
778+
779+
co_await run_bundle(job_id);
780+
781+
ASSERT_NO_THROW_CORO(co_await wait_for_kvstore_to_populate(_kvstore.get()));
782+
783+
{
784+
auto metadata_buf = _kvstore->get(
785+
storage::kvstore::key_space::debug_bundle,
786+
bytes::from_string(debug_bundle::service::debug_bundle_metadata_key));
787+
ASSERT_TRUE_CORO(metadata_buf.has_value());
788+
iobuf_parser parser(std::move(metadata_buf.value()));
789+
auto metadata = serde::read<debug_bundle::metadata>(parser);
790+
metadata.sha256_checksum = bytes::from_string("invalid");
791+
iobuf buf;
792+
serde::write(buf, std::move(metadata));
793+
co_await _kvstore->put(
794+
storage::kvstore::key_space::debug_bundle,
795+
bytes::from_string(debug_bundle::service::debug_bundle_metadata_key),
796+
std::move(buf));
797+
}
798+
799+
co_await restart_service();
800+
801+
auto status_restart = co_await _service.local().rpk_debug_bundle_status();
802+
ASSERT_FALSE_CORO(status_restart.has_value());
803+
EXPECT_EQ(
804+
status_restart.assume_error().code(),
805+
debug_bundle::error_code::debug_bundle_process_never_started);
806+
807+
EXPECT_FALSE(
808+
_kvstore
809+
->get(
810+
storage::kvstore::key_space::debug_bundle,
811+
bytes::from_string(debug_bundle::service::debug_bundle_metadata_key))
812+
.has_value());
813+
}
814+
815+
TEST_F_CORO(debug_bundle_service_started_fixture, restart_unsuccessful_run) {
816+
debug_bundle::job_id_t job_id(uuid_t::create());
817+
818+
auto res = co_await _service.local().initiate_rpk_debug_bundle_collection(
819+
job_id, {});
820+
ASSERT_TRUE_CORO(res.has_value()) << res.assume_error().message();
821+
822+
auto status = co_await _service.local().rpk_debug_bundle_status();
823+
ASSERT_TRUE_CORO(status.has_value()) << res.assume_error().message();
824+
825+
EXPECT_EQ(
826+
status.assume_value().status, debug_bundle::debug_bundle_status::running);
827+
828+
auto term_res = co_await _service.local().cancel_rpk_debug_bundle(job_id);
829+
ASSERT_TRUE_CORO(term_res.has_value()) << term_res.assume_error().message();
830+
831+
ASSERT_NO_THROW_CORO(status = co_await wait_for_process_to_finish());
832+
833+
ASSERT_TRUE_CORO(status.has_value()) << status.assume_error().message();
834+
835+
EXPECT_EQ(
836+
status.assume_value().status, debug_bundle::debug_bundle_status::error);
837+
838+
co_await restart_service();
839+
840+
auto status_restart = co_await _service.local().rpk_debug_bundle_status();
841+
ASSERT_TRUE_CORO(status_restart.has_value())
842+
<< status_restart.assume_error().message();
843+
EXPECT_EQ(
844+
status_restart.assume_value().status,
845+
debug_bundle::debug_bundle_status::error);
846+
847+
EXPECT_TRUE(
848+
_kvstore
849+
->get(
850+
storage::kvstore::key_space::debug_bundle,
851+
bytes::from_string(debug_bundle::service::debug_bundle_metadata_key))
852+
.has_value());
853+
}

‎src/v/debug_bundle/types.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ struct debug_bundle_status_data {
129129
ss::sstring file_name;
130130
chunked_vector<ss::sstring> cout;
131131
chunked_vector<ss::sstring> cerr;
132+
133+
friend bool
134+
operator==(const debug_bundle_status_data&, const debug_bundle_status_data&)
135+
= default;
132136
};
133137
} // namespace debug_bundle
134138

0 commit comments

Comments
 (0)