| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "config/analyzer_config_manager.h" |
| |
| #include <errno.h> |
| #include <spawn.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| #include <fstream> |
| #include <memory> |
| #include <mutex> |
| #include <string> |
| #include <utility> |
| |
| #include "config/analyzer_config.h" |
| #include "config/cobalt_registry.pb.h" |
| #include "gflags/gflags.h" |
| #include "glog/logging.h" |
| #include "util/log_based_metrics.h" |
| |
| namespace cobalt { |
| namespace config { |
| |
| DEFINE_string(cobalt_registry_proto_path, "", |
| "Location on disk of the serialized CobaltRegistry proto from " |
| "which the Report Master's configuration is to be read."); |
| DEFINE_string(config_update_repository_url, "", |
| "URL to a git repository containing a cobalt configuration in " |
| "its master branch. If this flag is set, the configuration of " |
| "report master will be updated by pulling from the specified " |
| "repository before scheduled reports are run. " |
| "(e.g. \"https://cobalt-analytics.googlesource.com/config/\")"); |
| DEFINE_string(config_parser_bin_path, "/usr/local/bin/config_parser", |
| "Location on disk of the configuration parser."); |
| |
| // Stackdriver metric constants |
| namespace { |
| const char kUpdateFailure[] = "analyzer-config-manager-update-failure"; |
| const char kReadConfigFromCobaltRegistryFileFailure[] = |
| "analyzer-config-manager-read-config-from-cobalt-config-file-failure"; |
| } // namespace |
| |
| AnalyzerConfigManager::AnalyzerConfigManager( |
| std::shared_ptr<AnalyzerConfig> config, |
| std::string cobalt_registry_proto_path, |
| std::string config_update_repository_url, |
| std::string config_parser_bin_path) |
| : cobalt_registry_proto_path_(cobalt_registry_proto_path), |
| update_repository_path_(config_update_repository_url), |
| config_parser_bin_path_(config_parser_bin_path) { |
| ptr_ = config; |
| } |
| |
| AnalyzerConfigManager::AnalyzerConfigManager( |
| std::shared_ptr<AnalyzerConfig> config) { |
| ptr_ = config; |
| } |
| |
| std::shared_ptr<AnalyzerConfig> AnalyzerConfigManager::GetCurrent() { |
| std::lock_guard<std::mutex> lock(m_); |
| return ptr_; |
| } |
| |
| std::shared_ptr<AnalyzerConfigManager> |
| AnalyzerConfigManager::CreateFromFlagsOrDie() { |
| if (FLAGS_cobalt_registry_proto_path.empty()) { |
| auto config = AnalyzerConfig::CreateFromFlagsOrDie(); |
| return std::shared_ptr<AnalyzerConfigManager>( |
| new AnalyzerConfigManager(std::move(config))); |
| } |
| |
| // If a file containing a serialized CobaltRegistry is specified, we load the |
| // initial configuration from that file. |
| auto config = ReadConfigFromSerializedCobaltRegistryFile( |
| FLAGS_cobalt_registry_proto_path); |
| if (!config) { |
| LOG(FATAL) << "Could not load the initial configuration."; |
| } |
| LOG(INFO) << "Initial configuration loaded."; |
| |
| auto manager = |
| std::shared_ptr<AnalyzerConfigManager>(new AnalyzerConfigManager( |
| std::move(config), FLAGS_cobalt_registry_proto_path, |
| FLAGS_config_update_repository_url, FLAGS_config_parser_bin_path)); |
| manager->Update(120); |
| return manager; |
| } |
| |
| bool AnalyzerConfigManager::Update(unsigned int timeout_seconds) { |
| // If no repository to get updates from was specified, skip the update. |
| if (update_repository_path_.empty()) { |
| return false; |
| } |
| |
| LOG(INFO) << "Updating configuration from " << update_repository_path_; |
| |
| std::string timeout_seconds_str = std::to_string(timeout_seconds); |
| const char* argv[] = { |
| config_parser_bin_path_.c_str(), "-repo_url", |
| update_repository_path_.c_str(), "-output_file", |
| cobalt_registry_proto_path_.c_str(), "-git_timeout", |
| timeout_seconds_str.c_str(), nullptr, |
| }; |
| pid_t pid; |
| posix_spawnattr_t spawnattr; |
| posix_spawnattr_init(&spawnattr); |
| |
| errno = 0; |
| // The variable "environ" is the current environment found in unistd.h. |
| int status = posix_spawn(&pid, config_parser_bin_path_.c_str(), nullptr, |
| &spawnattr, const_cast<char* const*>(argv), environ); |
| |
| if (0 != status) { |
| LOG_STACKDRIVER_COUNT_METRIC(ERROR, kUpdateFailure) |
| << "Error spawning config_parser at " << config_parser_bin_path_ |
| << " with error: " << strerror(errno); |
| return false; |
| } |
| LOG(INFO) << "Spawned " << config_parser_bin_path_; |
| |
| // Catch state changes of config_parser process until it terminates. |
| errno = 0; |
| if (waitpid(pid, &status, WUNTRACED) != pid) { |
| LOG_STACKDRIVER_COUNT_METRIC(ERROR, kUpdateFailure) |
| << "Error waiting for config_parser: " << strerror(errno); |
| return false; |
| } |
| |
| if (!WIFEXITED(status) || 0 != WEXITSTATUS(status)) { |
| if (WIFSIGNALED(status)) { |
| LOG_STACKDRIVER_COUNT_METRIC(ERROR, kUpdateFailure) |
| << "config_parser terminated by signal " << WTERMSIG(status); |
| return false; |
| } else if (WIFEXITED(status)) { |
| LOG_STACKDRIVER_COUNT_METRIC(ERROR, kUpdateFailure) |
| << "config_parser exited with signal " << WEXITSTATUS(status); |
| return false; |
| } else if (WIFSTOPPED(status)) { |
| LOG_STACKDRIVER_COUNT_METRIC(ERROR, kUpdateFailure) |
| << "config_parser was stopped by signal " << WSTOPSIG(status); |
| return false; |
| } else { |
| LOG_STACKDRIVER_COUNT_METRIC(ERROR, kUpdateFailure) |
| << "Error while waiting for config_parser."; |
| return false; |
| } |
| } |
| LOG(INFO) << "Done getting updated configuration from " |
| << update_repository_path_; |
| |
| auto config = |
| ReadConfigFromSerializedCobaltRegistryFile(cobalt_registry_proto_path_); |
| std::lock_guard<std::mutex> lock(m_); |
| ptr_.reset(config.release()); |
| |
| LOG(INFO) << "Configuration updated."; |
| return true; |
| } |
| |
| std::unique_ptr<AnalyzerConfig> |
| AnalyzerConfigManager::ReadConfigFromSerializedCobaltRegistryFile( |
| std::string config_path) { |
| std::ifstream config_file_stream; |
| config_file_stream.open(config_path); |
| if (!config_file_stream) { |
| LOG_STACKDRIVER_COUNT_METRIC(ERROR, |
| kReadConfigFromCobaltRegistryFileFailure) |
| << "Could not open config proto: " << config_path; |
| return nullptr; |
| } |
| |
| CobaltRegistry cobalt_config; |
| if (!cobalt_config.ParseFromIstream(&config_file_stream)) { |
| LOG_STACKDRIVER_COUNT_METRIC(ERROR, |
| kReadConfigFromCobaltRegistryFileFailure) |
| << "Could not parse config proto: " << config_path; |
| return nullptr; |
| } |
| |
| auto config = AnalyzerConfig::CreateFromCobaltRegistryProto(&cobalt_config); |
| if (!config) { |
| LOG_STACKDRIVER_COUNT_METRIC(ERROR, |
| kReadConfigFromCobaltRegistryFileFailure) |
| << "Error creating AnalyzerConfig: " << config_path; |
| return nullptr; |
| } |
| |
| return config; |
| } |
| |
| } // namespace config |
| } // namespace cobalt |