From e6eab0c12df9192208233b31d8e2ac7e79cba44c Mon Sep 17 00:00:00 2001 From: creatiwit Date: Fri, 12 Jul 2019 17:00:11 -0700 Subject: Support for Google Private Access over IPv6 (#790) * Enable IPv6 on interfaces based on metadata key. * Support for FreeBSD and SLES images is still needed. * Support for multi-NIC is still needed. * NOTE: `-6` argument to dhclient silently changes pid-file to `/run/dhclient6.pid` --- .../distro_lib/debian_8/tests/utils_test.py | 22 +++- .../distro_lib/debian_8/utils.py | 10 ++ .../distro_lib/debian_9/tests/utils_test.py | 22 +++- .../distro_lib/debian_9/utils.py | 10 ++ .../distro_lib/el_6/tests/utils_test.py | 24 +++-- .../google_compute_engine/distro_lib/el_6/utils.py | 10 ++ .../distro_lib/el_7/tests/utils_test.py | 22 +++- .../google_compute_engine/distro_lib/el_7/utils.py | 10 ++ .../google_compute_engine/distro_lib/helpers.py | 46 ++++++++- .../distro_lib/tests/helpers_test.py | 112 ++++++++++++++++----- .../networking/network_daemon.py | 2 + .../networking/network_setup/network_setup.py | 28 +++++- .../network_setup/tests/network_setup_test.py | 89 ++++++++++++++-- .../networking/tests/network_daemon_test.py | 27 +++++ 14 files changed, 383 insertions(+), 51 deletions(-) diff --git a/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_8/tests/utils_test.py b/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_8/tests/utils_test.py index f714ba8..7f92795 100644 --- a/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_8/tests/utils_test.py +++ b/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_8/tests/utils_test.py @@ -27,12 +27,28 @@ class UtilsTest(unittest.TestCase): self.mock_setup = mock.create_autospec(utils.Utils) @mock.patch('google_compute_engine.distro_lib.helpers.CallDhclientIpv6') - def testEnableIpv6(self, mock_call): + @mock.patch('google_compute_engine.distro_lib.helpers.CallEnableRouteAdvertisements') + def testEnableIpv6(self, mock_call_enable_ra, mock_call_dhclient): mocks = mock.Mock() - mocks.attach_mock(mock_call, 'call') + mocks.attach_mock(mock_call_dhclient, 'dhclient') + mocks.attach_mock(mock_call_enable_ra, 'enable_ra') utils.Utils.EnableIpv6(self.mock_setup, ['A', 'B'], self.mock_logger) - expected_calls = [mock.call.call(['A', 'B'], mock.ANY)] + expected_calls = [ + mock.call.enable_ra(['A', 'B'], mock.ANY), + mock.call.dhclient(['A', 'B'], mock.ANY), + ] + self.assertEqual(mocks.mock_calls, expected_calls) + + @mock.patch('google_compute_engine.distro_lib.helpers.CallDhclientIpv6') + def testDisableIpv6(self, mock_call_dhclient): + mocks = mock.Mock() + mocks.attach_mock(mock_call_dhclient, 'dhclient') + + utils.Utils.DisableIpv6(self.mock_setup, ['A', 'B'], self.mock_logger) + expected_calls = [ + mock.call.dhclient(['A', 'B'], mock.ANY, None, release_lease=True), + ] self.assertEqual(mocks.mock_calls, expected_calls) @mock.patch('google_compute_engine.distro_lib.helpers.CallDhclient') diff --git a/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_8/utils.py b/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_8/utils.py index f58e09d..7872d1b 100644 --- a/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_8/utils.py +++ b/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_8/utils.py @@ -31,8 +31,18 @@ class Utils(utils.Utils): logger: logger object, used to write to SysLog and serial port. dhclient_script: string, the path to a dhclient script used by dhclient. """ + helpers.CallEnableRouteAdvertisements(interfaces, logger) helpers.CallDhclientIpv6(interfaces, logger) + def DisableIpv6(self, interfaces, logger): + """Disable Ipv6 by giving up the DHCP lease using dhclient. + + Args: + interface: string, the output device names for enabling IPv6. + logger: logger object, used to write to SysLog and serial port. + """ + helpers.CallDhclientIpv6(interfaces, logger, None, release_lease=True) + def EnableNetworkInterfaces(self, interfaces, logger, dhclient_script=None): """Enable the list of network interfaces. diff --git a/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_9/tests/utils_test.py b/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_9/tests/utils_test.py index fbb9a8e..4840753 100644 --- a/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_9/tests/utils_test.py +++ b/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_9/tests/utils_test.py @@ -27,12 +27,28 @@ class UtilsTest(unittest.TestCase): self.mock_setup = mock.create_autospec(utils.Utils) @mock.patch('google_compute_engine.distro_lib.helpers.CallDhclientIpv6') - def testEnableIpv6(self, mock_call): + @mock.patch('google_compute_engine.distro_lib.helpers.CallEnableRouteAdvertisements') + def testEnableIpv6(self, mock_call_enable_ra, mock_call_dhclient): mocks = mock.Mock() - mocks.attach_mock(mock_call, 'call') + mocks.attach_mock(mock_call_dhclient, 'dhclient') + mocks.attach_mock(mock_call_enable_ra, 'enable_ra') utils.Utils.EnableIpv6(self.mock_setup, ['A', 'B'], self.mock_logger) - expected_calls = [mock.call.call(['A', 'B'], mock.ANY)] + expected_calls = [ + mock.call.enable_ra(['A', 'B'], mock.ANY), + mock.call.dhclient(['A', 'B'], mock.ANY), + ] + self.assertEqual(mocks.mock_calls, expected_calls) + + @mock.patch('google_compute_engine.distro_lib.helpers.CallDhclientIpv6') + def testDisableIpv6(self, mock_call_dhclient): + mocks = mock.Mock() + mocks.attach_mock(mock_call_dhclient, 'dhclient') + + utils.Utils.DisableIpv6(self.mock_setup, ['A', 'B'], self.mock_logger) + expected_calls = [ + mock.call.dhclient(['A', 'B'], mock.ANY, None, release_lease=True), + ] self.assertEqual(mocks.mock_calls, expected_calls) @mock.patch('google_compute_engine.distro_lib.helpers.CallDhclient') diff --git a/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_9/utils.py b/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_9/utils.py index 11a5cbd..9255214 100644 --- a/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_9/utils.py +++ b/packages/python-google-compute-engine/google_compute_engine/distro_lib/debian_9/utils.py @@ -31,8 +31,18 @@ class Utils(utils.Utils): logger: logger object, used to write to SysLog and serial port. dhclient_script: string, the path to a dhclient script used by dhclient. """ + helpers.CallEnableRouteAdvertisements(interfaces, logger) helpers.CallDhclientIpv6(interfaces, logger) + def DisableIpv6(self, interfaces, logger): + """Disable Ipv6 by giving up the DHCP lease using dhclient. + + Args: + interface: string, the output device names for enabling IPv6. + logger: logger object, used to write to SysLog and serial port. + """ + helpers.CallDhclientIpv6(interfaces, logger, None, release_lease=True) + def EnableNetworkInterfaces(self, interfaces, logger, dhclient_script=None): """Enable the list of network interfaces. diff --git a/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_6/tests/utils_test.py b/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_6/tests/utils_test.py index f47f5c7..1dcbc60 100644 --- a/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_6/tests/utils_test.py +++ b/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_6/tests/utils_test.py @@ -30,15 +30,27 @@ class UtilsTest(unittest.TestCase): pass @mock.patch('google_compute_engine.distro_lib.helpers.CallDhclientIpv6') - def testEnableIpv6(self, mock_call): + @mock.patch('google_compute_engine.distro_lib.helpers.CallEnableRouteAdvertisements') + def testEnableIpv6(self, mock_call_enable_ra, mock_call_dhclient): mocks = mock.Mock() - mocks.attach_mock(mock_call, 'call') + mocks.attach_mock(mock_call_dhclient, 'dhclient') + mocks.attach_mock(mock_call_enable_ra, 'enable_ra') - utils.Utils.EnableIpv6( - self.mock_setup, ['A', 'B'], self.mock_logger, - dhclient_script='test_script') + utils.Utils.EnableIpv6(self.mock_setup, ['A', 'B'], self.mock_logger) expected_calls = [ - mock.call.call(['A', 'B'], mock.ANY, dhclient_script='test_script'), + mock.call.enable_ra(['A', 'B'], mock.ANY), + mock.call.dhclient(['A', 'B'], mock.ANY, dhclient_script=None), + ] + self.assertEqual(mocks.mock_calls, expected_calls) + + @mock.patch('google_compute_engine.distro_lib.helpers.CallDhclientIpv6') + def testDisableIpv6(self, mock_call_dhclient): + mocks = mock.Mock() + mocks.attach_mock(mock_call_dhclient, 'dhclient') + + utils.Utils.DisableIpv6(self.mock_setup, ['A', 'B'], self.mock_logger) + expected_calls = [ + mock.call.dhclient(['A', 'B'], mock.ANY, None, release_lease=True), ] self.assertEqual(mocks.mock_calls, expected_calls) diff --git a/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_6/utils.py b/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_6/utils.py index 43b8769..9f9bb98 100644 --- a/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_6/utils.py +++ b/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_6/utils.py @@ -31,9 +31,19 @@ class Utils(utils.Utils): logger: logger object, used to write to SysLog and serial port. dhclient_script: string, the path to a dhclient script used by dhclient. """ + helpers.CallEnableRouteAdvertisements(interfaces, logger) helpers.CallDhclientIpv6( interfaces, logger, dhclient_script=dhclient_script) + def DisableIpv6(self, interfaces, logger): + """Disable Ipv6 by giving up the DHCP lease using dhclient. + + Args: + interface: string, the output device names for enabling IPv6. + logger: logger object, used to write to SysLog and serial port. + """ + helpers.CallDhclientIpv6(interfaces, logger, None, release_lease=True) + def EnableNetworkInterfaces(self, interfaces, logger, dhclient_script=None): """Enable the list of network interfaces. diff --git a/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_7/tests/utils_test.py b/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_7/tests/utils_test.py index 0e096db..61260b6 100644 --- a/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_7/tests/utils_test.py +++ b/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_7/tests/utils_test.py @@ -97,12 +97,28 @@ class UtilsTest(unittest.TestCase): self.assertEqual(mocks.mock_calls, expected_calls) @mock.patch('google_compute_engine.distro_lib.helpers.CallDhclientIpv6') - def testEnableIpv6(self, mock_call): + @mock.patch('google_compute_engine.distro_lib.helpers.CallEnableRouteAdvertisements') + def testEnableIpv6(self, mock_call_enable_ra, mock_call_dhclient): mocks = mock.Mock() - mocks.attach_mock(mock_call, 'call') + mocks.attach_mock(mock_call_dhclient, 'dhclient') + mocks.attach_mock(mock_call_enable_ra, 'enable_ra') utils.Utils.EnableIpv6(self.mock_setup, ['A', 'B'], self.mock_logger) - expected_calls = [mock.call.call(['A', 'B'], mock.ANY)] + expected_calls = [ + mock.call.enable_ra(['A', 'B'], mock.ANY), + mock.call.dhclient(['A', 'B'], mock.ANY), + ] + self.assertEqual(mocks.mock_calls, expected_calls) + + @mock.patch('google_compute_engine.distro_lib.helpers.CallDhclientIpv6') + def testDisableIpv6(self, mock_call_dhclient): + mocks = mock.Mock() + mocks.attach_mock(mock_call_dhclient, 'dhclient') + + utils.Utils.DisableIpv6(self.mock_setup, ['A', 'B'], self.mock_logger) + expected_calls = [ + mock.call.dhclient(['A', 'B'], mock.ANY, None, release_lease=True), + ] self.assertEqual(mocks.mock_calls, expected_calls) @mock.patch('google_compute_engine.distro_lib.el_7.utils.os.path.exists') diff --git a/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_7/utils.py b/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_7/utils.py index 062d471..e0e318b 100644 --- a/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_7/utils.py +++ b/packages/python-google-compute-engine/google_compute_engine/distro_lib/el_7/utils.py @@ -38,8 +38,18 @@ class Utils(utils.Utils): logger: logger object, used to write to SysLog and serial port. dhclient_script: string, the path to a dhclient script used by dhclient. """ + helpers.CallEnableRouteAdvertisements(interfaces, logger) helpers.CallDhclientIpv6(interfaces, logger) + def DisableIpv6(self, interfaces, logger): + """Disable Ipv6 by giving up the DHCP lease using dhclient. + + Args: + interface: string, the output device names for enabling IPv6. + logger: logger object, used to write to SysLog and serial port. + """ + helpers.CallDhclientIpv6(interfaces, logger, None, release_lease=True) + def EnableNetworkInterfaces(self, interfaces, logger, dhclient_script=None): """Enable the list of network interfaces. diff --git a/packages/python-google-compute-engine/google_compute_engine/distro_lib/helpers.py b/packages/python-google-compute-engine/google_compute_engine/distro_lib/helpers.py index cbe810e..b7d16a1 100644 --- a/packages/python-google-compute-engine/google_compute_engine/distro_lib/helpers.py +++ b/packages/python-google-compute-engine/google_compute_engine/distro_lib/helpers.py @@ -42,19 +42,32 @@ def CallDhclient( logger.warning('Could not enable interfaces %s.', interfaces) -def CallDhclientIpv6(interfaces, logger, dhclient_script=None): +def CallDhclientIpv6(interfaces, logger, dhclient_script=None, + release_lease=False): """Configure the network interfaces for IPv6 using dhclient. Args: interface: string, the output device names for enabling IPv6. logger: logger object, used to write to SysLog and serial port. dhclient_script: string, the path to a dhclient script used by dhclient. + release_lease: Release the IPv6 lease. """ - logger.info('Enabling IPv6 on the Ethernet interfaces %s.', interfaces) + logger.info('Calling Dhclient for IPv6 configuration ' + 'on the Ethernet interfaces %s.', interfaces) timeout_command = ['timeout', '5'] dhclient_command = ['dhclient'] + if release_lease: + try: + subprocess.check_call( + timeout_command + dhclient_command + [ + '-6', '-r', '-v'] + interfaces) + except subprocess.CalledProcessError: + logger.warning('Could not release IPv6 lease on interface %s.', + interfaces) + return + if dhclient_script and os.path.exists(dhclient_script): dhclient_command += ['-sf', dhclient_script] @@ -65,6 +78,18 @@ def CallDhclientIpv6(interfaces, logger, dhclient_script=None): logger.warning('Could not enable IPv6 on interface %s.', interfaces) +def CallEnableRouteAdvertisements(interfaces, logger): + """Enable route advertisements. + Args: + interfaces: list of string, the output device names to enable. + logger: logger object, used to write to SysLog and serial port. + """ + for interface in interfaces: + accept_ra = ( + 'net.ipv6.conf.{interface}.accept_ra_rt_info_max_plen'.format( + interface=interface)) + CallSysctl(logger, accept_ra, 128) + def CallHwclock(logger): """Sync clock using hwclock. @@ -98,3 +123,20 @@ def CallNtpdate(logger): logger.warning('Failed to sync system time with ntp server.') else: logger.info('Synced system time with ntp server.') + +def CallSysctl(logger, name, value): + """Write a variable using sysctl. + + Args: + logger: logger object, used to write to SysLog and serial port. + name: string name of the sysctl variable. + value: value of the sysctl variable. + """ + logger.info('Configuring sysctl %s.', name) + + sysctl_command = [ + 'sysctl', '-w', '{name}={value}'.format(name=name, value=value)] + try: + subprocess.check_call(sysctl_command) + except subprocess.CalledProcessError: + logger.warning('Unable to configure sysctl %s.', name) diff --git a/packages/python-google-compute-engine/google_compute_engine/distro_lib/tests/helpers_test.py b/packages/python-google-compute-engine/google_compute_engine/distro_lib/tests/helpers_test.py index 7c2c0b6..6afca57 100644 --- a/packages/python-google-compute-engine/google_compute_engine/distro_lib/tests/helpers_test.py +++ b/packages/python-google-compute-engine/google_compute_engine/distro_lib/tests/helpers_test.py @@ -67,42 +67,89 @@ class HelpersTest(unittest.TestCase): @mock.patch('google_compute_engine.distro_lib.helpers.os.path.exists') @mock.patch('google_compute_engine.distro_lib.helpers.subprocess.check_call') - def testCallDhclientIpv6(self, mock_call, mock_exists): - mocks = mock.Mock() - mocks.attach_mock(mock_exists, 'exists') - mocks.attach_mock(mock_call, 'call') - mocks.attach_mock(self.mock_logger, 'logger') + def testCallDhclientIpv6NonExistentScript(self, mock_call, mock_exists): + mock_logger = mock.Mock() - mock_exists.side_effect = [False, True] + mock_exists.side_effect = [False] + helpers.CallDhclientIpv6(['a', 'b'], mock_logger, 'test_script') + mock_call.assert_has_calls( + [ + mock.call.call( + ['timeout', '5', 'dhclient', '-1', '-6', '-v', 'a', 'b']), + ]) + + @mock.patch('google_compute_engine.distro_lib.helpers.os.path.exists') + @mock.patch('google_compute_engine.distro_lib.helpers.subprocess.check_call') + def testCallDhclientIpv6(self, mock_call, mock_exists): + mock_logger = mock.Mock() + mock_exists.side_effect = [True] mock_call.side_effect = [ - None, None, None, subprocess.CalledProcessError(1, 'Test'), + None, + None, + subprocess.CalledProcessError(1, 'Test'), + None, + None, + subprocess.CalledProcessError(1, 'Test'), ] - helpers.CallDhclientIpv6(['a', 'b'], self.mock_logger, 'test_script') - helpers.CallDhclientIpv6(['c', 'd'], self.mock_logger, 'test_script') - helpers.CallDhclientIpv6(['e', 'f'], self.mock_logger, None) - helpers.CallDhclientIpv6(['g', 'h'], self.mock_logger, None) + helpers.CallDhclientIpv6(['a', 'b'], mock_logger, 'test_script') + helpers.CallDhclientIpv6(['c', 'd'], mock_logger, None) + helpers.CallDhclientIpv6(['e', 'f'], mock_logger, None) + helpers.CallDhclientIpv6( + ['g', 'h'], mock_logger, 'test_script', release_lease=True) + helpers.CallDhclientIpv6(['i', 'j'], mock_logger, None, release_lease=True) + helpers.CallDhclientIpv6(['k', 'l'], mock_logger, None, release_lease=True) expected_calls = [ - mock.call.logger.info(mock.ANY, ['a', 'b']), - mock.call.exists('test_script'), mock.call.call( - ['timeout', '5', 'dhclient', '-1', '-6', '-v', 'a', 'b']), - mock.call.logger.info(mock.ANY, ['c', 'd']), - mock.call.exists('test_script'), + [ + 'timeout', '5','dhclient', '-sf', 'test_script', '-1', '-6', + '-v', 'a', 'b', + ]), mock.call.call( - ['timeout', '5', 'dhclient', '-sf', 'test_script', '-1', '-6', - '-v', 'c', 'd']), - mock.call.logger.info(mock.ANY, ['e', 'f']), + [ + 'timeout', '5', 'dhclient', '-1', '-6', '-v', 'c', 'd', + ]), mock.call.call( - ['timeout', '5', 'dhclient', '-1', '-6', '-v', 'e', 'f']), - mock.call.logger.info(mock.ANY, ['g', 'h']), + [ + 'timeout', '5', 'dhclient', '-1', '-6', '-v', 'e', 'f', + ]), mock.call.call( - ['timeout', '5', 'dhclient', '-1', '-6', '-v', 'g', 'h']), - mock.call.logger.warning(mock.ANY, ['g', 'h']), + [ + 'timeout', '5', 'dhclient', '-6', '-r', '-v', 'g', 'h', + ]), + mock.call.call( + [ + 'timeout', '5', 'dhclient', '-6', '-r', '-v', 'i', 'j', + ]), + mock.call.call( + [ + 'timeout', '5', 'dhclient', '-6', '-r', '-v', 'k', 'l', + ]), ] - self.assertEqual(mocks.mock_calls, expected_calls) + self.assertEqual(mock_call.mock_calls, expected_calls) + mock_logger.assert_has_calls( + [ + mock.call.warning(mock.ANY, ['e', 'f']), + ]) + mock_logger.assert_has_calls( + [ + mock.call.warning(mock.ANY, ['k', 'l']), + ]) + + @mock.patch('google_compute_engine.distro_lib.helpers.subprocess.check_call') + def testEnableRouteAdvertisements(self, mock_call): + mock_logger = mock.Mock() + interfaces = ['foo', 'bar', 'baz'] + helpers.CallEnableRouteAdvertisements(interfaces, mock_logger) + mock_call.assert_has_calls([ + mock.call( + [ + 'sysctl', '-w', + 'net.ipv6.conf.%s.accept_ra_rt_info_max_plen=128' % interface, + ]) + for interface in interfaces]) @mock.patch('google_compute_engine.distro_lib.helpers.subprocess.check_call') def testCallHwclock(self, mock_call): @@ -174,3 +221,20 @@ class HelpersTest(unittest.TestCase): mock_check_call.assert_called_once_with(command_ntpdate, shell=True) expected_calls = [mock.call.warning(mock.ANY)] self.assertEqual(mock_logger.mock_calls, expected_calls) + + @mock.patch('google_compute_engine.distro_lib.helpers.subprocess.check_call') + def testCallSysctl(self, mock_call): + command = ['sysctl', '-w'] + mock_logger = mock.Mock() + expected_log_calls = [] + for name in ['foo', 'bar', 'baz']: + for value in ['foo', 'bar', 'baz']: + params = ['{name}={value}'.format(name=name, value=value)] + helpers.CallSysctl(mock_logger, name, value) + mock_call.assert_called_with(command + params) + expected_log_calls.append(mock.call.info(mock.ANY, name)) + self.assertEqual(mock_logger.mock_calls, expected_log_calls) + + mock_call.side_effect = subprocess.CalledProcessError(1, 'Test') + helpers.CallSysctl(mock_logger, 'fail', 1) + mock_logger.assert_has_calls([mock.call.warning(mock.ANY, 'fail')]) diff --git a/packages/python-google-compute-engine/google_compute_engine/networking/network_daemon.py b/packages/python-google-compute-engine/google_compute_engine/networking/network_daemon.py index ce0dd65..e152464 100644 --- a/packages/python-google-compute-engine/google_compute_engine/networking/network_daemon.py +++ b/packages/python-google-compute-engine/google_compute_engine/networking/network_daemon.py @@ -93,6 +93,8 @@ class NetworkDaemon(object): default_interface = network_interfaces[0] if default_interface.ipv6: self.network_setup.EnableIpv6([default_interface.name]) + else: + self.network_setup.DisableIpv6([default_interface.name]) self.network_setup.EnableNetworkInterfaces( [interface.name for interface in network_interfaces[1:]]) diff --git a/packages/python-google-compute-engine/google_compute_engine/networking/network_setup/network_setup.py b/packages/python-google-compute-engine/google_compute_engine/networking/network_setup/network_setup.py index f8b3be5..b92bed1 100755 --- a/packages/python-google-compute-engine/google_compute_engine/networking/network_setup/network_setup.py +++ b/packages/python-google-compute-engine/google_compute_engine/networking/network_setup/network_setup.py @@ -26,7 +26,6 @@ class NetworkSetup(object): """Enable network interfaces.""" interfaces = set() - ipv6_interfaces = set() network_interfaces = 'instance/network-interfaces' def __init__(self, dhclient_script=None, dhcp_command=None, debug=False): @@ -43,6 +42,8 @@ class NetworkSetup(object): self.logger = logger.Logger( name='network-setup', debug=debug, facility=facility) self.distro_utils = distro_utils.Utils(debug=debug) + self.ipv6_initialized = False + self.ipv6_interfaces = set() def EnableIpv6(self, interfaces): """Enable IPv6 on the list of network interfaces. @@ -50,16 +51,37 @@ class NetworkSetup(object): Args: interfaces: list of string, the output device names for enabling IPv6. """ - if not interfaces or set(interfaces) == self.ipv6_interfaces: + if not interfaces or self.ipv6_interfaces == set(interfaces): return self.logger.info('Enabling IPv6 on Ethernet interface: %s.', interfaces) - self.ipv6_interfaces = set(interfaces) + self.ipv6_interfaces = self.ipv6_interfaces.union(set(interfaces)) + self.ipv6_initialized = True # Distro-specific setup for enabling IPv6 on network interfaces. self.distro_utils.EnableIpv6( interfaces, self.logger, dhclient_script=self.dhclient_script) + def DisableIpv6(self, interfaces): + """Disable IPv6 on the list of network interfaces. + + Args: + interfaces: list of string, the output device names for disabling IPv6. + """ + # Allow to run once during Initialization and after that only when an + # interface is found in the ipv6_interfaces set. + if not interfaces or ( + self.ipv6_initialized and not self.ipv6_interfaces.intersection( + set(interfaces))): + return + + self.logger.info('Disabling IPv6 on Ethernet interface: %s.', interfaces) + self.ipv6_interfaces.difference_update(interfaces) + self.ipv6_initialized = True + + # Distro-specific setup for disabling IPv6 on network interfaces. + self.distro_utils.DisableIpv6(interfaces, self.logger) + def EnableNetworkInterfaces(self, interfaces): """Enable the list of network interfaces. diff --git a/packages/python-google-compute-engine/google_compute_engine/networking/network_setup/tests/network_setup_test.py b/packages/python-google-compute-engine/google_compute_engine/networking/network_setup/tests/network_setup_test.py index c57547e..10fe2c5 100644 --- a/packages/python-google-compute-engine/google_compute_engine/networking/network_setup/tests/network_setup_test.py +++ b/packages/python-google-compute-engine/google_compute_engine/networking/network_setup/tests/network_setup_test.py @@ -47,16 +47,13 @@ class NetworkSetupTest(unittest.TestCase): network_setup.NetworkSetup.EnableIpv6(self.setup, None) network_setup.NetworkSetup.EnableIpv6(self.setup, []) # Enable interfaces. - network_setup.NetworkSetup.EnableIpv6( - self.setup, ['A', 'B']) + network_setup.NetworkSetup.EnableIpv6(self.setup, ['A', 'B']) self.assertEqual(self.setup.ipv6_interfaces, set(['A', 'B'])) # Add a new interface. - network_setup.NetworkSetup.EnableIpv6( - self.setup, ['A', 'B', 'C']) + network_setup.NetworkSetup.EnableIpv6(self.setup, ['A', 'B', 'C']) self.assertEqual(self.setup.ipv6_interfaces, set(['A', 'B', 'C'])) - # Interfaces are already enabled. - network_setup.NetworkSetup.EnableIpv6( - self.setup, ['A', 'B', 'C']) + # Interfaces are already enabled, do nothing. + network_setup.NetworkSetup.EnableIpv6(self.setup, ['A', 'B', 'C']) self.assertEqual(self.setup.ipv6_interfaces, set(['A', 'B', 'C'])) expected_calls = [ mock.call.logger.info(mock.ANY, ['A', 'B']), @@ -67,6 +64,84 @@ class NetworkSetupTest(unittest.TestCase): ] self.assertEqual(mocks.mock_calls, expected_calls) + @mock.patch('google_compute_engine.networking.network_setup.network_setup.subprocess.check_call') + def testDisableIpv6(self, mock_call): + mocks = mock.Mock() + mocks.attach_mock(mock_call, 'call') + mocks.attach_mock(self.mock_logger, 'logger') + mocks.attach_mock(self.mock_distro_utils.EnableIpv6, 'enable') + mocks.attach_mock(self.mock_distro_utils.DisableIpv6, 'disable') + expected_calls = [] + + # Clean run, run disable once e.g. at boot. + network_setup.NetworkSetup.DisableIpv6(self.setup, ['A']) + self.assertEqual(self.setup.ipv6_interfaces, set([])) + # No more disables allowed, have to follow the contract of Enable and then + # Disable. + network_setup.NetworkSetup.DisableIpv6(self.setup, ['A']) + expected_calls.extend( + [ + mock.call.logger.info(mock.ANY, ['A']), + mock.call.disable(['A'], mock.ANY), + ]) + # Enable interfaces. + network_setup.NetworkSetup.EnableIpv6(self.setup, ['A', 'B', 'C']) + expected_calls.extend( + [ + mock.call.logger.info(mock.ANY, ['A', 'B', 'C']), + mock.call.enable( + ['A', 'B', 'C'], mock.ANY, dhclient_script='/bin/script'), + ]) + # Remove interface. + network_setup.NetworkSetup.DisableIpv6(self.setup, ['A']) + self.assertEqual(self.setup.ipv6_interfaces, set(['B', 'C'])) + expected_calls.extend( + [ + mock.call.logger.info(mock.ANY, ['A']), + mock.call.disable(['A'], mock.ANY), + ]) + + # Add it back. + network_setup.NetworkSetup.EnableIpv6(self.setup, ['A']) + self.assertEqual(self.setup.ipv6_interfaces, set(['A', 'B', 'C'])) + expected_calls.extend( + [ + mock.call.logger.info(mock.ANY, ['A']), + mock.call.enable(['A'], mock.ANY, dhclient_script='/bin/script'), + ]) + + # Remove list. + network_setup.NetworkSetup.DisableIpv6(self.setup, ['A', 'B']) + self.assertEqual(self.setup.ipv6_interfaces, set(['C'])) + expected_calls.extend( + [ + mock.call.logger.info(mock.ANY, ['A', 'B']), + mock.call.disable(['A', 'B'], mock.ANY), + ]) + + # Try removing again, these are no ops. + network_setup.NetworkSetup.DisableIpv6(self.setup, ['A']) + network_setup.NetworkSetup.DisableIpv6(self.setup, ['A', 'B']) + + # Remove the last element. + network_setup.NetworkSetup.DisableIpv6(self.setup, ['C']) + self.assertEqual(self.setup.ipv6_interfaces, set([])) + expected_calls.extend( + [ + mock.call.logger.info(mock.ANY, ['C']), + mock.call.disable(['C'], mock.ANY), + ]) + + # Empty list, allow adds back again. + network_setup.NetworkSetup.EnableIpv6(self.setup, ['A']) + self.assertEqual(self.setup.ipv6_interfaces, set(['A'])) + expected_calls.extend( + [ + mock.call.logger.info(mock.ANY, ['A']), + mock.call.enable(['A'], mock.ANY, dhclient_script='/bin/script'), + ]) + self.assertEqual(mocks.mock_calls, expected_calls) + @mock.patch('google_compute_engine.networking.network_setup.network_setup.subprocess.check_call') def testEnableNetworkInterfaces(self, mock_call): mocks = mock.Mock() diff --git a/packages/python-google-compute-engine/google_compute_engine/networking/tests/network_daemon_test.py b/packages/python-google-compute-engine/google_compute_engine/networking/tests/network_daemon_test.py index dd74fdd..9216ea3 100644 --- a/packages/python-google-compute-engine/google_compute_engine/networking/tests/network_daemon_test.py +++ b/packages/python-google-compute-engine/google_compute_engine/networking/tests/network_daemon_test.py @@ -149,6 +149,7 @@ class NetworkDaemonTest(unittest.TestCase): self.mock_setup, result) expected_calls = [ mock.call.setup._ExtractInterfaceMetadata(result), + mock.call.network_setup.DisableIpv6(['eth0']), mock.call.network_setup.EnableNetworkInterfaces(['eth1']), mock.call.forwarding.HandleForwardedIps( 'eth0', ['a'], '1.1.1.1'), @@ -182,6 +183,32 @@ class NetworkDaemonTest(unittest.TestCase): ] self.assertEqual(mocks.mock_calls, expected_calls) + def testHandleNetworkInterfacesIpv6Disabled(self): + mocks = mock.Mock() + mocks.attach_mock(self.mock_ip_forwarding, 'forwarding') + mocks.attach_mock(self.mock_network_setup, 'network_setup') + mocks.attach_mock(self.mock_setup, 'setup') + self.mock_setup.ip_aliases = None + self.mock_setup.target_instance_ips = None + self.mock_setup.ip_forwarding_enabled = True + self.mock_setup.network_setup_enabled = True + self.mock_setup._ExtractInterfaceMetadata.return_value = [ + network_daemon.NetworkDaemon.NetworkInterface( + 'eth0', forwarded_ips=['a'], ip='1.1.1.1', ipv6=False), + ] + result = mock.Mock() + + network_daemon.NetworkDaemon.HandleNetworkInterfaces( + self.mock_setup, result) + expected_calls = [ + mock.call.setup._ExtractInterfaceMetadata(result), + mock.call.network_setup.DisableIpv6(['eth0']), + mock.call.network_setup.EnableNetworkInterfaces([]), + mock.call.forwarding.HandleForwardedIps( + 'eth0', ['a'], '1.1.1.1'), + ] + self.assertEqual(mocks.mock_calls, expected_calls) + def testHandleNetworkInterfacesDisabled(self): mocks = mock.Mock() mocks.attach_mock(self.mock_ip_forwarding, 'forwarding') -- cgit v1.2.1