Merge pull request #72 from ifupdown-ng/feature/depgraph-rdepends
dependency graph: reverse-dependency coloring
This commit is contained in:
commit
e15f3ffbaa
9 changed files with 156 additions and 11 deletions
|
@ -67,9 +67,9 @@ print_interface_dot(struct lif_dict *collection, struct lif_interface *iface, st
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (parent != NULL)
|
if (parent != NULL)
|
||||||
printf("\"%s\" -> ", parent->ifname);
|
printf("\"%s (%zu)\" -> ", parent->ifname, parent->rdepends_count);
|
||||||
|
|
||||||
printf("\"%s\"", iface->ifname);
|
printf("\"%s (%zu)\"", iface->ifname, iface->rdepends_count);
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
|
@ -290,6 +290,12 @@ ifquery_main(int argc, char *argv[])
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lif_lifecycle_count_rdepends(&exec_opts, &collection) == -1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: could not validate dependency tree\n", argv0);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
/* --list --state is not allowed */
|
/* --list --state is not allowed */
|
||||||
if (listing && listing_stat)
|
if (listing && listing_stat)
|
||||||
generic_usage(self_applet, EXIT_FAILURE);
|
generic_usage(self_applet, EXIT_FAILURE);
|
||||||
|
|
|
@ -101,14 +101,16 @@ skip_interface(struct lif_interface *iface, const char *ifname)
|
||||||
|
|
||||||
if (up && iface->refcount > 0)
|
if (up && iface->refcount > 0)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: skipping %s (already configured), use --force to force configuration\n",
|
if (exec_opts.verbose)
|
||||||
|
fprintf(stderr, "%s: skipping auto interface %s (already configured), use --force to force configuration\n",
|
||||||
argv0, ifname);
|
argv0, ifname);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!up && iface->refcount == 0)
|
if (!up && iface->refcount == 0)
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: skipping %s (already deconfigured), use --force to force deconfiguration\n",
|
if (exec_opts.verbose)
|
||||||
|
fprintf(stderr, "%s: skipping auto interface %s (already deconfigured), use --force to force deconfiguration\n",
|
||||||
argv0, ifname);
|
argv0, ifname);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -229,6 +231,12 @@ ifupdown_main(int argc, char *argv[])
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lif_lifecycle_count_rdepends(&exec_opts, &collection) == -1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%s: could not validate dependency tree\n", argv0);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
if (!lif_state_sync(&state, &collection))
|
if (!lif_state_sync(&state, &collection))
|
||||||
{
|
{
|
||||||
fprintf(stderr, "%s: could not sync state\n", argv0);
|
fprintf(stderr, "%s: could not sync state\n", argv0);
|
||||||
|
|
|
@ -53,7 +53,8 @@ struct lif_interface {
|
||||||
|
|
||||||
struct lif_dict vars;
|
struct lif_dict vars;
|
||||||
|
|
||||||
size_t refcount; /* >= 0 if up, else 0 */
|
size_t refcount; /* > 0 if up, else 0 */
|
||||||
|
size_t rdepends_count; /* > 0 if any reverse dependency */
|
||||||
};
|
};
|
||||||
|
|
||||||
#define LIF_INTERFACE_COLLECTION_FOREACH(iter, collection) \
|
#define LIF_INTERFACE_COLLECTION_FOREACH(iter, collection) \
|
||||||
|
|
|
@ -339,9 +339,6 @@ lif_lifecycle_run(const struct lif_execute_opts *opts, struct lif_interface *ifa
|
||||||
if (lifname == NULL)
|
if (lifname == NULL)
|
||||||
lifname = iface->ifname;
|
lifname = iface->ifname;
|
||||||
|
|
||||||
if (!lif_lifecycle_query_dependents(opts, iface, lifname))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (up)
|
if (up)
|
||||||
{
|
{
|
||||||
/* when going up, dependents go up first. */
|
/* when going up, dependents go up first. */
|
||||||
|
@ -389,3 +386,102 @@ lif_lifecycle_run(const struct lif_execute_opts *opts, struct lif_interface *ifa
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
count_interface_rdepends(const struct lif_execute_opts *opts, struct lif_dict *collection, struct lif_interface *parent, size_t depth)
|
||||||
|
{
|
||||||
|
/* query our dependents if we don't have them already */
|
||||||
|
if (!lif_lifecycle_query_dependents(opts, parent, parent->ifname))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* set rdepends_count to depth, dependents will be depth + 1 */
|
||||||
|
parent->rdepends_count = depth;
|
||||||
|
|
||||||
|
struct lif_dict_entry *requires = lif_dict_find(&parent->vars, "requires");
|
||||||
|
|
||||||
|
/* no dependents, nothing to worry about */
|
||||||
|
if (requires == NULL)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
/* walk any dependents */
|
||||||
|
char require_ifs[4096] = {};
|
||||||
|
strlcpy(require_ifs, requires->data, sizeof require_ifs);
|
||||||
|
char *bufp = require_ifs;
|
||||||
|
|
||||||
|
for (char *tokenp = lif_next_token(&bufp); *tokenp; tokenp = lif_next_token(&bufp))
|
||||||
|
{
|
||||||
|
struct lif_interface *iface = lif_interface_collection_find(collection, tokenp);
|
||||||
|
|
||||||
|
if (!count_interface_rdepends(opts, collection, iface, depth + 1))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
lif_lifecycle_count_rdepends(const struct lif_execute_opts *opts, struct lif_dict *collection)
|
||||||
|
{
|
||||||
|
struct lif_node *iter;
|
||||||
|
|
||||||
|
LIF_DICT_FOREACH(iter, collection)
|
||||||
|
{
|
||||||
|
struct lif_dict_entry *entry = iter->data;
|
||||||
|
struct lif_interface *iface = entry->data;
|
||||||
|
|
||||||
|
/* start depth at interface's rdepends_count, which will be 0 for the root,
|
||||||
|
* but will be more if additional rdepends are found...
|
||||||
|
*/
|
||||||
|
if (!count_interface_rdepends(opts, collection, iface, iface->rdepends_count))
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ifupdown: dependency graph is broken for interface %s\n", iface->ifname);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* figure out the max depth */
|
||||||
|
size_t maxdepth = 0;
|
||||||
|
|
||||||
|
LIF_DICT_FOREACH(iter, collection)
|
||||||
|
{
|
||||||
|
struct lif_dict_entry *entry = iter->data;
|
||||||
|
struct lif_interface *iface = entry->data;
|
||||||
|
|
||||||
|
if (iface->rdepends_count > maxdepth)
|
||||||
|
maxdepth = iface->rdepends_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* move the collection to a temporary list so we can reorder it */
|
||||||
|
struct lif_list temp_list = {};
|
||||||
|
struct lif_node *iter_next;
|
||||||
|
|
||||||
|
LIF_LIST_FOREACH_SAFE(iter, iter_next, collection->list.head)
|
||||||
|
{
|
||||||
|
void *data = iter->data;
|
||||||
|
|
||||||
|
lif_node_delete(iter, &collection->list);
|
||||||
|
memset(iter, 0, sizeof *iter);
|
||||||
|
|
||||||
|
lif_node_insert(iter, data, &temp_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* walk backwards from maxdepth to 0, readding nodes */
|
||||||
|
for (ssize_t curdepth = maxdepth; curdepth > -1; curdepth--)
|
||||||
|
{
|
||||||
|
LIF_LIST_FOREACH_SAFE(iter, iter_next, temp_list.head)
|
||||||
|
{
|
||||||
|
struct lif_dict_entry *entry = iter->data;
|
||||||
|
struct lif_interface *iface = entry->data;
|
||||||
|
|
||||||
|
if ((ssize_t) iface->rdepends_count != curdepth)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
lif_node_delete(iter, &temp_list);
|
||||||
|
memset(iter, 0, sizeof *iter);
|
||||||
|
|
||||||
|
lif_node_insert(iter, entry, &collection->list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxdepth;
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
extern bool lif_lifecycle_query_dependents(const struct lif_execute_opts *opts, struct lif_interface *iface, const char *lifname);
|
extern bool lif_lifecycle_query_dependents(const struct lif_execute_opts *opts, struct lif_interface *iface, const char *lifname);
|
||||||
extern bool lif_lifecycle_run_phase(const struct lif_execute_opts *opts, struct lif_interface *iface, const char *phase, const char *lifname, bool up);
|
extern bool lif_lifecycle_run_phase(const struct lif_execute_opts *opts, struct lif_interface *iface, const char *phase, const char *lifname, bool up);
|
||||||
extern bool lif_lifecycle_run(const struct lif_execute_opts *opts, struct lif_interface *iface, struct lif_dict *collection, struct lif_dict *state, const char *lifname, bool up);
|
extern bool lif_lifecycle_run(const struct lif_execute_opts *opts, struct lif_interface *iface, struct lif_dict *collection, struct lif_dict *state, const char *lifname, bool up);
|
||||||
|
extern ssize_t lif_lifecycle_count_rdepends(const struct lif_execute_opts *opts, struct lif_dict *collection);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
3
tests/fixtures/teardown-dep-ordering.ifstate
vendored
Normal file
3
tests/fixtures/teardown-dep-ordering.ifstate
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
dummy=dummy 2
|
||||||
|
bat=bat 2
|
||||||
|
br=br 1
|
13
tests/fixtures/teardown-dep-ordering.interfaces
vendored
Normal file
13
tests/fixtures/teardown-dep-ordering.interfaces
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
auto dummy
|
||||||
|
iface dummy
|
||||||
|
link-type dummy
|
||||||
|
|
||||||
|
auto bat
|
||||||
|
iface bat
|
||||||
|
link-type dummy
|
||||||
|
requires dummy
|
||||||
|
|
||||||
|
auto br
|
||||||
|
iface br
|
||||||
|
link-type dummy
|
||||||
|
requires bat
|
|
@ -22,6 +22,7 @@ tests_init \
|
||||||
deferred_teardown_1 \
|
deferred_teardown_1 \
|
||||||
deferred_teardown_2 \
|
deferred_teardown_2 \
|
||||||
deferred_teardown_3 \
|
deferred_teardown_3 \
|
||||||
|
teardown_dep_ordering \
|
||||||
regress_opt_f
|
regress_opt_f
|
||||||
|
|
||||||
noargs_body() {
|
noargs_body() {
|
||||||
|
@ -172,6 +173,14 @@ deferred_teardown_3_body() {
|
||||||
-i $FIXTURES/deferred-teardown-2.interfaces tun0 tun1 tun2 tun3
|
-i $FIXTURES/deferred-teardown-2.interfaces tun0 tun1 tun2 tun3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
teardown_dep_ordering_body() {
|
||||||
|
atf_check -s exit:0 -o ignore \
|
||||||
|
-e match:"skipping auto interface bat" \
|
||||||
|
-e match:"skipping auto interface dummy" \
|
||||||
|
ifdown -n -i $FIXTURES/teardown-dep-ordering.interfaces \
|
||||||
|
-S $FIXTURES/teardown-dep-ordering.ifstate -E $EXECUTORS -a
|
||||||
|
}
|
||||||
|
|
||||||
regress_opt_f_body() {
|
regress_opt_f_body() {
|
||||||
atf_check -s exit:0 -o ignore -e ignore \
|
atf_check -s exit:0 -o ignore -e ignore \
|
||||||
ifdown -n -S $FIXTURES/vlan.ifstate -E $EXECUTORS -i $FIXTURES/vlan.interfaces -f eth0.8
|
ifdown -n -S $FIXTURES/vlan.ifstate -E $EXECUTORS -i $FIXTURES/vlan.interfaces -f eth0.8
|
||||||
|
|
|
@ -18,7 +18,8 @@ tests_init \
|
||||||
learned_dependency \
|
learned_dependency \
|
||||||
learned_dependency_2 \
|
learned_dependency_2 \
|
||||||
learned_executor \
|
learned_executor \
|
||||||
implicit_vlan
|
implicit_vlan \
|
||||||
|
teardown_dep_ordering
|
||||||
|
|
||||||
noargs_body() {
|
noargs_body() {
|
||||||
atf_check -s exit:1 -e ignore ifup -S/dev/null
|
atf_check -s exit:1 -e ignore ifup -S/dev/null
|
||||||
|
@ -131,3 +132,10 @@ implicit_vlan_body() {
|
||||||
-e match:"attempting to run link executor" \
|
-e match:"attempting to run link executor" \
|
||||||
ifup -n -S/dev/null -E $EXECUTORS -i $FIXTURES/vlan.interfaces eth0.8
|
ifup -n -S/dev/null -E $EXECUTORS -i $FIXTURES/vlan.interfaces eth0.8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
teardown_dep_ordering_body() {
|
||||||
|
atf_check -s exit:0 -o ignore \
|
||||||
|
-e match:"skipping auto interface bat" \
|
||||||
|
-e match:"skipping auto interface dummy" \
|
||||||
|
ifup -n -i $FIXTURES/teardown-dep-ordering.interfaces -E $EXECUTORS -a
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue