Merge ~netplan-developers/netplan/+git/netplan-lp:cyphermox/forward-definition into ~netplan-developers/netplan/+git/netplan-lp:master

Proposed by Mathieu Trudel-Lapierre
Status: Merged
Merged at revision: e41215b24f43f5a67ba9f38bd6373a0f75405335
Proposed branch: ~netplan-developers/netplan/+git/netplan-lp:cyphermox/forward-definition
Merge into: ~netplan-developers/netplan/+git/netplan-lp:master
Diff against target: 586 lines (+389/-42)
4 files modified
src/parse.c (+116/-29)
src/parse.h (+6/-0)
tests/generate.py (+174/-13)
tests/integration.py (+93/-0)
Reviewer Review Type Date Requested Status
Tiago Stürmer Daitx (community) code review Approve
Review via email: mp+318939@code.launchpad.net

Description of the change

Add forward declaration support -- this means that the netplan parser needs to run multiple passes over a yaml file to first catch any identifiers that were "missing" (used before they were defined), then go through the yaml file again to piece everything together and take the steps that were skipped while the missing identifiers couldn't be matched to anything.

To post a comment you must log in.
Revision history for this message
Tiago Stürmer Daitx (tdaitx) :
review: Approve (code review)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/parse.c b/src/parse.c
index 345c8ad..cac70f9 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -50,6 +50,13 @@ GHashTable* netdefs;
50 * existing definition */50 * existing definition */
51GHashTable* ids_in_file;51GHashTable* ids_in_file;
5252
53/* List of "seen" ids not found in netdefs yet by the parser.
54 * These are removed when it exists in this list and we reach the point of
55 * creating a netdef for that id; so by the time we're done parsing the yaml
56 * document it should be empty. */
57GHashTable *missing_id;
58int missing_ids_found;
59
53/****************************************************60/****************************************************
54 * Loading and error handling61 * Loading and error handling
55 ****************************************************/62 ****************************************************/
@@ -137,6 +144,22 @@ scalar(const yaml_node_t* node)
137 return (const char*) node->data.scalar.value;144 return (const char*) node->data.scalar.value;
138}145}
139146
147static void
148add_missing_node(const yaml_node_t* node)
149{
150 missing_node* missing;
151
152 /* Let's capture the current netdef we were playing with along with the
153 * actual yaml_node_t that errors (that is an identifier not previously
154 * seen by the compiler). We can use it later to write an sensible error
155 * message and point the user in the right direction. */
156 missing = g_new0(missing_node, 1);
157 missing->netdef_id = cur_netdef->id;
158 missing->node = node;
159
160 g_debug("recording missing yaml_node_t %s", scalar(node));
161 g_hash_table_insert(missing_id, (gpointer)scalar(node), missing);
162}
140163
141/**164/**
142 * Check that node contains a valid ID/interface name. Raise GError if not.165 * Check that node contains a valid ID/interface name. Raise GError if not.
@@ -276,11 +299,11 @@ handle_netdef_id_ref(yaml_document_t* doc, yaml_node_t* node, const void* data,
276 net_definition* ref = NULL;299 net_definition* ref = NULL;
277300
278 ref = g_hash_table_lookup(netdefs, scalar(node));301 ref = g_hash_table_lookup(netdefs, scalar(node));
279 if (!ref)302 if (!ref) {
280 return yaml_error(node, error, "%s: interface %s is not defined",303 add_missing_node(node);
281 cur_netdef->id, scalar(node));304 } else {
282305 *((net_definition**) ((void*) cur_netdef + offset)) = ref;
283 *((net_definition**) ((void*) cur_netdef + offset)) = ref;306 }
284 return TRUE;307 return TRUE;
285}308}
286309
@@ -563,14 +586,15 @@ handle_interfaces(yaml_document_t* doc, yaml_node_t* node, const void* data, GEr
563586
564 assert_type(entry, YAML_SCALAR_NODE);587 assert_type(entry, YAML_SCALAR_NODE);
565 component = g_hash_table_lookup(netdefs, scalar(entry));588 component = g_hash_table_lookup(netdefs, scalar(entry));
566 if (!component)589 if (!component) {
567 return yaml_error(node, error, "%s: interface %s is not defined",590 add_missing_node(entry);
568 cur_netdef->id, scalar(entry));591 } else {
569 component_ref_ptr = ((char**) ((void*) component + GPOINTER_TO_UINT(data)));592 component_ref_ptr = ((char**) ((void*) component + GPOINTER_TO_UINT(data)));
570 if (*component_ref_ptr)593 if (*component_ref_ptr && *component_ref_ptr != cur_netdef->id)
571 return yaml_error(node, error, "%s: interface %s is already assigned to %s",594 return yaml_error(node, error, "%s: interface %s is already assigned to %s",
572 cur_netdef->id, scalar(entry), *component_ref_ptr);595 cur_netdef->id, scalar(entry), *component_ref_ptr);
573 *component_ref_ptr = cur_netdef->id;596 *component_ref_ptr = cur_netdef->id;
597 }
574 }598 }
575599
576 return TRUE;600 return TRUE;
@@ -720,22 +744,22 @@ handle_bridge_path_cost(yaml_document_t* doc, yaml_node_t* node, const void* dat
720 assert_type(value, YAML_SCALAR_NODE);744 assert_type(value, YAML_SCALAR_NODE);
721745
722 component = g_hash_table_lookup(netdefs, scalar(key));746 component = g_hash_table_lookup(netdefs, scalar(key));
723 if (!component)747 if (!component) {
724 return yaml_error(node, error, "%s: interface %s is not defined",748 add_missing_node(key);
725 cur_netdef->id, scalar(key));749 } else {
726750 ref_ptr = ((guint*) ((void*) component + GPOINTER_TO_UINT(data)));
727 ref_ptr = ((guint*) ((void*) component + GPOINTER_TO_UINT(data)));751 if (*ref_ptr)
728 if (*ref_ptr)752 return yaml_error(node, error, "%s: interface %s already has a path cost of %u",
729 return yaml_error(node, error, "%s: interface %s already has a path cost of %u",753 cur_netdef->id, scalar(key), *ref_ptr);
730 cur_netdef->id, scalar(key), *ref_ptr);
731754
732 v = g_ascii_strtoull(scalar(value), &endptr, 10);755 v = g_ascii_strtoull(scalar(value), &endptr, 10);
733 if (*endptr != '\0' || v > G_MAXUINT)756 if (*endptr != '\0' || v > G_MAXUINT)
734 return yaml_error(node, error, "invalid unsigned int value %s", scalar(value));757 return yaml_error(node, error, "invalid unsigned int value %s", scalar(value));
735758
736 g_debug("%s: adding path '%s' of cost: %d", cur_netdef->id, scalar(key), v);759 g_debug("%s: adding path '%s' of cost: %d", cur_netdef->id, scalar(key), v);
737760
738 *ref_ptr = v;761 *ref_ptr = v;
762 }
739 }763 }
740 return TRUE;764 return TRUE;
741}765}
@@ -959,8 +983,15 @@ handle_network_renderer(yaml_document_t* doc, yaml_node_t* node, const void* _,
959static gboolean983static gboolean
960validate_netdef(net_definition* nd, yaml_node_t* node, GError** error)984validate_netdef(net_definition* nd, yaml_node_t* node, GError** error)
961{985{
986 int missing_id_count = g_hash_table_size(missing_id);
962 g_assert(nd->type != ND_NONE);987 g_assert(nd->type != ND_NONE);
963988
989 /* Skip all validation if we're missing some definition IDs (devices).
990 * The ones we have yet to see may be necessary for validation to succeed,
991 * we can complete it on the next parser pass. */
992 if (missing_id_count > 0)
993 return TRUE;
994
964 /* set-name: requires match: */995 /* set-name: requires match: */
965 if (nd->set_name && !nd->has_match)996 if (nd->set_name && !nd->has_match)
966 return yaml_error(node, error, "%s: set-name: requires match: properties", nd->id);997 return yaml_error(node, error, "%s: set-name: requires match: properties", nd->id);
@@ -1010,6 +1041,12 @@ handle_network_type(yaml_document_t* doc, yaml_node_t* node, const void* data, G
10101041
1011 assert_type(value, YAML_MAPPING_NODE);1042 assert_type(value, YAML_MAPPING_NODE);
10121043
1044 /* At this point we've seen a new starting definition, if it has been
1045 * already mentioned in another netdef, removing it from our "missing"
1046 * list. */
1047 if(g_hash_table_remove(missing_id, scalar(key)))
1048 missing_ids_found++;
1049
1013 cur_netdef = g_hash_table_lookup(netdefs, scalar(key));1050 cur_netdef = g_hash_table_lookup(netdefs, scalar(key));
1014 if (cur_netdef) {1051 if (cur_netdef) {
1015 /* already exists, overriding/amending previous definition */1052 /* already exists, overriding/amending previous definition */
@@ -1026,8 +1063,9 @@ handle_network_type(yaml_document_t* doc, yaml_node_t* node, const void* data, G
1026 g_hash_table_insert(netdefs, cur_netdef->id, cur_netdef);1063 g_hash_table_insert(netdefs, cur_netdef->id, cur_netdef);
1027 }1064 }
10281065
1029 if (!g_hash_table_add(ids_in_file, cur_netdef->id))1066 // XXX: breaks multi-pass parsing.
1030 return yaml_error(key, error, "Duplicate net definition ID '%s'", cur_netdef->id);1067 //if (!g_hash_table_add(ids_in_file, cur_netdef->id))
1068 // return yaml_error(key, error, "Duplicate net definition ID '%s'", cur_netdef->id);
10311069
1032 /* and fill it with definitions */1070 /* and fill it with definitions */
1033 switch (cur_netdef->type) {1071 switch (cur_netdef->type) {
@@ -1074,7 +1112,55 @@ const mapping_entry_handler root_handlers[] = {
1074 {NULL}1112 {NULL}
1075};1113};
10761114
1115/**
1116 * Handle multiple-pass parsing of the yaml document.
1117 */
1118static gboolean
1119process_document(yaml_document_t* doc, GError** error)
1120{
1121 gboolean ret;
1122 int previously_found;
1123 int still_missing;
1124
1125 g_assert(missing_id == NULL);
1126 missing_id = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
1127
1128 do {
1129 g_debug("starting new processing pass");
1130
1131 previously_found = missing_ids_found;
1132 missing_ids_found = 0;
1133
1134 ret = process_mapping(doc, yaml_document_get_root_node(doc), root_handlers, error);
1135
1136 still_missing = g_hash_table_size(missing_id);
1137
1138 if (still_missing > 0 && missing_ids_found == previously_found)
1139 break;
1140 } while (still_missing > 0 || missing_ids_found > 0);
10771141
1142 if (g_hash_table_size(missing_id) > 0) {
1143 GHashTableIter iter;
1144 gpointer key, value;
1145 missing_node *missing;
1146
1147 g_clear_error(error);
1148
1149 /* Get the first missing identifier we can get from our list, to
1150 * approximate early failure and give the user a meaningful error. */
1151 g_hash_table_iter_init (&iter, missing_id);
1152 g_hash_table_iter_next (&iter, &key, &value);
1153 missing = (missing_node*) value;
1154
1155 return yaml_error(missing->node, error, "%s: interface %s is not defined",
1156 missing->netdef_id,
1157 key);
1158 }
1159
1160 g_hash_table_destroy(missing_id);
1161 missing_id = NULL;
1162 return ret;
1163}
10781164
1079/**1165/**
1080 * Parse given YAML file and create/update global "netdefs" list.1166 * Parse given YAML file and create/update global "netdefs" list.
@@ -1098,7 +1184,8 @@ parse_yaml(const char* filename, GError** error)
1098 g_assert(ids_in_file == NULL);1184 g_assert(ids_in_file == NULL);
1099 ids_in_file = g_hash_table_new(g_str_hash, NULL);1185 ids_in_file = g_hash_table_new(g_str_hash, NULL);
11001186
1101 ret = process_mapping(&doc, yaml_document_get_root_node(&doc), root_handlers, error);1187 ret = process_document(&doc, error);
1188
1102 cur_netdef = NULL;1189 cur_netdef = NULL;
1103 yaml_document_delete(&doc);1190 yaml_document_delete(&doc);
1104 g_hash_table_destroy(ids_in_file);1191 g_hash_table_destroy(ids_in_file);
diff --git a/src/parse.h b/src/parse.h
index 75d6531..f2e9c81 100644
--- a/src/parse.h
+++ b/src/parse.h
@@ -18,6 +18,7 @@
18#pragma once18#pragma once
1919
20#include <uuid.h>20#include <uuid.h>
21#include <yaml.h>
2122
22/****************************************************23/****************************************************
23 * Parsed definitions24 * Parsed definitions
@@ -41,6 +42,11 @@ typedef enum {
41 BACKEND_NM,42 BACKEND_NM,
42} netdef_backend;43} netdef_backend;
4344
45typedef struct missing_node {
46 char* netdef_id;
47 yaml_node_t* node;
48} missing_node;
49
44/**50/**
45 * Represent a configuration stanza51 * Represent a configuration stanza
46 */52 */
diff --git a/tests/generate.py b/tests/generate.py
index e168283..179b4de 100755
--- a/tests/generate.py
+++ b/tests/generate.py
@@ -777,6 +777,27 @@ Address=1.2.3.4/12
777unmanaged-devices+=interface-name:br0,''')777unmanaged-devices+=interface-name:br0,''')
778 self.assert_udev(None)778 self.assert_udev(None)
779779
780 def test_bridge_forward_declaration(self):
781 self.generate('''network:
782 version: 2
783 bridges:
784 br0:
785 interfaces: [eno1, switchports]
786 dhcp4: true
787 ethernets:
788 eno1: {}
789 switchports:
790 match:
791 driver: yayroute
792''')
793
794 self.assert_networkd({'br0.netdev': '[NetDev]\nName=br0\nKind=bridge\n',
795 'br0.network': ND_DHCP4 % 'br0',
796 'eno1.network': '[Match]\nName=eno1\n\n'
797 '[Network]\nBridge=br0\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n',
798 'switchports.network': '[Match]\nDriver=yayroute\n\n'
799 '[Network]\nBridge=br0\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n'})
800
780 def test_bridge_components(self):801 def test_bridge_components(self):
781 self.generate('''network:802 self.generate('''network:
782 version: 2803 version: 2
@@ -1836,6 +1857,58 @@ address1=1.2.3.4/12
1836 self.assert_networkd({})1857 self.assert_networkd({})
1837 self.assert_udev(None)1858 self.assert_udev(None)
18381859
1860 def test_bridge_forward_declaration(self):
1861 self.generate('''network:
1862 version: 2
1863 renderer: NetworkManager
1864 bridges:
1865 br0:
1866 interfaces: [eno1, switchport]
1867 dhcp4: true
1868 ethernets:
1869 eno1: {}
1870 switchport:
1871 match:
1872 name: enp2s1
1873''')
1874
1875 self.assert_nm({'eno1': '''[connection]
1876id=netplan-eno1
1877type=ethernet
1878interface-name=eno1
1879slave-type=bridge
1880master=br0
1881
1882[ethernet]
1883wake-on-lan=0
1884
1885[ipv4]
1886method=link-local
1887''',
1888 'switchport': '''[connection]
1889id=netplan-switchport
1890type=ethernet
1891interface-name=enp2s1
1892slave-type=bridge
1893master=br0
1894
1895[ethernet]
1896wake-on-lan=0
1897
1898[ipv4]
1899method=link-local
1900''',
1901 'br0': '''[connection]
1902id=netplan-br0
1903type=bridge
1904interface-name=br0
1905
1906[ipv4]
1907method=auto
1908'''})
1909 self.assert_networkd({})
1910 self.assert_udev(None)
1911
1839 def test_bridge_components(self):1912 def test_bridge_components(self):
1840 self.generate('''network:1913 self.generate('''network:
1841 version: 21914 version: 2
@@ -2384,17 +2457,6 @@ class TestConfigErrors(TestBase):
2384 err = self.generate('network:\n version: 1', expect_fail=True)2457 err = self.generate('network:\n version: 1', expect_fail=True)
2385 self.assertIn('/a.yaml line 1 column 11: Only version 2 is supported', err)2458 self.assertIn('/a.yaml line 1 column 11: Only version 2 is supported', err)
23862459
2387 def test_duplicate_id(self):
2388 err = self.generate('''network:
2389 version: 2
2390 ethernets:
2391 id0:
2392 wakeonlan: true
2393 id0:
2394 wakeonlan: true
2395''', expect_fail=True)
2396 self.assertIn("Duplicate net definition ID 'id0'", err)
2397
2398 def test_id_redef_type_mismatch(self):2460 def test_id_redef_type_mismatch(self):
2399 err = self.generate('''network:2461 err = self.generate('''network:
2400 version: 22462 version: 2
@@ -2448,7 +2510,7 @@ class TestConfigErrors(TestBase):
2448 bridges:2510 bridges:
2449 br0:2511 br0:
2450 interfaces: ['foo']''', expect_fail=True)2512 interfaces: ['foo']''', expect_fail=True)
2451 self.assertIn('/a.yaml line 4 column 18: br0: interface foo is not defined\n', err)2513 self.assertIn('/a.yaml line 4 column 19: br0: interface foo is not defined\n', err)
24522514
2453 def test_bridge_multiple_assignments(self):2515 def test_bridge_multiple_assignments(self):
2454 err = self.generate('''network:2516 err = self.generate('''network:
@@ -2717,7 +2779,7 @@ class TestConfigErrors(TestBase):
2717 version: 22779 version: 2
2718 vlans:2780 vlans:
2719 ena: {id: 1, link: en1}''', expect_fail=True)2781 ena: {id: 1, link: en1}''', expect_fail=True)
2720 self.assertIn('interface en1 is not defined\n', err)2782 self.assertIn('ena: interface en1 is not defined\n', err)
27212783
2722 def test_device_bad_route_to(self):2784 def test_device_bad_route_to(self):
2723 self.generate('''network:2785 self.generate('''network:
@@ -2870,6 +2932,105 @@ class TestConfigErrors(TestBase):
2870 dhcp4: true''', expect_fail=True)2932 dhcp4: true''', expect_fail=True)
28712933
28722934
2935class TestForwardDeclaration(TestBase):
2936
2937 def test_fwdecl_bridge_on_bond(self):
2938 self.generate('''network:
2939 version: 2
2940 bridges:
2941 br0:
2942 interfaces: ['bond0']
2943 dhcp4: true
2944 bonds:
2945 bond0:
2946 interfaces: ['eth0', 'eth1']
2947 ethernets:
2948 eth0:
2949 match:
2950 macaddress: 00:01:02:03:04:05
2951 set-name: eth0
2952 eth1:
2953 match:
2954 macaddress: 02:01:02:03:04:05
2955 set-name: eth1
2956''')
2957
2958 self.assert_networkd({'br0.netdev': '[NetDev]\nName=br0\nKind=bridge\n',
2959 'br0.network': ND_DHCP4 % 'br0',
2960 'bond0.netdev': '[NetDev]\nName=bond0\nKind=bond\n',
2961 'bond0.network': '[Match]\nName=bond0\n\n'
2962 '[Network]\nBridge=br0\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n',
2963 'eth0.link': '[Match]\nMACAddress=00:01:02:03:04:05\n\n'
2964 '[Link]\nName=eth0\nWakeOnLan=off\n',
2965 'eth0.network': '[Match]\nMACAddress=00:01:02:03:04:05\nName=eth0\n\n'
2966 '[Network]\nBond=bond0\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n',
2967 'eth1.link': '[Match]\nMACAddress=02:01:02:03:04:05\n\n'
2968 '[Link]\nName=eth1\nWakeOnLan=off\n',
2969 'eth1.network': '[Match]\nMACAddress=02:01:02:03:04:05\nName=eth1\n\n'
2970 '[Network]\nBond=bond0\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n'})
2971
2972 def test_fwdecl_feature_blend(self):
2973 self.generate('''network:
2974 version: 2
2975 vlans:
2976 vlan1:
2977 link: 'br0'
2978 id: 1
2979 dhcp4: true
2980 bridges:
2981 br0:
2982 interfaces: ['bond0', 'eth2']
2983 parameters:
2984 path-cost:
2985 eth2: 1000
2986 bond0: 8888
2987 bonds:
2988 bond0:
2989 interfaces: ['eth0', 'br1']
2990 ethernets:
2991 eth0:
2992 match:
2993 macaddress: 00:01:02:03:04:05
2994 set-name: eth0
2995 bridges:
2996 br1:
2997 interfaces: ['eth1']
2998 ethernets:
2999 eth1:
3000 match:
3001 macaddress: 02:01:02:03:04:05
3002 set-name: eth1
3003 eth2:
3004 match:
3005 name: eth2
3006''')
3007
3008 self.assert_networkd({'vlan1.netdev': '[NetDev]\nName=vlan1\nKind=vlan\n\n'
3009 '[VLAN]\nId=1\n',
3010 'vlan1.network': ND_DHCP4 % 'vlan1',
3011 'br0.netdev': '[NetDev]\nName=br0\nKind=bridge\n',
3012 'br0.network': '[Match]\nName=br0\n\n'
3013 '[Network]\nVLAN=vlan1\n',
3014 'bond0.netdev': '[NetDev]\nName=bond0\nKind=bond\n',
3015 'bond0.network': '[Match]\nName=bond0\n\n'
3016 '[Network]\nBridge=br0\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n\n'
3017 '[Bridge]\nCost=8888\n',
3018 'eth2.network': '[Match]\nName=eth2\n\n'
3019 '[Network]\nBridge=br0\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n\n'
3020 '[Bridge]\nCost=1000\n',
3021 'br1.netdev': '[NetDev]\nName=br1\nKind=bridge\n',
3022 'br1.network': '[Match]\nName=br1\n\n'
3023 '[Network]\nBond=bond0\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n',
3024 'eth0.link': '[Match]\nMACAddress=00:01:02:03:04:05\n\n'
3025 '[Link]\nName=eth0\nWakeOnLan=off\n',
3026 'eth0.network': '[Match]\nMACAddress=00:01:02:03:04:05\nName=eth0\n\n'
3027 '[Network]\nBond=bond0\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n',
3028 'eth1.link': '[Match]\nMACAddress=02:01:02:03:04:05\n\n'
3029 '[Link]\nName=eth1\nWakeOnLan=off\n',
3030 'eth1.network': '[Match]\nMACAddress=02:01:02:03:04:05\nName=eth1\n\n'
3031 '[Network]\nBridge=br1\nLinkLocalAddressing=no\nIPv6AcceptRA=no\n'})
3032
3033
2873class TestMerging(TestBase):3034class TestMerging(TestBase):
2874 '''multiple *.yaml merging'''3035 '''multiple *.yaml merging'''
28753036
diff --git a/tests/integration.py b/tests/integration.py
index b1bfbcc..6fe97f6 100755
--- a/tests/integration.py
+++ b/tests/integration.py
@@ -1147,6 +1147,99 @@ wpa_passphrase=12345678
1147 universal_newlines=True)1147 universal_newlines=True)
1148 self.assertRegex(out, 'DNS.*192.168.5.1')1148 self.assertRegex(out, 'DNS.*192.168.5.1')
11491149
1150 def test_mix_bridge_on_bond(self):
1151 self.setup_eth(None)
1152 self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'bond0'], stderr=subprocess.DEVNULL)
1153 self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'br0'], stderr=subprocess.DEVNULL)
1154 self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'br1'], stderr=subprocess.DEVNULL)
1155 with open(self.config, 'w') as f:
1156 f.write('''network:
1157 renderer: %(r)s
1158 bridges:
1159 br0:
1160 interfaces: [bond0]
1161 dhcp4: true
1162 bonds:
1163 bond0:
1164 interfaces: [ethbn, ethb2]
1165 parameters:
1166 mii-monitor-interval: 100000
1167 ethernets:
1168 ethbn:
1169 match: {name: %(ec)s}
1170 ethb2:
1171 match: {name: %(e2c)s}
1172''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client})
1173 self.generate_and_settle()
1174 self.assert_iface_up(self.dev_e_client,
1175 ['master bond0'],
1176 ['inet '])
1177 self.assert_iface_up(self.dev_e2_client,
1178 ['master bond0'],
1179 ['inet '])
1180 self.assert_iface_up('bond0',
1181 ['master br0'])
1182 self.assert_iface_up('br0',
1183 ['inet 192.168'])
1184 with open('/sys/class/net/bond0/bonding/slaves') as f:
1185 result = f.read().strip()
1186 self.assertIn(self.dev_e_client, result)
1187 self.assertIn(self.dev_e2_client, result)
1188
1189 def test_mix_vlan_on_bridge_on_bond(self):
1190 self.setup_eth(None)
1191 self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'bond0'], stderr=subprocess.DEVNULL)
1192 self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'br0'], stderr=subprocess.DEVNULL)
1193 self.addCleanup(subprocess.call, ['ip', 'link', 'delete', 'br1'], stderr=subprocess.DEVNULL)
1194 with open(self.config, 'w') as f:
1195 f.write('''network:
1196 renderer: %(r)s
1197 version: 2
1198 vlans:
1199 vlan1:
1200 link: 'br0'
1201 id: 1
1202 addresses: [ '10.10.10.1/24' ]
1203 bridges:
1204 br0:
1205 interfaces: ['bond0', 'vlan2']
1206 parameters:
1207 path-cost:
1208 bond0: 1000
1209 vlan2: 2000
1210 bonds:
1211 bond0:
1212 interfaces: ['br1']
1213 parameters:
1214 mii-monitor-interval: 100000
1215 bridges:
1216 br1:
1217 interfaces: ['ethb2']
1218 vlans:
1219 vlan2:
1220 link: ethbn
1221 id: 2
1222 ethernets:
1223 ethbn:
1224 match: {name: %(ec)s}
1225 ethb2:
1226 match: {name: %(e2c)s}
1227''' % {'r': self.backend, 'ec': self.dev_e_client, 'e2c': self.dev_e2_client})
1228 self.generate_and_settle()
1229 self.assert_iface_up('vlan1', ['vlan1@br0'])
1230 self.assert_iface_up('vlan2',
1231 ['vlan2@' + self.dev_e_client, 'master br0'])
1232 self.assert_iface_up(self.dev_e2_client,
1233 ['master br1'],
1234 ['inet '])
1235 self.assert_iface_up('br1',
1236 ['master bond0'])
1237 self.assert_iface_up('bond0',
1238 ['master br0'])
1239 with open('/sys/class/net/bond0/bonding/slaves') as f:
1240 result = f.read().strip()
1241 self.assertIn('br1', result)
1242
11501243
1151class TestNetworkd(NetworkTestBase, _CommonTests):1244class TestNetworkd(NetworkTestBase, _CommonTests):
1152 backend = 'networkd'1245 backend = 'networkd'

Subscribers

People subscribed via source and target branches

to all changes: