From 15a1ca8b7ce95a6f2a03c264584b401a6b9ab997 Mon Sep 17 00:00:00 2001 From: Roland Pabel Date: Thu, 25 Jan 2018 16:45:46 +0100 Subject: [PATCH 1/2] add macvlan support --- README.md | 29 ++++ examples/macvlan.yml | 26 ++++ library/network_connections.py | 61 +++++++- library/test_network_connections.py | 212 ++++++++++++++++++++++++++++ 4 files changed, 324 insertions(+), 4 deletions(-) create mode 100644 examples/macvlan.yml diff --git a/README.md b/README.md index 2e940fb..0e9a258 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ The role can be used to configure: - Bridge interfaces - Bonded interfaces - VLAN interfaces +- MacVLAN interfaces - IP configuration General @@ -363,6 +364,34 @@ network_connections: Like for `master`, the `parent` references the connection profile in the ansible role. +### `type: macvlan` + +MACVLANs also work: + +```yaml +network_connections: + - name: eth0-profile + type: ethernet + interface_name: eth0 + ip: + address: + - 192.168.0.1/24 + + - name: veth0 + type: macvlan + parent: eth0-profile + macvlan: + mode: bridge + promiscuous: True + tap: False + ip: + address: + - 192.168.1.1/24 +``` + +Like for `master` and `vlan`, the `parent` references the connection profile in the ansible +role. + ### `network_provider` Whether to use `nm` or `initscripts` is detected based on the distribution. diff --git a/examples/macvlan.yml b/examples/macvlan.yml new file mode 100644 index 0000000..fbf11c2 --- /dev/null +++ b/examples/macvlan.yml @@ -0,0 +1,26 @@ +--- +- hosts: network-test + vars: + network_connections: + + - name: eth0 + type: ethernet + interface_name: eth0 + ip: + address: + - 192.168.0.1/24 + + # Create a virtual ethernet card bound to eth0 + - name: veth0 + type: macvlan + parent: eth0 + macvlan: + mode: bridge + promiscuous: True + tap: False + ip: + address: + - 192.168.1.1/24 + + roles: + - network diff --git a/library/network_connections.py b/library/network_connections.py index 90ce70f..54cf345 100755 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -851,11 +851,37 @@ class ArgValidator_DictVlan(ArgValidatorDict): 'id': None, } +class ArgValidator_DictMacvlan(ArgValidatorDict): + + VALID_MODES = ['vepa', 'bridge', 'private', 'passthru', 'source'] + + def __init__(self): + ArgValidatorDict.__init__(self, + name = 'macvlan', + nested = [ + ArgValidatorStr ('mode', enum_values = ArgValidator_DictMacvlan.VALID_MODES, default_value = 'bridge'), + ArgValidatorBool('promiscuous', default_value = True), + ArgValidatorBool('tap', default_value = False), + ], + default_value = ArgValidator.MISSING, + ) + + def get_default_macvlan(self): + return { + 'mode': 'bridge', + 'promiscuous': True, + 'tap': False, + } + + def _validate_post(self, value, name, result): + if result['promiscuous'] == False and result['mode'] != "passthru": + raise ValidationError(name, 'non promiscuous operation is allowed only in passthru mode') + return result class ArgValidator_DictConnection(ArgValidatorDict): VALID_STATES = ['up', 'down', 'present', 'absent', 'wait'] - VALID_TYPES = [ 'ethernet', 'infiniband', 'bridge', 'team', 'bond', 'vlan' ] + VALID_TYPES = [ 'ethernet', 'infiniband', 'bridge', 'team', 'bond', 'vlan', 'macvlan' ] VALID_SLAVE_TYPES = [ 'bridge', 'bond', 'team' ] def __init__(self): @@ -882,6 +908,7 @@ class ArgValidator_DictConnection(ArgValidatorDict): ArgValidator_DictBond(), ArgValidator_DictInfiniband(), ArgValidator_DictVlan(), + ArgValidator_DictMacvlan(), # deprecated options: ArgValidatorStr ('infiniband_transport_mode', enum_values = ['datagram', 'connected'], default_value = ArgValidator.MISSING), @@ -1003,7 +1030,7 @@ class ArgValidator_DictConnection(ArgValidatorDict): if not Util.ifname_valid(result['interface_name']): raise ValidationError(name + '.interface_name', 'invalid "interface_name" "%s"' % (result['interface_name'])) else: - if result['type'] in [ 'bridge', 'bond', 'team', 'vlan' ]: + if result['type'] in [ 'bridge', 'bond', 'team', 'vlan', 'macvlan' ]: if not Util.ifname_valid(result['name']): raise ValidationError(name + '.interface_name', 'requires "interface_name" as "name" "%s" is not valid' % (result['name'])) result['interface_name'] = result['name'] @@ -1027,8 +1054,8 @@ class ArgValidator_DictConnection(ArgValidatorDict): raise ValidationError(name + '.vlan_id', '"vlan_id" is only allowed for "type" "vlan"') if 'parent' in result: - if result['type'] not in ['vlan', 'infiniband']: - raise ValidationError(name + '.parent', '"parent" is only allowed for type "vlan" or "infiniband"') + if result['type'] not in ['vlan', 'macvlan', 'infiniband']: + raise ValidationError(name + '.parent', '"parent" is only allowed for type "vlan", "macvlan" or "infiniband"') if result['parent'] == result['name']: raise ValidationError(name + '.parent', '"parent" cannot refer to itself') @@ -1046,6 +1073,13 @@ class ArgValidator_DictConnection(ArgValidatorDict): if 'ethernet' in result: raise ValidationError(name + '.ethernet', '"ethernet" settings are not allowed for "type" "%s"' % (result['type'])) + if result['type'] == 'macvlan': + if 'macvlan' not in result: + result['macvlan'] = self.nested['macvlan'].get_default_macvlan() + else: + if 'macvlan' in result: + raise ValidationError(name + '.macvlan', '"macvlan" settings are not allowed for "type" "%s"' % (result['type'])) + for k in VALID_FIELDS: if k in result: continue @@ -1667,6 +1701,18 @@ class NMUtil: s_vlan = self.connection_ensure_setting(con, NM.SettingVlan) s_vlan.set_property(NM.SETTING_VLAN_ID, connection['vlan']['id']) s_vlan.set_property(NM.SETTING_VLAN_PARENT, ArgUtil.connection_find_master_uuid(connection['parent'], connections, idx)) + elif connection['type'] == 'macvlan': + # convert mode name to a number (which is actually expected by nm) + mode = connection['macvlan']['mode'] + try: + mode_id = int(getattr( NM.SettingMacvlanMode, mode.upper() )) + except AttributeError as e: + raise MyError('Macvlan mode "%s" is not recognized' % (mode)) + s_macvlan = self.connection_ensure_setting(con, NM.SettingMacvlan) + s_macvlan.set_property(NM.SETTING_MACVLAN_MODE, mode_id) + s_macvlan.set_property(NM.SETTING_MACVLAN_PROMISCUOUS, connection['macvlan']['promiscuous']) + s_macvlan.set_property(NM.SETTING_MACVLAN_TAP, connection['macvlan']['tap']) + s_macvlan.set_property(NM.SETTING_MACVLAN_PARENT, ArgUtil.connection_find_master(connection['parent'], connections, idx)) else: raise MyError('unsupported type %s' % (connection['type'])) @@ -2600,6 +2646,13 @@ class Cmd_initscripts(Cmd): Cmd.__init__(self, **kwargs) self.validate_one_type = ArgValidator_ListConnections.VALIDATE_ONE_MODE_INITSCRIPTS + def run_prepare(self): + Cmd.run_prepare(self) + names = {} + for idx, connection in enumerate(self.connections): + if connection['type'] in [ 'macvlan' ]: + self.log_fatal(idx, 'unsupported type %s for initscripts provider' % (connection['type'])) + def check_name(self, idx, name = None): if name is None: name = self.connections[idx]['name'] diff --git a/library/test_network_connections.py b/library/test_network_connections.py index 15297ca..a9bb60f 100755 --- a/library/test_network_connections.py +++ b/library/test_network_connections.py @@ -705,6 +705,218 @@ class TestValidator(unittest.TestCase): ], ) + self.do_connections_validate( + [ + { + 'autoconnect': True, + 'name': 'eth0-parent', + 'parent': None, + 'ip': { + 'dhcp4': False, + 'auto6': False, + 'address': [ + { + 'prefix': 24, + 'family': socket.AF_INET, + 'address': '192.168.122.3' + }, + ], + 'route_append_only': False, + 'rule_append_only': False, + 'route': [], + 'route_metric6': None, + 'route_metric4': None, + 'dns_search': [], + 'dhcp4_send_hostname': None, + 'gateway6': None, + 'gateway4': None, + 'dns': [] + }, + 'ethernet': { + 'autoneg': None, + 'duplex': None, + 'speed': 0, + }, + 'state': 'up', + 'mtu': 1450, + 'check_iface_exists': True, + 'force_state_change': None, + 'mac': '33:24:10:24:2f:b9', + 'zone': None, + 'master': None, + 'ignore_errors': None, + 'interface_name': "eth0", + 'type': 'ethernet', + 'slave_type': None, + 'wait': None, + }, + { + 'autoconnect': True, + 'name': 'veth0.0', + 'parent': 'eth0-parent', + 'ip': { + 'dhcp4': False, + 'route_metric6': None, + 'route_metric4': None, + 'dns_search': [], + 'dhcp4_send_hostname': None, + 'gateway6': None, + 'gateway4': None, + 'auto6': False, + 'dns': [], + 'address': [ + { + 'prefix': 24, + 'family': socket.AF_INET, + 'address': '192.168.244.1' + }, + ], + 'route_append_only': False, + 'rule_append_only': False, + 'route': [ + { + 'family': socket.AF_INET, + 'network': '192.168.244.0', + 'prefix': 24, + 'gateway': None, + 'metric': -1, + }, + ], + }, + 'mac': None, + 'mtu': None, + 'zone': None, + 'check_iface_exists': True, + 'force_state_change': None, + 'state': 'up', + 'master': None, + 'slave_type': None, + 'ignore_errors': None, + 'interface_name': 'veth0', + 'type': 'macvlan', + 'macvlan': { + 'mode' : 'bridge', + 'promiscuous': True, + 'tap': False, + }, + 'wait': None, + }, + { + 'autoconnect': True, + 'name': 'veth0.1', + 'parent': 'eth0-parent', + 'ip': { + 'dhcp4': False, + 'route_metric6': None, + 'route_metric4': None, + 'dns_search': [], + 'dhcp4_send_hostname': None, + 'gateway6': None, + 'gateway4': None, + 'auto6': False, + 'dns': [], + 'address': [ + { + 'prefix': 24, + 'family': socket.AF_INET, + 'address': '192.168.245.7' + }, + ], + 'route_append_only': False, + 'rule_append_only': False, + 'route': [ + { + 'family': socket.AF_INET, + 'network': '192.168.245.0', + 'prefix': 24, + 'gateway': None, + 'metric': -1, + }, + ], + }, + 'mac': None, + 'mtu': None, + 'zone': None, + 'check_iface_exists': True, + 'force_state_change': None, + 'state': 'up', + 'master': None, + 'slave_type': None, + 'ignore_errors': None, + 'interface_name': 'veth1', + 'type': 'macvlan', + 'macvlan': { + 'mode' : 'passthru', + 'promiscuous': False, + 'tap': True, + }, + 'wait': None, + } + ], + [ + { + 'name': 'eth0-parent', + 'state': 'up', + 'type': 'ethernet', + 'autoconnect': 'yes', + 'interface_name': 'eth0', + 'mac': '33:24:10:24:2f:b9', + 'mtu': 1450, + 'ip': { + 'address': '192.168.122.3/24', + 'auto6': False + }, + }, + { + 'name': 'veth0.0', + 'state': 'up', + 'type': 'macvlan', + 'parent': 'eth0-parent', + 'interface_name': 'veth0', + 'macvlan': { + 'mode': 'bridge', + 'promiscuous': True, + 'tap': False, + }, + 'ip': { + 'address': '192.168.244.1/24', + 'auto6': False, + 'route_append_only': False, + 'rule_append_only': False, + 'route': [ + { + 'network': '192.168.244.0', + }, + ], + } + }, + { + 'name': 'veth0.1', + 'state': 'up', + 'type': 'macvlan', + 'parent': 'eth0-parent', + 'interface_name': 'veth1', + 'macvlan': { + 'mode': 'passthru', + 'promiscuous': False, + 'tap': True + }, + 'ip': { + 'address': '192.168.245.7/24', + 'auto6': False, + 'route_append_only': False, + 'rule_append_only': False, + 'route': [ + { + 'network': '192.168.245.0', + }, + ], + } + } + ], + ) + + self.do_connections_validate( [ { From 2dbc68b7d936a3649916e52f914878dbcff18009 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 13 Feb 2018 08:23:22 +0100 Subject: [PATCH 2/2] library: fix tests for macvlan with initscripts --- library/network_connections.py | 1 - library/test_network_connections.py | 22 +++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/library/network_connections.py b/library/network_connections.py index 54cf345..43129e6 100755 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -2648,7 +2648,6 @@ class Cmd_initscripts(Cmd): def run_prepare(self): Cmd.run_prepare(self) - names = {} for idx, connection in enumerate(self.connections): if connection['type'] in [ 'macvlan' ]: self.log_fatal(idx, 'unsupported type %s for initscripts provider' % (connection['type'])) diff --git a/library/test_network_connections.py b/library/test_network_connections.py index a9bb60f..bde388d 100755 --- a/library/test_network_connections.py +++ b/library/test_network_connections.py @@ -112,15 +112,19 @@ class TestValidator(unittest.TestCase): ARGS_CONNECTIONS.validate_connection_one(mode, connections, idx) except n.ValidationError as e: continue - if 'type' in connection: - content_current = kwargs.get('initscripts_content_current', None) - if content_current: - content_current = content_current[idx] - c = n.IfcfgUtil.ifcfg_create(connections, idx, content_current = content_current) - #pprint("con[%s] = \"%s\"" % (idx, connections[idx]['name']), c) - exp = kwargs.get('initscripts_dict_expected', None) - if exp is not None: - self.assertEqual(exp[idx], c) + if 'type' not in connection: + continue + if connection['type'] in ['macvlan']: + # initscripts do not support this type. Skip the test. + continue + content_current = kwargs.get('initscripts_content_current', None) + if content_current: + content_current = content_current[idx] + c = n.IfcfgUtil.ifcfg_create(connections, idx, content_current = content_current) + #pprint("con[%s] = \"%s\"" % (idx, connections[idx]['name']), c) + exp = kwargs.get('initscripts_dict_expected', None) + if exp is not None: + self.assertEqual(exp[idx], c) def do_connections_validate(self, expected_connections, input_connections, **kwargs):