depot_deploy: support multiple runtimes

This patch changes the 'depot_deploy' tool to spawn any number of
runtimes. In contrast to the original version, which merely consumed a
blueprint generated by a pre-configured 'depot_query' instance, the new
version actively generates queries as needed. So there is a feedback
loop between 'depot_deploy' and 'depot_query'. The instantiation of
subsystems is controlled by the '<start>' nodes of the 'depot_deploy'
configuration. For each start node, the tool tries to determine the
ingredients (provided by the depot) by asking the 'depot_query' tool.
Once the information is complete, a corresponding start node of the
dynamic init instance is generated.
This commit is contained in:
Norman Feske 2018-01-23 19:21:25 +01:00
parent 3149506963
commit d7522defde
5 changed files with 447 additions and 147 deletions

View File

@ -43,7 +43,8 @@ install_config {
<provides> <service name="Report"/> <service name="ROM"/> </provides>
<config verbose="yes">
<policy label="depot_deploy -> blueprint" report="depot_query -> blueprint"/>
<policy label="subinit -> config" report="depot_deploy -> init.config"/>
<policy label="depot_query -> query" report="depot_deploy -> query"/>
<policy label="dynamic -> config" report="depot_deploy -> init.config"/>
</config>
</start>
@ -64,20 +65,18 @@ install_config {
<start name="depot_query">
<resource name="RAM" quantum="1M"/>
<config arch="} [depot_spec] {">
<config query="rom">
<vfs> <dir name="depot"> <fs label="depot"/> </dir> </vfs>
<env>
<rom label="ld.lib.so"/>
<rom label="init"/>
</env>
<scan user="genodelabs"/>
<blueprint pkg="genodelabs/pkg/} [query_pkg] {"/>
</config>
<route>
<service name="ROM" label="query"> <child name="report_rom"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="depot_deploy">
<resource name="RAM" quantum="1M"/>
<config>
<config arch="} [depot_spec] {">
<static>
<parent-provides>
<service name="ROM"/>
@ -95,6 +94,7 @@ install_config {
<service name="LOG"> <parent/> </service>
<service name="Timer"> <parent/> </service>
</common_routes>
<start name="test" pkg="genodelabs/pkg/} [query_pkg] {"/>
</config>
<route>
<service name="ROM" label="blueprint"> <child name="report_rom"/> </service>
@ -102,7 +102,7 @@ install_config {
</route>
</start>
<start name="subinit" caps="8000">
<start name="dynamic" caps="8000">
<resource name="RAM" quantum="64M"/>
<binary name="init"/>
<route>

View File

@ -0,0 +1,294 @@
/*
* \brief Child representation
* \author Norman Feske
* \date 2018-01-23
*/
/*
* Copyright (C) 2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _CHILD_H_
#define _CHILD_H_
/* Genode includes */
#include <util/list_model.h>
#include <base/service.h>
#include <os/reporter.h>
#include <os/buffered_xml.h>
#include <depot/archive.h>
namespace Depot_deploy {
using namespace Depot;
struct Child;
}
class Depot_deploy::Child : public List_model<Child>::Element
{
public:
typedef String<100> Name;
typedef String<80> Binary_name;
typedef String<80> Config_name;
private:
Allocator &_alloc;
Reconstructible<Buffered_xml> _start_xml; /* from config */
Constructible<Buffered_xml> _pkg_xml { }; /* from blueprint */
Name const _name;
Archive::Path _config_pkg_path() const
{
return _start_xml->xml().attribute_value("pkg", Archive::Path());
}
/*
* The pkg-archive path of the current blueprint query, which may
* deviate from pkg path given in the config, once the config is
* updated.
*/
Archive::Path _blueprint_pkg_path = _config_pkg_path();
Ram_quota _ram_quota { 0 };
Cap_quota _cap_quota { 0 };
Binary_name _binary_name { };
Config_name _config_name { };
bool _configured() const
{
return _pkg_xml.constructed()
&& (_config_pkg_path() == _blueprint_pkg_path);
}
inline void _gen_routes(Xml_generator &, Xml_node common) const;
static void _gen_provides_sub_node(Xml_generator &xml, Xml_node service,
Xml_node::Type const &node_type,
Service::Name const &service_name)
{
if (service.type() == node_type)
xml.node("service", [&] () {
xml.attribute("name", service_name); });
}
static void _gen_copy_of_sub_node(Xml_generator &xml, Xml_node from_node,
Xml_node::Type const &sub_node_type)
{
if (!from_node.has_sub_node(sub_node_type.string()))
return;
Xml_node const sub_node = from_node.sub_node(sub_node_type.string());
xml.append(sub_node.addr(), sub_node.size());
}
public:
Child(Allocator &alloc, Xml_node start_node)
:
_alloc(alloc),
_start_xml(_alloc, start_node),
_name(_start_xml->xml().attribute_value("name", Name()))
{ }
Name name() const { return _name; }
void apply_config(Xml_node start_node)
{
/*
* String-compare new with current start node to quicky skip
* the start nodes that have not changed.
*/
bool const start_node_changed =
(start_node.size() != _start_xml->xml().size()) ||
(strcmp(start_node.addr(), _start_xml->xml().addr(),
start_node.size()) != 0);
if (!start_node_changed)
return;
Archive::Path const pkg =
start_node.attribute_value("pkg", Archive::Path());
/* invalidate blueprint if 'pkg' attribute of start node changed */
if (pkg != _config_pkg_path())
_pkg_xml.destruct();
/* import new start node */
_start_xml.construct(_alloc, start_node);
}
void apply_blueprint(Xml_node pkg)
{
if (pkg.attribute_value("path", Archive::Path()) != _blueprint_pkg_path)
return;
Xml_node const runtime = pkg.sub_node("runtime");
_ram_quota = Ram_quota { runtime.attribute_value("ram", Number_of_bytes()) };
_cap_quota = Cap_quota { runtime.attribute_value("caps", 0UL) };
_binary_name = runtime.attribute_value("binary", Binary_name());
_config_name = runtime.attribute_value("config", Config_name());
/* keep copy of the blueprint info */
_pkg_xml.construct(_alloc, pkg);
}
void gen_query(Xml_generator &xml) const
{
if (_configured())
return;
xml.node("blueprint", [&] () {
xml.attribute("pkg", _blueprint_pkg_path); });
}
/**
* Generate start node of init configuration
*
* \param common session routes to be added in addition to the ones
* found in the pkg blueprint
*/
inline void gen_start_node(Xml_generator &, Xml_node common) const;
};
void Depot_deploy::Child::gen_start_node(Xml_generator &xml, Xml_node common) const
{
if (!_configured())
return;
if (!_pkg_xml->xml().has_sub_node("runtime")) {
warning("blueprint for '", _name, "' lacks runtime information");
return;
}
xml.node("start", [&] () {
xml.attribute("name", _name);
xml.attribute("caps", _cap_quota.value);
xml.node("binary", [&] () { xml.attribute("name", _binary_name); });
xml.node("resource", [&] () {
xml.attribute("name", "RAM");
xml.attribute("quantum", String<32>(Number_of_bytes(_ram_quota.value)));
});
Xml_node const runtime = _pkg_xml->xml().sub_node("runtime");
/*
* Insert inline '<config>' node if provided by the start node or the
* blueprint. The former is preferred over the latter.
*/
if (_start_xml->xml().has_sub_node("config")) {
_gen_copy_of_sub_node(xml, _start_xml->xml(), "config");
} else {
if (runtime.has_sub_node("config"))
_gen_copy_of_sub_node(xml, runtime, "config");
}
/*
* Declare services provided by the subsystem.
*/
if (runtime.has_sub_node("provides")) {
xml.node("provides", [&] () {
runtime.sub_node("provides").for_each_sub_node([&] (Xml_node service) {
_gen_provides_sub_node(xml, service, "rom", "ROM");
_gen_provides_sub_node(xml, service, "log", "LOG");
_gen_provides_sub_node(xml, service, "timer", "Timer");
_gen_provides_sub_node(xml, service, "block", "Block");
_gen_provides_sub_node(xml, service, "report", "Report");
_gen_provides_sub_node(xml, service, "nic", "Nic");
_gen_provides_sub_node(xml, service, "nitpicker", "Nitpicker");
_gen_provides_sub_node(xml, service, "framebuffer", "Framebuffer");
_gen_provides_sub_node(xml, service, "input", "Input");
_gen_provides_sub_node(xml, service, "audio_out", "Audio_out");
_gen_provides_sub_node(xml, service, "audio_in", "Audio_in");
_gen_provides_sub_node(xml, service, "file_system", "File_system");
});
});
}
xml.node("route", [&] () { _gen_routes(xml, common); });
});
}
void Depot_deploy::Child::_gen_routes(Xml_generator &xml, Xml_node common) const
{
if (!_pkg_xml.constructed())
return;
typedef String<160> Path;
/*
* Add routes given in the start node.
*/
if (_start_xml->xml().has_sub_node("route")) {
Xml_node const route = _start_xml->xml().sub_node("route");
xml.append(route.content_base(), route.content_size());
}
/*
* Redirect config ROM request to label as given in the 'config' attribute,
* if present. We need to search the blueprint's <rom> nodes for the
* matching ROM module to rewrite the label with the configuration's path
* within the depot.
*/
if (_config_name.valid()) {
_pkg_xml->xml().for_each_sub_node("rom", [&] (Xml_node rom) {
if (!rom.has_attribute("path"))
return;
if (rom.attribute_value("label", Name()) != _config_name)
return;
/* we found the <rom> node for the config ROM */
xml.node("service", [&] () {
xml.attribute("name", "ROM");
xml.attribute("label", "config");
xml.node("parent", [&] () {
typedef String<160> Path;
xml.attribute("label", rom.attribute_value("path", Path()));
});
});
});
}
/*
* Add common routes as defined in our config.
*/
xml.append(common.content_base(), common.content_size());
/*
* Add ROM routing rule with the label rewritten to the path within the
* depot.
*/
_pkg_xml->xml().for_each_sub_node("rom", [&] (Xml_node rom) {
if (!rom.has_attribute("path"))
return;
typedef Name Label;
Path const path = rom.attribute_value("path", Path());
Label const label = rom.attribute_value("label", Label());
xml.node("service", [&] () {
xml.attribute("name", "ROM");
xml.attribute("label_last", label);
xml.node("parent", [&] () {
xml.attribute("label", path); });
});
});
}
#endif /* _CHILD_H_ */

View File

@ -12,16 +12,83 @@
*/
/* Genode includes */
#include <util/retry.h>
#include <base/component.h>
#include <base/attached_rom_dataspace.h>
#include <base/heap.h>
#include <os/reporter.h>
/* local includes */
#include "child.h"
namespace Depot_deploy {
using namespace Genode;
struct Children;
struct Main;
}
class Depot_deploy::Children
{
private:
Allocator &_alloc;
List_model<Child> _children { };
struct Model_update_policy : List_model<Child>::Update_policy
{
Allocator &_alloc;
Model_update_policy(Allocator &alloc) : _alloc(alloc) { }
void destroy_element(Child &c) { destroy(_alloc, &c); }
Child &create_element(Xml_node node)
{
return *new (_alloc) Child(_alloc, node);
}
void update_element(Child &c, Xml_node node) { c.apply_config(node); }
static bool element_matches_xml_node(Child const &child, Xml_node node)
{
return node.attribute_value("name", Child::Name()) == child.name();
}
static bool node_is_element(Xml_node node) { return node.has_type("start"); }
} _model_update_policy { _alloc };
public:
Children(Allocator &alloc) : _alloc(alloc) { }
void apply_config(Xml_node config)
{
_children.update_from_xml(_model_update_policy, config);
}
void apply_blueprint(Xml_node blueprint)
{
blueprint.for_each_sub_node("pkg", [&] (Xml_node pkg) {
_children.for_each([&] (Child &child) {
child.apply_blueprint(pkg); }); });
}
void gen_start_nodes(Xml_generator &xml, Xml_node common)
{
_children.for_each([&] (Child const &child) {
child.gen_start_node(xml, common); });
}
void gen_queries(Xml_generator &xml)
{
_children.for_each([&] (Child const &child) {
child.gen_query(xml); });
}
};
struct Depot_deploy::Main
{
Env &_env;
@ -29,67 +96,55 @@ struct Depot_deploy::Main
Attached_rom_dataspace _config { _env, "config" };
Attached_rom_dataspace _blueprint { _env, "blueprint" };
Reporter _init_config_reporter { _env, "config", "init.config", 16*1024 };
Expanding_reporter _query_reporter { _env, "query" , "query"};
Expanding_reporter _init_config_reporter { _env, "config", "init.config"};
size_t _query_buffer_size = 4096;
size_t _init_config_buffer_size = 4096;
Heap _heap { _env.ram(), _env.rm() };
Children _children { _heap };
Signal_handler<Main> _config_handler {
_env.ep(), *this, &Main::_handle_config };
typedef String<128> Name;
typedef String<80> Binary;
typedef String<80> Config;
/**
* Generate start node of init configuration
*
* \param pkg pkg node of the subsystem blueprint
* \param common session routes to be added in addition to the ones
* found in the pkg blueprint
*/
static void _gen_start_node(Xml_generator &, Xml_node pkg, Xml_node common);
void _handle_config()
{
_config.update();
_blueprint.update();
Xml_node const config = _config.xml();
Xml_node const blueprint = _blueprint.xml();
Xml_node const config = _config.xml();
Reporter::Xml_generator xml(_init_config_reporter, [&] () {
_children.apply_config(config);
_children.apply_blueprint(_blueprint.xml());
/* determine CPU architecture of deployment */
typedef String<16> Arch;
Arch const arch = config.attribute_value("arch", Arch());
if (!arch.valid())
warning("config lacks 'arch' attribute");
/* generate init config containing all configured start nodes */
_init_config_reporter.generate([&] (Xml_generator &xml) {
Xml_node static_config = config.sub_node("static");
xml.append(static_config.content_base(), static_config.content_size());
blueprint.for_each_sub_node("pkg", [&] (Xml_node pkg) {
/*
* Check preconditions for generating a '<start>' node.
*/
Name const name = pkg.attribute_value("name", Name());
if (!pkg.has_sub_node("runtime")) {
warning("<pkg> node for '", name, "' lacks <runtime> node");
return;
}
Xml_node const runtime = pkg.sub_node("runtime");
if (!runtime.has_attribute("binary")) {
warning("<runtime> node for '", name, "' lacks 'binary' attribute");
return;
}
xml.node("start", [&] () {
_gen_start_node(xml, pkg, config.sub_node("common_routes"));
});
});
_children.gen_start_nodes(xml, config.sub_node("common_routes"));
});
/* update query for blueprints of all unconfigured start nodes */
if (arch.valid()) {
_query_reporter.generate([&] (Xml_generator &xml) {
xml.attribute("arch", arch);
_children.gen_queries(xml);
});
}
}
Main(Env &env) : _env(env)
{
_init_config_reporter.enabled(true);
_config .sigh(_config_handler);
_blueprint.sigh(_config_handler);
@ -98,80 +153,5 @@ struct Depot_deploy::Main
};
void Depot_deploy::Main::_gen_start_node(Xml_generator &xml, Xml_node pkg, Xml_node common)
{
typedef String<80> Name;
Name const name = pkg.attribute_value("name", Name());
Xml_node const runtime = pkg.sub_node("runtime");
size_t const caps = runtime.attribute_value("caps", 0UL);
Number_of_bytes const ram = runtime.attribute_value("ram", Number_of_bytes());
Binary const binary = runtime.attribute_value("binary", Binary());
Config const config = runtime.attribute_value("config", Config());
xml.attribute("name", name);
xml.attribute("caps", caps);
xml.node("binary", [&] () { xml.attribute("name", binary); });
xml.node("resource", [&] () {
xml.attribute("name", "RAM");
xml.attribute("quantum", String<32>(ram));
});
/*
* Insert inline '<config>' node if provided by the blueprint.
*/
if (runtime.has_sub_node("config")) {
Xml_node config = runtime.sub_node("config");
xml.node("config", [&] () {
xml.append(config.content_base(), config.content_size()); });
};
xml.node("route", [&] () {
/*
* Redirect config ROM request to label given in the 'config'
* attribute.
*/
if (config.valid()) {
xml.node("service", [&] () {
xml.attribute("name", "ROM");
xml.attribute("label", "config");
xml.node("parent", [&] () {
xml.attribute("label", config); });
});
}
/*
* Add common routes as defined in our config.
*/
xml.append(common.content_base(), common.content_size());
/*
* Add ROM routing rule with the label rewritten to
* the path within the depot.
*/
pkg.for_each_sub_node("rom", [&] (Xml_node rom) {
if (!rom.has_attribute("path"))
return;
typedef String<160> Path;
typedef Name Label;
Path const path = rom.attribute_value("path", Path());
Label const label = rom.attribute_value("label", Label());
xml.node("service", [&] () {
xml.attribute("name", "ROM");
xml.attribute("label_last", label);
xml.node("parent", [&] () {
xml.attribute("label", path); });
});
});
});
}
void Component::construct(Genode::Env &env) { static Depot_deploy::Main main(env); }

View File

@ -1,5 +1,3 @@
TARGET = depot_deploy
SRC_CC = main.cc
LIBS += base vfs
CC_CXX_WARN_STRICT =
LIBS += base

View File

@ -161,10 +161,25 @@ struct Depot_query::Main
Signal_handler<Main> _config_handler {
_env.ep(), *this, &Main::_handle_config };
Reporter _directory_reporter { _env, "directory" };
Reporter _blueprint_reporter { _env, "blueprint" };
Reporter _dependencies_reporter { _env, "dependencies" };
Reporter _user_reporter { _env, "user" };
Signal_handler<Main> _query_handler {
_env.ep(), *this, &Main::_handle_config };
typedef Constructible<Expanding_reporter> Constructible_reporter;
Constructible_reporter _directory_reporter { _env, "directory" };
Constructible_reporter _blueprint_reporter { _env, "blueprint" };
Constructible_reporter _dependencies_reporter { _env, "dependencies" };
Constructible_reporter _user_reporter { _env, "user" };
template <typename T, typename... ARGS>
static void _construct_if(bool condition, Constructible<T> &obj, ARGS &&... args)
{
if (condition && !obj.constructed())
obj.construct(args...);
if (!condition && obj.constructed())
obj.destruct();
}
typedef String<64> Rom_label;
typedef String<16> Architecture;
@ -205,7 +220,7 @@ struct Depot_query::Main
if (query_from_rom && !_query_rom.constructed()) {
_query_rom.construct(_env, "query");
_query_rom->sigh(_config_handler);
_query_rom->sigh(_query_handler);
}
if (!query_from_rom && _query_rom.constructed())
@ -216,20 +231,31 @@ struct Depot_query::Main
Xml_node const query = (query_from_rom ? _query_rom->xml() : config);
_directory_reporter .enabled(query.has_sub_node("scan"));
_blueprint_reporter .enabled(query.has_sub_node("blueprint"));
_dependencies_reporter.enabled(query.has_sub_node("dependencies"));
_user_reporter .enabled(query.has_sub_node("user"));
_construct_if(query.has_sub_node("scan"),
_directory_reporter, _env, "directory", "directory");
_construct_if(query.has_sub_node("blueprint"),
_blueprint_reporter, _env, "blueprint", "blueprint");
_construct_if(query.has_sub_node("dependencies"),
_dependencies_reporter, _env, "dependencies", "dependencies");
_construct_if(query.has_sub_node("user"),
_user_reporter, _env, "user", "user");
_root.apply_config(config.sub_node("vfs"));
/* ignore incomplete queries that may occur at the startup */
if (query.has_type("empty"))
return;
if (!query.has_attribute("arch"))
warning("query lacks 'arch' attribute");
_architecture = query.attribute_value("arch", Architecture());
if (_directory_reporter.enabled()) {
Reporter::Xml_generator xml(_directory_reporter, [&] () {
if (_directory_reporter.constructed()) {
_directory_reporter->generate([&] (Xml_generator &xml) {
query.for_each_sub_node("scan", [&] (Xml_node node) {
Archive::User const user = node.attribute_value("user", Archive::User());
Directory::Path path("depot/", user, "/pkg");
@ -239,11 +265,13 @@ struct Depot_query::Main
});
}
if (_blueprint_reporter.enabled()) {
Reporter::Xml_generator xml(_blueprint_reporter, [&] () {
if (_blueprint_reporter.constructed()) {
_blueprint_reporter->generate([&] (Xml_generator &xml) {
query.for_each_sub_node("blueprint", [&] (Xml_node node) {
Archive::Path pkg = node.attribute_value("pkg", Archive::Path());
try { _query_blueprint(pkg, xml); }
catch (Xml_generator::Buffer_exceeded) {
throw; /* handled by 'generate' */ }
catch (...) {
warning("could not obtain blueprint for '", pkg, "'");
}
@ -251,8 +279,8 @@ struct Depot_query::Main
});
}
if (_dependencies_reporter.enabled()) {
Reporter::Xml_generator xml(_dependencies_reporter, [&] () {
if (_dependencies_reporter.constructed()) {
_dependencies_reporter->generate([&] (Xml_generator &xml) {
Dependencies dependencies(_heap, _depot_dir);
query.for_each_sub_node("dependencies", [&] (Xml_node node) {
@ -268,8 +296,8 @@ struct Depot_query::Main
});
}
if (_user_reporter.enabled()) {
Reporter::Xml_generator xml(_user_reporter, [&] () {
if (_user_reporter.constructed()) {
_user_reporter->generate([&] (Xml_generator &xml) {
query.for_each_sub_node("user", [&] (Xml_node node) {
_query_user(node.attribute_value("name", Archive::User()), xml); });
});