33 #include <boost/filesystem/path.hpp> 34 #include <boost/optional.hpp> 47 #ifndef CLOE_STACK_VERSION 48 #define CLOE_STACK_VERSION "4.1" 51 #ifndef CLOE_STACK_SUPPORTED_VERSIONS 52 #define CLOE_STACK_SUPPORTED_VERSIONS {"4", "4.0", "4.1"} 55 #ifndef CLOE_XDG_SUFFIX 56 #define CLOE_XDG_SUFFIX "cloe" 59 #ifndef CLOE_CONFIG_HOME 60 #define CLOE_CONFIG_HOME "${XDG_CONFIG_HOME-${HOME}/.config}/" CLOE_XDG_SUFFIX 63 #ifndef CLOE_DATA_HOME 64 #define CLOE_DATA_HOME "${XDG_DATA_HOME-${HOME}/.local/share}/" CLOE_XDG_SUFFIX 67 #define CLOE_SIMULATION_UUID_VAR "CLOE_SIMULATION_UUID" 79 const Conf& conf()
const {
return conf_; }
90 inline auto id_prototype(std::string&& desc =
"") {
91 return schema::make_prototype<std::string>(std::move(desc)).c_identifier();
94 inline auto id_path_prototype(std::string&& desc =
"") {
95 return schema::make_prototype<std::string>(std::move(desc))
96 .pattern(
"^([a-zA-Z_][a-zA-Z0-9_]*/?)+$");
106 using IncludeSchema = decltype(schema::make_schema(static_cast<IncludeConf*>(
nullptr),
""));
162 boost::optional<std::string> pattern;
163 boost::optional<LogLevel> level;
172 {
"name", make_schema(&name,
"name of the logger to configure").require()},
173 {
"pattern", make_schema(&pattern,
"pattern of the logger")},
174 {
"level", make_schema(&level,
"level of the logger")},
179 const auto& s = this->
schema();
181 if (!c.
has(
"pattern") && !c.
has(
"level")) {
183 "require at least one of 'pattern' or 'level' properties");
192 std::string listen_address{
"127.0.0.1"};
193 uint16_t listen_port{8080};
194 uint16_t listen_threads{10};
195 std::string api_prefix{
"/api"};
196 std::string static_prefix{
""};
202 {
"listen", make_schema(&listen,
"whether web server is enabled")},
203 {
"listen_address", make_schema(&listen_address,
"address web server should listen at")},
204 {
"listen_port", make_schema(&listen_port,
"port web server should listen at")},
205 {
"listen_threads", make_schema(&listen_threads,
"threads web server should use")},
206 {
"static_prefix", make_schema(&static_prefix,
"endpoint prefix for static resources")},
207 {
"api_prefix", make_schema(&api_prefix,
"endpoint prefix for API resources")},
220 boost::filesystem::path plugin_path{};
223 boost::optional<std::string> plugin_name{};
226 boost::optional<std::string> plugin_prefix{};
229 boost::optional<bool> ignore_missing{};
236 boost::optional<bool> ignore_failure{};
244 boost::optional<bool> allow_clobber{};
248 explicit PluginConf(
const std::string& p) : plugin_path(p) {}
258 std::string canonical()
const;
264 auto proto = String(
nullptr,
"").c_identifier();
266 {
"path", make_schema(&plugin_path,
"absolute or relative path to plugin").require().not_empty().normalize(
true)},
267 {
"name", make_schema(&plugin_name, proto,
"alternative name plugin is available by")},
268 {
"prefix", make_schema(&plugin_prefix, proto,
"prefix the plugin name with this")},
269 {
"ignore_missing", make_schema(&ignore_missing,
"ignore not-exist errors")},
270 {
"ignore_failure", make_schema(&ignore_failure,
"ignore plugin loading errors")},
271 {
"allow_clobber", make_schema(&allow_clobber,
"replace same-named plugins")},
296 {WatchdogMode::Off,
"off"},
297 {WatchdogMode::Log,
"log"},
298 {WatchdogMode::Abort,
"abort"},
299 {WatchdogMode::Kill,
"kill"},
303 struct EngineConf :
public Confable {
305 std::vector<std::string> ignore_sections{};
308 bool security_enable_hooks{
true};
309 bool security_enable_commands{
false};
310 bool security_enable_includes{
true};
311 size_t security_max_include_depth{64};
314 std::vector<std::string> plugin_path{};
315 bool plugins_ignore_missing{
false};
316 bool plugins_ignore_failure{
false};
317 bool plugins_allow_clobber{
true};
320 std::vector<Command> hooks_pre_connect{};
321 std::vector<Command> hooks_post_disconnect{};
324 bool triggers_ignore_source{
false};
327 boost::optional<boost::filesystem::path> registry_path{CLOE_DATA_HOME
"/registry"};
328 boost::optional<boost::filesystem::path> output_path{
"${CLOE_SIMULATION_UUID}"};
329 boost::optional<boost::filesystem::path> output_file_config{
"config.json"};
330 boost::optional<boost::filesystem::path> output_file_result{
"result.json"};
331 boost::optional<boost::filesystem::path> output_file_triggers{
"triggers.json"};
332 boost::optional<boost::filesystem::path> output_file_data_stream;
333 bool output_clobber_files{
true};
340 std::chrono::milliseconds polling_interval{100};
358 std::chrono::milliseconds watchdog_default_timeout{90
'000}; 387 std::map<std::string, boost::optional<std::chrono::milliseconds>> watchdog_state_timeouts{ 388 {"CONNECT", std::chrono::milliseconds{300'000}},
389 {
"ABORT", std::chrono::milliseconds{90
'000}}, 390 {"STOP", std::chrono::milliseconds{300'000}},
391 {
"DISCONNECT", std::chrono::milliseconds{600
'000}}, 400 bool keep_alive{false}; 402 public: // Confable Overrides 403 CONFABLE_SCHEMA(EngineConf) { 405 using namespace schema; // NOLINT(build/namespaces) 406 auto dir_proto = []() { return make_prototype<boost::filesystem::path>().not_file(); }; 407 auto file_proto = []() { return make_prototype<boost::filesystem::path>().not_dir().resolve(false); }; 409 {"ignore", make_schema(&ignore_sections, "JSON pointers to sections that should be ignored").extend(true)}, 411 {"enable_hooks_section", make_schema(&security_enable_hooks, "whether to enable engine hooks")}, 412 {"enable_command_action", make_schema(&security_enable_commands, "whether to enable the command action")}, 413 {"enable_include_section", make_schema(&security_enable_includes, "whether to allow config files to include other files")}, 414 {"max_include_depth", make_schema(&security_max_include_depth, "how many recursive includes are allowed")}, 417 {"pre_connect", make_schema(&hooks_pre_connect, "pre-connect hooks to execute").extend(true)}, 418 {"post_disconnect", make_schema(&hooks_post_disconnect, "post-disconnect hooks to execute").extend(true)}, 420 {"plugin_path", make_schema(&plugin_path, "list of directories to scan for plugins").extend(false)}, 422 {"ignore_missing", make_schema(&plugins_ignore_missing, "ignore not-exist errors")}, 423 {"ignore_failure", make_schema(&plugins_ignore_failure, "ignore plugin loading errors")}, 424 {"allow_clobber", make_schema(&plugins_allow_clobber, "replace same-named plugins")}, 426 {"registry_path", make_schema(®istry_path, dir_proto(), "cloe registry directory")}, 428 {"path", make_schema(&output_path, dir_proto().resolve(false), "directory to dump output files in, relative to registry path")}, 429 {"clobber", make_schema(&output_clobber_files, "whether to clobber existing files or not")}, 431 {"config", make_schema(&output_file_config, file_proto(), "file to store config in")}, 432 {"result", make_schema(&output_file_result, file_proto(), "file to store simulation result in")}, 433 {"triggers", make_schema(&output_file_triggers, file_proto(), "file to store triggers in")}, 434 {"api_recording", make_schema(&output_file_data_stream, file_proto(), "file to store api data stream")}, 438 {"ignore_source", make_schema(&triggers_ignore_source, "ignore trigger source when reading in triggers")}, 440 {"polling_interval", make_schema(&polling_interval, "milliseconds to sleep when polling for next state")}, 442 {"mode", make_schema(&watchdog_mode, "modus operandi of watchdog [one of: off, log, abort, kill]")}, 443 {"default_timeout", make_schema(&watchdog_default_timeout, "default timeout if not overridden, 0 for no timeout")}, 444 {"state_timeouts", make_schema(&watchdog_state_timeouts, "timeout specific to a given state, 0 for no timeout").unique_properties(false)}, 446 {"keep_alive", make_schema(&keep_alive, "keep simulation alive after termination")}, 452 using EngineSchema = schema_type<EngineConf>::type; 454 // --------------------------------------------------------------------------------------------- // 466 struct DefaultConf : public Confable { 467 boost::optional<std::string> name; 468 boost::optional<std::string> binding; 471 public: // Confable Overrides 472 CONFABLE_SCHEMA(DefaultConf) { 473 using namespace schema; // NOLINT(build/namespaces) 475 {"binding", make_schema(&binding, "name of binding")}, 476 {"name", make_schema(&name, id_prototype(), "globally unique identifier for component")}, 477 {"args", make_schema(&args, "defaults to set for binding/name combination").require()}, 482 // --------------------------------------------------------------------------------------------- // 484 template <typename C, typename F> 485 class FactoryPlugin : public fable::schema::FactoryPointerless<C> { 488 this->set_factory_key("binding"); 489 this->set_args_subset(false); 491 virtual ~FactoryPlugin() = default; 493 void add_plugin(const std::string& name, std::shared_ptr<Plugin> p) { 494 this->set_factory(name, p->make<F>()->schema(), [p, name](const Conf& c) { 495 C tmp{name, p->make<F>()}; 502 // --------------------------------------------------------------------------------------------- // 507 struct SimulatorConf : public Confable { 508 const std::string binding; 509 boost::optional<std::string> name; 510 std::shared_ptr<SimulatorFactory> factory; 513 public: // Constructors 514 SimulatorConf(const std::string& b, std::shared_ptr<SimulatorFactory> f) 515 : binding(b), factory(std::move(f)) {} 517 public: // Confable Overrides 518 CONFABLE_SCHEMA(SimulatorConf) { 519 using namespace schema; // NOLINT(build/namespaces) 521 {"binding", make_const_str(binding, "name of simulator binding").require()}, 522 {"name", make_schema(&name, id_prototype(), "identifier override for binding")}, 523 {"args", make_schema(&args, factory->schema(), "factory-specific arguments")}, 528 class SimulatorSchema : public FactoryPlugin<SimulatorConf, SimulatorFactory> { 531 virtual ~SimulatorSchema() = default; 534 // --------------------------------------------------------------------------------------------- // 539 struct ControllerConf : public Confable { 540 const std::string binding; 541 boost::optional<std::string> name; 543 std::shared_ptr<ControllerFactory> factory; 546 public: // Constructors 547 ControllerConf(const std::string& b, std::shared_ptr<ControllerFactory> f) 548 : binding(b), factory(std::move(f)) {} 550 public: // Confable Overrides 551 CONFABLE_SCHEMA(ControllerConf) { 553 using namespace schema; // NOLINT(build/namespaces) 555 {"binding", make_const_str(binding, "name of controller binding").require()}, 556 {"name", make_schema(&name, id_prototype(), "identifier override for binding")}, 557 {"vehicle", make_schema(&vehicle, "vehicle controller is assigned to").c_identifier().require()}, 558 {"args", make_schema(&args, factory->schema(), "factory-specific arguments")}, 564 class ControllerSchema : public FactoryPlugin<ControllerConf, ControllerFactory> { 567 virtual ~ControllerSchema() = default; 570 // --------------------------------------------------------------------------------------------- // 572 struct FromSimulator : public Confable { 573 std::string simulator; 574 std::string index_str; 578 bool is_by_name() const { return !index_str.empty(); } 579 bool is_by_index() const { return index_str.empty(); } 586 public: // Confable Overrides 587 CONFABLE_SCHEMA(FromSimulator) { 589 using namespace schema; // NOLINT(build/namespaces) 592 {"simulator", make_schema(&simulator, "simulator").not_empty().require()}, 593 {"index", make_schema(&index_num, "index of vehicle in simulator").require()}, 596 {"simulator", make_schema(&simulator, "simulator").not_empty().require()}, 597 {"name", make_schema(&index_str, "name of vehicle in simulator").not_empty().require()}, 603 void from_conf(const Conf& c) override { 604 clear(); // Avoid inconsistent state 605 Confable::from_conf(c); 608 void to_json(Json& j) const override { 611 {"simulator", simulator}, 612 {"index", index_num}, 616 {"simulator", simulator}, 623 struct ComponentConf : public Confable { 624 const std::string binding; 625 boost::optional<std::string> name; 626 std::vector<std::string> from; 627 std::shared_ptr<ComponentFactory> factory; 630 public: // Constructors 631 ComponentConf(const std::string& b, std::shared_ptr<ComponentFactory> f) 632 : binding(b), factory(std::move(f)) {} 634 public: // Confable Overrides 635 CONFABLE_SCHEMA(ComponentConf) { 637 using namespace schema; // NOLINT(build/namespaces) 639 {"binding", make_const_str(binding, "name of binding").require()}, 640 {"name", make_schema(&name, id_prototype(), "globally unique identifier for component")}, 642 make_schema(&from, "component inputs for binding"), 644 make_prototype<std::string>("component input for binding"), 645 [this](CustomDeserializer*, const Conf& c) { 646 this->from.push_back(c.get<std::string>()); 650 {"args", make_schema(&args, factory->schema(), "factory-specific args")}, 656 class ComponentSchema : public FactoryPlugin<ComponentConf, ComponentFactory> { 659 virtual ~ComponentSchema() = default; 662 // TODO(ben): Add AliasConf as alternative to ComponentConf. 685 struct VehicleConf : public Confable { 687 FromSimulator from_sim; 688 std::string from_veh; 689 std::map<std::string, ComponentConf> components; 692 ComponentSchema component_schema; 694 public: // Constructors 695 explicit VehicleConf(const ComponentSchema& s) : component_schema(s) {} 698 bool is_from_simulator() const { return from_veh.empty(); } 699 bool is_from_vehicle() const { return !from_veh.empty(); } 706 public: // Confable Overrides 707 CONFABLE_SCHEMA(VehicleConf) { 709 using namespace schema; // NOLINT(build/namespaces) 711 {"name", make_schema(&name, "globally unique identifier for vehicle").c_identifier().require()}, 713 make_schema(&from_sim, "simulator source"), 714 make_schema(&from_veh, "vehicle source").c_identifier(), 716 {"components", make_schema(&components, component_schema, "component configuration of vehicle")}, 721 void from_conf(const Conf& c) override { 722 clear(); // Avoid inconsistent state 723 Confable::from_conf(c); 726 void to_json(Json& j) const override { 727 Json from = is_from_simulator() ? Json(from_sim) : Json(from_veh); 731 {"components", components}, 736 class VehicleSchema : public fable::schema::Base<VehicleSchema> { 737 public: // Constructors 738 using Type = VehicleConf; 739 using MakeFunc = ComponentSchema::MakeFunc; 740 using TypeFactory = ComponentSchema::TypeFactory; 742 explicit VehicleSchema(std::string&& desc = "") : Base(std::move(desc)) {} 745 bool has_factory(const std::string& name) const { return components_.has_factory(name); } 746 void add_plugin(const std::string& name, std::shared_ptr<Plugin> p) { 747 components_.add_plugin(name, p); 751 Json json_schema() const override { 752 VehicleConf v{components_}; 753 return v.schema().json_schema(); 756 void validate(const Conf& c) const override { 757 VehicleConf v{components_}; 758 v.schema().validate(c); 761 Json serialize(const Type& x) const { return x.to_json(); } 763 Type make(const Conf& c) const { return deserialize(c); } 765 Type deserialize(const Conf& c) const { 766 VehicleConf v{components_}; 771 void from_conf(const Conf&) override { 772 throw std::logic_error("VehicleSchema does not implement from_conf"); 775 void to_json(Json&) const override { 776 throw std::logic_error("VehicleSchema does not implement to_json"); 779 void reset_ptr() override {} 782 ComponentSchema components_; 785 // --------------------------------------------------------------------------------------------- // 787 struct TriggerConf : public PersistentConfable { 788 boost::optional<std::string> label{boost::none}; 789 Source source{Source::FILESYSTEM}; 794 bool optional{false}; 796 public: // Confable Overrides 797 CONFABLE_SCHEMA(TriggerConf) { 799 using namespace schema; // NOLINT(build/namespaces) 800 auto EANDA_SCHEMA = Variant{ 801 String{nullptr, "inline format"}.pattern("^[a-zA-Z0-9_/]+(=.*)?$"), 803 {"name", id_path_prototype().require()} 804 }.additional_properties(true), 807 {"label", make_schema(&label, "description of trigger")}, 808 {"source", make_schema(&source, "source from which trigger originates")}, 809 {"event", make_schema(&event, EANDA_SCHEMA, "event").require()}, 810 {"action", make_schema(&action, EANDA_SCHEMA, "action").require()}, 811 {"sticky", make_schema(&sticky, "whether trigger should be sticky")}, 812 {"conceal", make_schema(&conceal, "whether trigger should be concealed in history")}, 813 {"optional", make_schema(&optional, "whether errors creating event or action should be ignored")}, 814 {"at", Ignore("time at which trigger was executed", JsonType::string)}, 815 {"since", Ignore("time since which trigger was in queue", JsonType::string)}, 821 // --------------------------------------------------------------------------------------------- // 827 struct SimulationConf : public Confable { 831 boost::optional<std::string> name{boost::none}; 836 Duration model_step_width = Duration{20'000
'000}; // 20 ms 843 int64_t controller_retry_limit{1000}; 848 std::chrono::milliseconds controller_retry_sleep{1}; 856 bool abort_on_controller_failure{true}; 858 public: // Confable Overrides 859 CONFABLE_SCHEMA(SimulationConf) { 861 using namespace schema; // NOLINT(build/namespaces) 863 {"namespace", make_schema(&name, id_prototype(), "namespace for simulation events and actions")}, 864 {"model_step_width", make_schema(&model_step_width, "default model time step in ns")}, 865 {"controller_retry_limit", make_schema(&controller_retry_limit, "times to retry controller processing before aborting")}, 866 {"controller_retry_sleep", make_schema(&controller_retry_sleep, "time to sleep before retrying controller process")}, 867 {"abort_on_controller_failure", make_schema(&abort_on_controller_failure, "abort simulation on controller failure")}, 873 // --------------------------------------------------------------------------------------------- // 875 class StackIncompleteError : public Error { 877 explicit StackIncompleteError(std::vector<std::string>&& missing); 879 std::string all_sections_missing(const std::string& sep = ", ") const; 880 const std::vector<std::string>& sections_missing() const { return sections_missing_; } 883 std::vector<std::string> sections_missing_; 886 using ConfReader = std::function<Conf(const std::string&)>; 888 class Stack : public Confable { 889 private: // Constants (1) 890 std::vector<std::string> reserved_ids_; 891 boost::optional<std::string> schema_ref_; 893 public: // Configuration (13) 897 std::vector<IncludeConf> include; 898 std::vector<LoggingConf> logging; 899 std::vector<PluginConf> plugins; 900 std::vector<DefaultConf> simulator_defaults; 901 std::vector<SimulatorConf> simulators; 902 std::vector<DefaultConf> controller_defaults; 903 std::vector<ControllerConf> controllers; 904 std::vector<DefaultConf> component_defaults; 905 std::vector<VehicleConf> vehicles; 906 std::vector<TriggerConf> triggers; 907 SimulationConf simulation; 909 private: // Schemas (3) & Prototypes (3) 910 EngineSchema engine_schema; 911 IncludesSchema include_schema; 912 PluginsSchema plugins_schema; 914 SimulatorSchema simulator_prototype; 915 ControllerSchema controller_prototype; 916 VehicleSchema vehicle_prototype; 918 private: // State (3) 919 std::set<std::string> scanned_plugin_paths_; 920 std::map<std::string, std::shared_ptr<Plugin>> all_plugins_; 921 std::vector<Conf> applied_confs_; 922 ConfReader conf_reader_func_; 924 public: // Constructors 926 Stack(const Stack& other); 927 Stack(Stack&& other); 928 Stack& operator=(Stack other); 931 friend void swap(Stack& left, Stack& right); 934 Logger logger() const { return logger::get("cloe"); } 944 void set_conf_reader(ConfReader fn) { 945 assert(fn != nullptr); 946 conf_reader_func_ = fn; 952 void apply_plugin_conf(const PluginConf& c); 960 void insert_plugin(const PluginConf& c); 968 void insert_plugin(std::shared_ptr<Plugin> p, const PluginConf& c = {}); 973 bool has_plugin_with_name(const std::string& key) const; 978 bool has_plugin_with_path(const std::string& path) const; 985 std::shared_ptr<Plugin> get_plugin_with_name(const std::string& key) const; 992 std::shared_ptr<Plugin> get_plugin_with_path(const std::string& key) const; 997 std::shared_ptr<Plugin> get_plugin_or_load(const std::string& key_or_path) const; 1002 const std::map<std::string, std::shared_ptr<Plugin>>& get_all_plugins() const { 1003 return all_plugins_; 1006 std::vector<DefaultConf> get_simulator_defaults(std::string binding, std::string name) const; 1007 std::vector<DefaultConf> get_controller_defaults(std::string binding, std::string name) const; 1008 std::vector<DefaultConf> get_vehicle_defaults(std::string name) const; 1009 std::vector<DefaultConf> get_component_defaults(std::string binding, std::string name) const; 1018 void validate() const; 1023 bool is_valid() const; 1037 void check_consistency() const; 1042 void check_defaults() const; 1054 bool is_complete() const; 1059 void check_completeness() const; 1065 void initialize() { from_conf(Conf{Json{{"version", CLOE_STACK_VERSION}}}); } 1070 Json active_config() const; 1075 Json input_config() const; 1077 public: // Confable Overrides 1087 void validate(const Conf& c) const override; 1089 void to_json(Json& j) const override; 1090 void from_conf(const Conf& c) override { from_conf(c, 0); } 1091 void reset_schema() override; 1093 CONFABLE_SCHEMA(Stack) { 1095 using namespace schema; // NOLINT(build/namespaces) 1098 {"$schema", make_schema(&schema_ref_, "valid URI to schema describing this cloe stack version")}, 1099 {"version", make_schema(&version, "version of stackfile").require().enum_of( 1100 CLOE_STACK_SUPPORTED_VERSIONS 1102 {"engine", engine_schema}, 1103 {"include", include_schema}, 1104 {"logging", make_schema(&logging, "logging configuration").extend(true)}, 1105 {"plugins", plugins_schema}, 1106 {"server", make_schema(&server, "server configuration")}, 1107 {"defaults", Struct{ 1108 {"simulators", make_schema(&simulator_defaults, "simulator default configurations").extend(true)}, 1109 {"controllers", make_schema(&controller_defaults, "controller default configurations").extend(true)}, 1110 {"components", make_schema(&component_defaults, "component default configurations").extend(true)}, 1112 {"vehicles", make_schema(&vehicles, vehicle_prototype, "vehicle configuration").extend(true)}, 1113 {"simulators", make_schema(&simulators, simulator_prototype, "simulator configuration").extend(true)}, 1114 {"controllers", make_schema(&controllers, controller_prototype, "controller configuration").extend(true)}, 1115 {"triggers", make_schema(&triggers, "triggers").extend(true)}, 1116 {"simulation", make_schema(&simulation, "simulation configuration")}, 1126 void from_conf(const Conf& c, size_t depth);
Definition: confable.hpp:46
Definition: stack.hpp:160
void validate(const Conf &c) const override
Definition: stack.hpp:178
bool has(const std::string &key) const
Definition: conf.hpp:130
Definition: stack.hpp:218
Definition: error.hpp:120
Definition: coordinator.hpp:36
Definition: schema.hpp:178
Schema & schema()
Definition: confable.cpp:55
#define ENUM_SERIALIZATION(xType, xMap)
Definition: enum.hpp:52
Definition: stack.hpp:190
virtual void from_conf(const Conf &c)
Definition: confable.cpp:66
Definition: schema.hpp:167
boost::filesystem::path IncludeConf
Definition: stack.hpp:105
void from_conf(const Conf &c)
Definition: stack.hpp:81
WatchdogMode
Definition: stack.hpp:287