Merge pull request #85 from ifupdown-ng/feature/dependency-loop-breaking

lifecycle: add lif_interface.is_pending to break dependency cycles
This commit is contained in:
Ariadne Conill 2020-10-10 22:42:34 -04:00 committed by GitHub
commit 172daa16a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 57 additions and 6 deletions

View file

@ -63,9 +63,6 @@ print_interface_dot(struct lif_dict *collection, struct lif_interface *iface, st
if (!lif_lifecycle_query_dependents(&exec_opts, iface, iface->ifname)) if (!lif_lifecycle_query_dependents(&exec_opts, iface, iface->ifname))
return; return;
if (iface->refcount > 0)
return;
if (parent != NULL) if (parent != NULL)
printf("\"%s (%zu)\" -> ", parent->ifname, parent->rdepends_count); printf("\"%s (%zu)\" -> ", parent->ifname, parent->rdepends_count);
@ -86,8 +83,12 @@ print_interface_dot(struct lif_dict *collection, struct lif_interface *iface, st
{ {
struct lif_interface *child_if = lif_interface_collection_find(collection, tokenp); struct lif_interface *child_if = lif_interface_collection_find(collection, tokenp);
if (child_if->is_pending)
continue;
child_if->is_pending = true;
print_interface_dot(collection, child_if, iface); print_interface_dot(collection, child_if, iface);
child_if->refcount++; child_if->is_pending = false;
} }
} }

View file

@ -52,6 +52,7 @@ struct lif_interface {
bool is_bridge; bool is_bridge;
bool is_bond; bool is_bond;
bool is_template; bool is_template;
bool is_pending;
bool has_config_error; /* error found in interface configuration */ bool has_config_error; /* error found in interface configuration */

View file

@ -359,6 +359,9 @@ handle_dependents(const struct lif_execute_opts *opts, struct lif_interface *par
if (requires == NULL) if (requires == NULL)
return true; return true;
/* set the parent's pending flag to break dependency cycles */
parent->is_pending = true;
char require_ifs[4096] = {}; char require_ifs[4096] = {};
strlcpy(require_ifs, requires->data, sizeof require_ifs); strlcpy(require_ifs, requires->data, sizeof require_ifs);
char *bufp = require_ifs; char *bufp = require_ifs;
@ -399,15 +402,23 @@ handle_dependents(const struct lif_execute_opts *opts, struct lif_interface *par
iface->ifname, parent->ifname, up ? "up" : "down"); iface->ifname, parent->ifname, up ? "up" : "down");
if (!lif_lifecycle_run(opts, iface, collection, state, iface->ifname, up)) if (!lif_lifecycle_run(opts, iface, collection, state, iface->ifname, up))
{
parent->is_pending = false;
return false; return false;
} }
}
parent->is_pending = false;
return true; return true;
} }
bool 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) 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)
{ {
/* if we're already pending, exit */
if (iface->is_pending)
return true;
if (iface->is_template) if (iface->is_template)
return false; return false;
@ -465,18 +476,26 @@ lif_lifecycle_run(const struct lif_execute_opts *opts, struct lif_interface *ifa
static bool static bool
count_interface_rdepends(const struct lif_execute_opts *opts, struct lif_dict *collection, struct lif_interface *parent, size_t depth) count_interface_rdepends(const struct lif_execute_opts *opts, struct lif_dict *collection, struct lif_interface *parent, size_t depth)
{ {
/* if we have looped, return true immediately to break the loop. */
if (parent->is_pending)
return true;
/* query our dependents if we don't have them already */ /* query our dependents if we don't have them already */
if (!lif_lifecycle_query_dependents(opts, parent, parent->ifname)) if (!lif_lifecycle_query_dependents(opts, parent, parent->ifname))
return false; return false;
/* set rdepends_count to depth, dependents will be depth + 1 */ /* set rdepends_count to depth, dependents will be depth + 1 */
parent->is_pending = true;
parent->rdepends_count = depth; parent->rdepends_count = depth;
struct lif_dict_entry *requires = lif_dict_find(&parent->vars, "requires"); struct lif_dict_entry *requires = lif_dict_find(&parent->vars, "requires");
/* no dependents, nothing to worry about */ /* no dependents, nothing to worry about */
if (requires == NULL) if (requires == NULL)
{
parent->is_pending = false;
return true; return true;
}
/* walk any dependents */ /* walk any dependents */
char require_ifs[4096] = {}; char require_ifs[4096] = {};
@ -488,9 +507,13 @@ count_interface_rdepends(const struct lif_execute_opts *opts, struct lif_dict *c
struct lif_interface *iface = lif_interface_collection_find(collection, tokenp); struct lif_interface *iface = lif_interface_collection_find(collection, tokenp);
if (!count_interface_rdepends(opts, collection, iface, depth + 1)) if (!count_interface_rdepends(opts, collection, iface, depth + 1))
{
parent->is_pending = false;
return false; return false;
} }
}
parent->is_pending = false;
return true; return true;
} }

View file

@ -0,0 +1,3 @@
lo=lo
a=a
b=b

View file

@ -0,0 +1,9 @@
auto a
iface a
use link
requires b
auto b
iface b
use link
requires a

View file

@ -23,7 +23,8 @@ tests_init \
deferred_teardown_2 \ deferred_teardown_2 \
deferred_teardown_3 \ deferred_teardown_3 \
teardown_dep_ordering \ teardown_dep_ordering \
regress_opt_f regress_opt_f \
dependency_loop_breaking
noargs_body() { noargs_body() {
atf_check -s exit:1 -e ignore ifdown -S/dev/null atf_check -s exit:1 -e ignore ifdown -S/dev/null
@ -185,3 +186,9 @@ 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
} }
dependency_loop_breaking_body() {
atf_check -s exit:0 -o ignore \
-e match:"ifdown: skipping auto interface a \\(already deconfigured\\), use --force to force deconfiguration" \
ifdown -n -i $FIXTURES/dependency-loop.interfaces -E $EXECUTORS -a
}

View file

@ -19,7 +19,8 @@ tests_init \
learned_dependency_2 \ learned_dependency_2 \
learned_executor \ learned_executor \
implicit_vlan \ implicit_vlan \
teardown_dep_ordering teardown_dep_ordering \
dependency_loop_breaking
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
@ -139,3 +140,9 @@ teardown_dep_ordering_body() {
-e match:"skipping auto interface dummy" \ -e match:"skipping auto interface dummy" \
ifup -n -i $FIXTURES/teardown-dep-ordering.interfaces -E $EXECUTORS -a ifup -n -i $FIXTURES/teardown-dep-ordering.interfaces -E $EXECUTORS -a
} }
dependency_loop_breaking_body() {
atf_check -s exit:0 -o ignore \
-e match:"ifup: skipping auto interface a \\(already configured\\), use --force to force configuration" \
ifup -n -i $FIXTURES/dependency-loop.interfaces -E $EXECUTORS -a
}