blob: 79309b0cde43ebd9d5fa27d2fc197bc2cf9968d0 [file] [log] [blame]
James Kuszmaul827bd212023-05-15 23:57:39 -07001#include "aos/util/config_validator_lib.h"
2
Stephan Pleinesb1177672024-05-27 17:48:32 -07003#include <algorithm>
James Kuszmaul827bd212023-05-15 23:57:39 -07004#include <chrono>
Stephan Pleinesb1177672024-05-27 17:48:32 -07005#include <cstdlib>
6#include <initializer_list>
7#include <map>
8#include <memory>
9#include <ostream>
10#include <set>
11#include <string>
12#include <string_view>
13#include <utility>
14#include <vector>
James Kuszmaul827bd212023-05-15 23:57:39 -070015
Austin Schuh99f7c6a2024-06-25 22:07:44 -070016#include "absl/flags/declare.h"
17#include "absl/log/check.h"
18#include "absl/log/log.h"
Stephan Pleinesb1177672024-05-27 17:48:32 -070019#include "flatbuffers/buffer.h"
20#include "flatbuffers/detached_buffer.h"
21#include "flatbuffers/string.h"
22#include "flatbuffers/vector.h"
Stephan Pleinesb1177672024-05-27 17:48:32 -070023#include "gtest/gtest.h"
24
25#include "aos/events/event_loop.h"
James Kuszmaul827bd212023-05-15 23:57:39 -070026#include "aos/events/logging/log_reader.h"
Stephan Pleinesb1177672024-05-27 17:48:32 -070027#include "aos/events/logging/logfile_sorting.h"
28#include "aos/events/logging/logfile_utils.h"
James Kuszmaul827bd212023-05-15 23:57:39 -070029#include "aos/events/simulated_event_loop.h"
Stephan Pleinesb1177672024-05-27 17:48:32 -070030#include "aos/flatbuffers/builder.h"
31#include "aos/flatbuffers/static_vector.h"
32#include "aos/json_to_flatbuffer.h"
James Kuszmaul827bd212023-05-15 23:57:39 -070033#include "aos/network/remote_message_generated.h"
34#include "aos/network/timestamp_channel.h"
35#include "aos/testing/tmpdir.h"
James Kuszmaul464012b2024-03-20 14:12:08 -070036#include "aos/util/config_validator_config_static.h"
Stephan Pleinesb1177672024-05-27 17:48:32 -070037#include "aos/util/file.h"
James Kuszmaul827bd212023-05-15 23:57:39 -070038#include "aos/util/simulation_logger.h"
39
Austin Schuh99f7c6a2024-06-25 22:07:44 -070040ABSL_DECLARE_FLAG(bool, validate_timestamp_logger_nodes);
James Kuszmaul827bd212023-05-15 23:57:39 -070041
42namespace aos::util {
43
44namespace {
45void RunSimulationAndExit(const aos::Configuration *config) {
46 aos::SimulatedEventLoopFactory factory(config);
47
48 factory.RunFor(std::chrono::seconds(1));
49
50 std::exit(EXIT_SUCCESS);
51}
52
53// Checks if either the node is in the specified list of node names or if the
54// list is empty (in which case it is treated as matching all nodes).
55bool NodeInList(
56 const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *list,
57 const aos::Node *node) {
58 if (list == nullptr || list->size() == 0) {
59 return true;
60 }
61 for (const flatbuffers::String *name : *list) {
62 if (name->string_view() == node->name()->string_view()) {
63 return true;
64 }
65 }
66 return false;
67}
68
69} // namespace
70
71void ConfigIsValid(const aos::Configuration *config,
James Kuszmaul464012b2024-03-20 14:12:08 -070072 const ConfigValidatorConfig *validation_config_raw) {
James Kuszmaul827bd212023-05-15 23:57:39 -070073 ASSERT_TRUE(config->has_channels())
74 << "An AOS config must have channels. If you have a valid use-case for "
75 "channels with no channels, please write a design proposal.";
76
James Kuszmaul464012b2024-03-20 14:12:08 -070077 aos::fbs::Builder<ConfigValidatorConfigStatic> validation_config;
78 CHECK(validation_config->FromFlatbuffer(validation_config_raw));
79
80 if (validation_config_raw->has_logging() &&
81 validation_config_raw->logging()->validate_individual_node_loggers() &&
82 configuration::MultiNode(config)) {
83 if (!validation_config->logging()->has_logger_sets()) {
84 validation_config->mutable_logging()->add_logger_sets();
85 }
86 auto logger_sets =
87 validation_config->mutable_logging()->mutable_logger_sets();
88 for (const aos::Node *node : configuration::GetNodes(config)) {
89 CHECK(logger_sets->reserve(logger_sets->size() + 1));
90 auto logger_set = logger_sets->emplace_back();
91 CHECK(logger_set->add_loggers()->FromFlatbuffer({node->name()->str()}));
92 CHECK(logger_set->add_replay_nodes()->FromFlatbuffer(
93 {node->name()->str()}));
94 }
95 }
96
James Kuszmaul827bd212023-05-15 23:57:39 -070097 // First, we do some sanity checks--these are likely to indicate a malformed
98 // config, and so catching them early with a clear error message is likely to
99 // help.
100
101 // The set of all channels that are required by the channels that are
102 // configured--these are the remote timestamp channels that *must* be present,
103 // and ideally there are no other channels present.
104 std::set<const Channel *> required_timestamp_channels;
105 // The set of all channels that *look* like remote timestamp channels. This
106 // may include channels that are improperly configured and thus have typos &
107 // aren't actually going to do anything at runtime.
108 std::set<const Channel *> configured_timestamp_channels;
109 bool validation_failed = false;
110 for (size_t channel_index = 0; channel_index < config->channels()->size();
111 ++channel_index) {
112 const aos::Channel *channel = config->channels()->Get(channel_index);
113 ASSERT_TRUE(channel->has_name()) << "All AOS channels must have a name.";
114 ASSERT_TRUE(channel->has_type()) << "All AOS channels must have a type.";
115
116 const bool channel_looks_like_remote_message_channel =
117 channel->type()->string_view() ==
118 message_bridge::RemoteMessage::GetFullyQualifiedName();
119
120 const bool check_for_not_logged_channels =
121 !validation_config->has_logging() ||
James Kuszmaul464012b2024-03-20 14:12:08 -0700122 validation_config->AsFlatbuffer().logging()->all_channels_logged();
James Kuszmaul827bd212023-05-15 23:57:39 -0700123 const bool channel_is_not_logged =
124 channel->logger() == aos::LoggerConfig::NOT_LOGGED;
125 if (check_for_not_logged_channels) {
126 if (channel_looks_like_remote_message_channel != channel_is_not_logged) {
127 LOG(WARNING)
128 << "Channel " << configuration::StrippedChannelToString(channel)
129 << " is " << EnumNameLoggerConfig(channel->logger()) << " but "
130 << (channel_looks_like_remote_message_channel ? "is" : "is not")
131 << " a remote timestamp channel. This is almost certainly wrong.";
132 validation_failed = true;
133 }
134 }
135
136 if (channel_looks_like_remote_message_channel) {
137 configured_timestamp_channels.insert(channel);
138 } else {
139 if (channel->has_destination_nodes()) {
140 // TODO(james): Technically the timestamp finder should receive a
141 // non-empty application name. However, there are no known users that
142 // care at this moment.
143 message_bridge::ChannelTimestampFinder timestamp_finder(
144 config, "",
145 configuration::GetNode(config,
146 channel->source_node()->string_view()));
147 for (const Connection *connection : *channel->destination_nodes()) {
148 switch (connection->timestamp_logger()) {
149 case LoggerConfig::NOT_LOGGED:
150 case LoggerConfig::LOCAL_LOGGER:
151 if (connection->has_timestamp_logger_nodes()) {
152 LOG(WARNING)
153 << "Connections that are "
154 << EnumNameLoggerConfig(connection->timestamp_logger())
155 << " should not have remote timestamp logger nodes "
156 "populated. This is for the connection to "
157 << connection->name()->string_view() << " on "
158 << configuration::StrippedChannelToString(channel);
159 validation_failed = true;
160 }
161 break;
162 case LoggerConfig::REMOTE_LOGGER:
163 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
164 if (!connection->has_timestamp_logger_nodes() ||
165 connection->timestamp_logger_nodes()->size() != 1 ||
166 connection->timestamp_logger_nodes()->Get(0)->string_view() !=
167 channel->source_node()->string_view()) {
168 LOG(WARNING)
169 << "Connections that are "
170 << EnumNameLoggerConfig(connection->timestamp_logger())
171 << " should have exactly 1 remote timestamp logger node "
172 "populated, and that node should be the source_node ("
173 << channel->source_node()->string_view()
174 << "). This is for the connection to "
175 << connection->name()->string_view() << " on "
176 << configuration::StrippedChannelToString(channel);
177 validation_failed = true;
178 }
179 // TODO(james): This will be overly noisy, as it ends up
180 // CHECK-failing.
Austin Schuh6bdcc372024-06-27 14:49:11 -0700181 const Channel *found_channel =
182 timestamp_finder.ForChannel(channel, connection);
183 CHECK(found_channel != nullptr);
184 required_timestamp_channels.insert(found_channel);
James Kuszmaul827bd212023-05-15 23:57:39 -0700185 break;
186 }
187 }
188 }
189 }
190 }
191
192 // Check that all of the things that look like timestamp channels are indeed
193 // required.
194 // Note: Because ForChannel() will die if a required channel is not present,
195 // we do not do a separate check that all the required channels exist.
196 for (const auto &channel : configured_timestamp_channels) {
197 if (required_timestamp_channels.count(channel) == 0) {
198 LOG(WARNING) << "Timestamp channel "
199 << configuration::StrippedChannelToString(channel)
200 << " was specified in the config but is not used.";
201 validation_failed = true;
202 }
203 }
204
205 if (validation_failed) {
206 FAIL() << "Remote timestamp linting failed.";
207 return;
208 }
209
210 // Because the most common way for simulation to fail involves it dying, force
211 // it to fail in a slightly more controlled manner.
212 ASSERT_EXIT(RunSimulationAndExit(config),
213 ::testing::ExitedWithCode(EXIT_SUCCESS), "");
214
215 if (!validation_config->has_logging() || !configuration::MultiNode(config)) {
216 return;
217 }
218
219 // We will run all the logger configs in two modes:
220 // 1) We don't send any data on any non-infrastructure channels; this confirms
221 // that the logs are readable in the absence of any user applications being
222 // present.
223 // 2) We confirm that we can generate a good logfile that actually has data
224 // on every channel (some checks in the LogReader may not get hit if there
225 // is no data on a given channel).
226 const std::string log_path = aos::testing::TestTmpDir() + "/logs/";
227 for (const bool send_data_on_channels : {false, true}) {
228 SCOPED_TRACE(send_data_on_channels);
Pallavi Madhukaraaba67e2023-09-08 14:20:00 -0700229 // Single nodes (multi-nodes with node count = 1) will not produce readable
230 // logs in the absense of data.
231 if (!send_data_on_channels && (configuration::NodesCount(config) == 1u)) {
232 continue;
233 }
Pallavi Madhukar3076d5c2023-09-09 10:23:26 -0700234 // Send timing report when we are sending data.
235 const bool do_skip_timing_report = !send_data_on_channels;
James Kuszmaul464012b2024-03-20 14:12:08 -0700236 for (const LoggerNodeSetValidationStatic &logger_set :
James Kuszmaul827bd212023-05-15 23:57:39 -0700237 *validation_config->logging()->logger_sets()) {
James Kuszmaul464012b2024-03-20 14:12:08 -0700238 SCOPED_TRACE(aos::FlatbufferToJson(&logger_set.AsFlatbuffer()));
James Kuszmaul827bd212023-05-15 23:57:39 -0700239 aos::SimulatedEventLoopFactory factory(config);
240 std::vector<std::unique_ptr<LoggerState>> loggers;
James Kuszmaul464012b2024-03-20 14:12:08 -0700241 if (logger_set.has_loggers() && logger_set.loggers()->size() > 0) {
James Kuszmaul827bd212023-05-15 23:57:39 -0700242 std::vector<std::string> logger_nodes;
James Kuszmaul464012b2024-03-20 14:12:08 -0700243 for (const auto &node : *logger_set.loggers()) {
244 logger_nodes.push_back(node.str());
James Kuszmaul827bd212023-05-15 23:57:39 -0700245 }
Pallavi Madhukar3076d5c2023-09-09 10:23:26 -0700246 loggers = MakeLoggersForNodes(&factory, logger_nodes, log_path,
247 do_skip_timing_report);
James Kuszmaul827bd212023-05-15 23:57:39 -0700248 } else {
Pallavi Madhukar3076d5c2023-09-09 10:23:26 -0700249 loggers =
250 MakeLoggersForAllNodes(&factory, log_path, do_skip_timing_report);
James Kuszmaul827bd212023-05-15 23:57:39 -0700251 }
252
253 std::vector<std::unique_ptr<EventLoop>> test_loops;
254 std::map<std::string, std::vector<std::unique_ptr<RawSender>>>
255 test_senders;
256
257 if (send_data_on_channels) {
258 // Make a sender on every non-infrastructure channel on every node
259 // (including channels that may not be observable by the current logger
260 // set).
261 for (const aos::Node *node : configuration::GetNodes(config)) {
262 test_loops.emplace_back(factory.MakeEventLoop("", node));
263 for (const aos::Channel *channel : *config->channels()) {
264 // TODO(james): Make a more sophisticated check for "infrastructure"
265 // channels than just looking for a "/aos" in the channel--we don't
266 // accidentally want to spam nonsense data onto any timestamp
267 // channels, though.
268 if (configuration::ChannelIsSendableOnNode(channel, node) &&
269 channel->name()->str().find("/aos") == std::string::npos &&
270 channel->logger() != LoggerConfig::NOT_LOGGED) {
271 test_senders[node->name()->str()].emplace_back(
272 test_loops.back()->MakeRawSender(channel));
273 RawSender *sender =
274 test_senders[node->name()->str()].back().get();
275 test_loops.back()->OnRun([sender, channel]() {
276 flatbuffers::DetachedBuffer buffer =
277 JsonToFlatbuffer("{}", channel->schema());
278 sender->CheckOk(sender->Send(buffer.data(), buffer.size()));
279 });
280 }
281 }
282 }
283 }
284
285 factory.RunFor(std::chrono::seconds(2));
286
287 // Get all of the loggers to close before trying to read the logfiles.
288 loggers.clear();
289
290 // Confirm that we can read the log, and that if we put data in it that we
291 // can find data on all the nodes that the user cares about.
292 logger::LogReader reader(logger::SortParts(logger::FindLogs(log_path)));
293 SimulatedEventLoopFactory replay_factory(reader.configuration());
294 reader.RegisterWithoutStarting(&replay_factory);
295
296 // Find every channel we deliberately sent data on, and if it is for a
297 // node that we care about, confirm that we get it during replay.
298 std::vector<std::unique_ptr<EventLoop>> replay_loops;
James Kuszmaul827bd212023-05-15 23:57:39 -0700299 for (const aos::Node *node :
300 configuration::GetNodes(replay_factory.configuration())) {
301 // If the user doesn't care about this node, don't check it.
James Kuszmaul464012b2024-03-20 14:12:08 -0700302 if (!NodeInList(logger_set.has_replay_nodes()
303 ? logger_set.replay_nodes()->AsFlatbufferVector()
304 : nullptr,
305 node)) {
James Kuszmaul827bd212023-05-15 23:57:39 -0700306 continue;
307 }
308 replay_loops.emplace_back(replay_factory.MakeEventLoop("", node));
James Kuszmaul827bd212023-05-15 23:57:39 -0700309 }
310
311 std::vector<std::pair<const aos::Node *, std::unique_ptr<RawFetcher>>>
James Kuszmaul464012b2024-03-20 14:12:08 -0700312 fetchers;
313 for (const auto &node_senders : test_senders) {
314 for (const auto &sender : node_senders.second) {
315 for (auto &loop : replay_loops) {
316 if (configuration::ChannelIsReadableOnNode(sender->channel(),
317 loop->node())) {
318 fetchers.push_back(std::make_pair(
319 loop->node(),
320 loop->MakeRawFetcher(configuration::GetChannel(
321 replay_factory.configuration(), sender->channel(),
322 loop->name(), loop->node()))));
323 }
James Kuszmaul827bd212023-05-15 23:57:39 -0700324 }
325 }
326 }
327
328 replay_factory.Run();
329
James Kuszmaul464012b2024-03-20 14:12:08 -0700330 for (auto &pair : fetchers) {
James Kuszmaul827bd212023-05-15 23:57:39 -0700331 EXPECT_TRUE(pair.second->Fetch())
332 << "Failed to log or replay any data on "
333 << configuration::StrippedChannelToString(pair.second->channel())
James Kuszmaul464012b2024-03-20 14:12:08 -0700334 << " reading from " << logger::MaybeNodeName(pair.first)
335 << " with source node "
336 << (pair.second->channel()->has_source_node()
337 ? pair.second->channel()->source_node()->string_view()
338 : "")
339 << ".";
James Kuszmaul827bd212023-05-15 23:57:39 -0700340 }
341
342 reader.Deregister();
343
344 // Clean up the logs.
345 UnlinkRecursive(log_path);
346 }
347 }
348}
349
350} // namespace aos::util