diff options
author | ryanwe <ryanwe@google.com> | 2018-02-12 12:12:51 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-12 12:12:51 -0800 |
commit | 1de8560846a00fe428b6f2ffcb099b0eaf4e6000 (patch) | |
tree | 30dd9f597ecf478ae02285023958dcb6add2a12c | |
parent | c50252a91a13b5dfbd9212f2464a23a93f73057a (diff) | |
download | google-compute-image-packages-1de8560846a00fe428b6f2ffcb099b0eaf4e6000.tar.gz |
Muti-Nic network setup support for SUSE 11 and 12. (#547)
Muti-Nic network setup support for SUSE 11 and 12.
Adds network setup logic to handle multiple Nics for SUSE 11 and 12.
For SUSE 11
- Run dhcpcd on additional Nics.
For SUSE 12
- For additional Nics, create the ifcfg-eth* files.
- Run wicked ifup eth1... to active the Nics.
Requires installation of `distro` package for python 3.5 and above.
Requires updated `setuptools` for all packages.
-rw-r--r-- | .travis.yml | 1 | ||||
-rw-r--r-- | google_compute_engine/compat.py | 19 | ||||
-rw-r--r-- | google_compute_engine/distro/sles_11/__init__.py | 0 | ||||
-rw-r--r-- | google_compute_engine/distro/sles_11/tests/__init__.py | 0 | ||||
-rw-r--r-- | google_compute_engine/distro/sles_11/tests/utils_test.py | 73 | ||||
-rw-r--r-- | google_compute_engine/distro/sles_11/utils.py | 60 | ||||
-rw-r--r-- | google_compute_engine/distro/sles_12/__init__.py | 0 | ||||
-rw-r--r-- | google_compute_engine/distro/sles_12/tests/__init__.py | 0 | ||||
-rw-r--r-- | google_compute_engine/distro/sles_12/tests/utils_test.py | 93 | ||||
-rw-r--r-- | google_compute_engine/distro/sles_12/utils.py | 82 | ||||
-rw-r--r-- | google_compute_engine/tests/compat_test.py | 16 | ||||
-rwxr-xr-x | setup.py | 2 | ||||
-rw-r--r-- | tox.ini | 3 |
13 files changed, 337 insertions, 12 deletions
diff --git a/.travis.yml b/.travis.yml index 8debce4..9e0a14b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,7 @@ install: # Tox needs an older version of virtualenv. - pip install "virtualenv<14.0.0" - pip install tox tox-travis codecov +- pip install "setuptools>20.0.0" script: - tox after_success: diff --git a/google_compute_engine/compat.py b/google_compute_engine/compat.py index bafb1f7..600d876 100644 --- a/google_compute_engine/compat.py +++ b/google_compute_engine/compat.py @@ -16,28 +16,35 @@ """A module for resolving compatibility issues between Python 2 and Python 3.""" import logging -import platform import subprocess import sys -# Set distro-specific utils. -distribution = platform.linux_distribution() +if sys.version_info >= (3, 5): + import distro +else: + import platform as distro + +distribution = distro.linux_distribution() distro_name = distribution[0].lower() distro_version = distribution[1].split('.')[0] distro_utils = None if 'centos' in distro_name and distro_version == '6': import google_compute_engine.distro.el_6.utils as distro_utils -elif 'centos' in distro_name and distro_version == '7': +elif 'centos' in distro_name: import google_compute_engine.distro.el_7.utils as distro_utils elif 'red hat enterprise linux' in distro_name and distro_version == '6': import google_compute_engine.distro.el_6.utils as distro_utils -elif 'red hat enterprise linux' in distro_name and distro_version == '7': +elif 'red hat enterprise linux' in distro_name: import google_compute_engine.distro.el_7.utils as distro_utils elif 'debian' in distro_name and distro_version == '8': import google_compute_engine.distro.debian_8.utils as distro_utils -elif 'debian' in distro_name and distro_version == '9': +elif 'debian' in distro_name: import google_compute_engine.distro.debian_9.utils as distro_utils +elif 'suse' in distro_name and distro_version == '11': + import google_compute_engine.distro.sles_11.utils as distro_utils +elif 'suse' in distro_name: + import google_compute_engine.distro.sles_12.utils as distro_utils else: # Default to Debian 9. import google_compute_engine.distro.debian_9.utils as distro_utils diff --git a/google_compute_engine/distro/sles_11/__init__.py b/google_compute_engine/distro/sles_11/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/google_compute_engine/distro/sles_11/__init__.py diff --git a/google_compute_engine/distro/sles_11/tests/__init__.py b/google_compute_engine/distro/sles_11/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/google_compute_engine/distro/sles_11/tests/__init__.py diff --git a/google_compute_engine/distro/sles_11/tests/utils_test.py b/google_compute_engine/distro/sles_11/tests/utils_test.py new file mode 100644 index 0000000..d18cc0c --- /dev/null +++ b/google_compute_engine/distro/sles_11/tests/utils_test.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +# Copyright 2018 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Unittest for utils.py module.""" + +import subprocess + +from google_compute_engine.distro.sles_11 import utils +from google_compute_engine.test_compat import mock +from google_compute_engine.test_compat import unittest + + +class UtilsTest(unittest.TestCase): + + def setUp(self): + self.mock_logger = mock.Mock() + self.mock_setup = mock.create_autospec(utils.Utils) + + def testEnableNetworkInterfacesWithSingleNic(self): + mocks = mock.Mock() + + utils.Utils.EnableNetworkInterfaces( + self.mock_setup, ['eth0'], self.mock_logger) + expected_calls = [] + self.assertEqual(mocks.mock_calls, expected_calls) + + def testEnableNetworkInterfacesWithMultipleNics(self): + mocks = mock.Mock() + mocks.attach_mock(self.mock_setup._Dhcpcd, 'dhcpcd') + + utils.Utils.EnableNetworkInterfaces( + self.mock_setup, ['eth0', 'eth1', 'eth2'], self.mock_logger) + expected_calls = [ + mock.call.dhcpcd(['eth1', 'eth2'], mock.ANY), + ] + self.assertEqual(mocks.mock_calls, expected_calls) + + @mock.patch( + 'google_compute_engine.distro.sles_11.utils.subprocess.check_call') + def testDhcpcd(self, mock_call): + mocks = mock.Mock() + mocks.attach_mock(mock_call, 'call') + mocks.attach_mock(self.mock_logger, 'logger') + mock_call.side_effect = [ + None, None, None, None, + subprocess.CalledProcessError(1, 'Test'), + subprocess.CalledProcessError(1, 'Test'), + ] + + utils.Utils._Dhcpcd( + self.mock_setup, ['eth1', 'eth2', 'eth3'], self.mock_logger) + expected_calls = [ + mock.call.call(['/sbin/dhcpcd', '-x', 'eth1']), + mock.call.call(['/sbin/dhcpcd', 'eth1']), + mock.call.call(['/sbin/dhcpcd', '-x', 'eth2']), + mock.call.call(['/sbin/dhcpcd', 'eth2']), + mock.call.call(['/sbin/dhcpcd', '-x', 'eth3']), + mock.call.logger.info(mock.ANY, 'eth3'), + mock.call.call(['/sbin/dhcpcd','eth3']), + mock.call.logger.warning(mock.ANY, 'eth3'), + ] + self.assertEqual(mocks.mock_calls, expected_calls) diff --git a/google_compute_engine/distro/sles_11/utils.py b/google_compute_engine/distro/sles_11/utils.py new file mode 100644 index 0000000..795b02b --- /dev/null +++ b/google_compute_engine/distro/sles_11/utils.py @@ -0,0 +1,60 @@ +#!/usr/bin/python +# Copyright 2018 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utilities that are distro specific for use on SUSE 11.""" + +import os +import subprocess + +from google_compute_engine import constants +from google_compute_engine.distro import utils + + +class Utils(utils.Utils): + """Utilities used by Linux guest services on SUSE 11.""" + + def EnableNetworkInterfaces( + self, interfaces, logger, dhclient_script=None): + """Enable the list of network interfaces. + + Args: + interfaces: list of string, the output device names to enable. + logger: logger object, used to write to SysLog and serial port. + dhclient_script: string, the path to a dhclient script used by dhclient. + """ + interfaces_to_up = [i for i in interfaces if i != 'eth0'] + if interfaces_to_up: + logger.info('Enabling the Ethernet interfaces %s.', interfaces_to_up) + self._Dhcpcd(interfaces_to_up, logger) + + def _Dhcpcd(self, interfaces, logger): + """Use dhcpcd to activate the interfaces. + + 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: + dhcpcd = ['/sbin/dhcpcd'] + try: + subprocess.check_call(dhcpcd + ['-x', interface]) + except subprocess.CalledProcessError: + # Dhcpcd not yet running for this device. + logger.info('Dhcpcd not yet running for interface %s.', interface) + try: + subprocess.check_call(dhcpcd + [interface]) + except subprocess.CalledProcessError: + # The interface is already active. + logger.warning('Could not activate interface %s.', interface) diff --git a/google_compute_engine/distro/sles_12/__init__.py b/google_compute_engine/distro/sles_12/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/google_compute_engine/distro/sles_12/__init__.py diff --git a/google_compute_engine/distro/sles_12/tests/__init__.py b/google_compute_engine/distro/sles_12/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/google_compute_engine/distro/sles_12/tests/__init__.py diff --git a/google_compute_engine/distro/sles_12/tests/utils_test.py b/google_compute_engine/distro/sles_12/tests/utils_test.py new file mode 100644 index 0000000..ba849a4 --- /dev/null +++ b/google_compute_engine/distro/sles_12/tests/utils_test.py @@ -0,0 +1,93 @@ +#!/usr/bin/python +# Copyright 2018 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Unittest for utils.py module.""" + +import subprocess + +from google_compute_engine.distro.sles_12 import utils +from google_compute_engine.test_compat import builtin +from google_compute_engine.test_compat import mock +from google_compute_engine.test_compat import unittest + + +class UtilsTest(unittest.TestCase): + + def setUp(self): + self.mock_logger = mock.Mock() + self.mock_setup = mock.create_autospec(utils.Utils) + self.mock_setup.network_path = '/etc/sysconfig/network' + + def testEnableNetworkInterfacesWithSingleNic(self): + mocks = mock.Mock() + + utils.Utils.EnableNetworkInterfaces( + self.mock_setup, ['eth0'], self.mock_logger) + expected_calls = [] + self.assertEqual(mocks.mock_calls, expected_calls) + + def testEnableNetworkInterfacesWithMultipleNics(self): + mocks = mock.Mock() + mocks.attach_mock(self.mock_setup._WriteIfcfg, 'writeIfcfg') + mocks.attach_mock(self.mock_setup._Ifup, 'ifup') + + utils.Utils.EnableNetworkInterfaces( + self.mock_setup, ['eth0', 'eth1', 'eth2'], self.mock_logger) + expected_calls = [ + mock.call.writeIfcfg(['eth1', 'eth2'], mock.ANY), + mock.call.ifup(['eth1', 'eth2'], mock.ANY), + ] + self.assertEqual(mocks.mock_calls, expected_calls) + + def testWriteIfcfg(self): + mocks = mock.Mock() + mock_open = mock.mock_open() + mocks.attach_mock(mock_open, 'open') + with mock.patch('%s.open' % builtin, mock_open, create=False): + + utils.Utils._WriteIfcfg( + self.mock_setup, ['eth1', 'eth2'], self.mock_logger) + expected_calls = [ + mock.call.open('/etc/sysconfig/network/ifcfg-eth1', 'w'), + mock.call.open().__enter__(), + mock.call.open().write(mock.ANY), + mock.call.open().__exit__(None, None, None), + mock.call.open('/etc/sysconfig/network/ifcfg-eth2', 'w'), + mock.call.open().__enter__(), + mock.call.open().write(mock.ANY), + mock.call.open().__exit__(None, None, None), + ] + self.assertEqual(mocks.mock_calls, expected_calls) + + @mock.patch( + 'google_compute_engine.distro.sles_12.utils.subprocess.check_call') + def testIfup(self, mock_call): + mocks = mock.Mock() + mocks.attach_mock(mock_call, 'call') + mocks.attach_mock(self.mock_logger, 'logger') + mock_call.side_effect = [ + None, subprocess.CalledProcessError(1, 'Test'), + ] + + utils.Utils._Ifup(self.mock_setup, ['eth1', 'eth2'], self.mock_logger) + utils.Utils._Ifup(self.mock_setup, ['eth1', 'eth2'], self.mock_logger) + expectedIfupCall = [ + '/usr/sbin/wicked', 'ifup', '--timeout', '1', 'eth1', 'eth2', + ] + expected_calls = [ + mock.call.call(expectedIfupCall), + mock.call.call(expectedIfupCall), + mock.call.logger.warning(mock.ANY, ['eth1', 'eth2']), + ] + self.assertEqual(mocks.mock_calls, expected_calls) diff --git a/google_compute_engine/distro/sles_12/utils.py b/google_compute_engine/distro/sles_12/utils.py new file mode 100644 index 0000000..19fe3e0 --- /dev/null +++ b/google_compute_engine/distro/sles_12/utils.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +# Copyright 2018 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utilities that are distro specific for use on SUSE 12.""" + +import os +import subprocess + +from google_compute_engine import constants +from google_compute_engine.distro import utils + + +class Utils(utils.Utils): + """Utilities used by Linux guest services on SUSE 12.""" + + network_path = constants.LOCALBASE + '/etc/sysconfig/network' + + def EnableNetworkInterfaces( + self, interfaces, logger, dhclient_script=None): + """Enable the list of network interfaces. + + Args: + interfaces: list of string, the output device names to enable. + logger: logger object, used to write to SysLog and serial port. + dhclient_script: string, the path to a dhclient script used by dhclient. + """ + interfaces_to_up = [i for i in interfaces if i != 'eth0'] + if interfaces_to_up: + logger.info('Enabling the Ethernet interfaces %s.', interfaces_to_up) + self._WriteIfcfg(interfaces_to_up, logger) + self._Ifup(interfaces_to_up, logger) + + def _WriteIfcfg(self, interfaces, logger): + """Write ifcfg files for multi-NIC support. + + Overwrites the files. This allows us to update ifcfg-* in the future. + Disable the network setup to override this behavior and customize the + configurations. + + 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: + interface_config = os.path.join( + self.network_path, 'ifcfg-%s' % interface) + interface_content = [ + '# Added by Google.', + 'STARTMODE=hotplug', + 'BOOTPROTO=dhcp', + 'DHCLIENT_SET_DEFAULT_ROUTE=yes', + 'DHCLIENT_ROUTE_PRIORITY=10%s00' % interface, + '', + ] + with open(interface_config, 'w') as interface_file: + interface_file.write('\n'.join(interface_content)) + logger.info('Created ifcfg file for interface %s.', interface) + + def _Ifup(self, interfaces, logger): + """Activate network interfaces. + + Args: + interfaces: list of string, the output device names to enable. + logger: logger object, used to write to SysLog and serial port. + """ + ifup = ['/usr/sbin/wicked', 'ifup', '--timeout', '1'] + try: + subprocess.check_call(ifup + interfaces) + except subprocess.CalledProcessError: + logger.warning('Could not activate interfaces %s.', interfaces) diff --git a/google_compute_engine/tests/compat_test.py b/google_compute_engine/tests/compat_test.py index 6f7671d..da0ceaf 100644 --- a/google_compute_engine/tests/compat_test.py +++ b/google_compute_engine/tests/compat_test.py @@ -68,27 +68,33 @@ class CompatTest(unittest.TestCase): else: pass - @mock.patch('google_compute_engine.compat.platform.linux_distribution') + @mock.patch('google_compute_engine.compat.distro.linux_distribution') def testDistroCompat(self, mock_call): test_cases = { ('debian', '8.10', ''): google_compute_engine.distro.debian_8.utils, ('debian', '9.3', ''): google_compute_engine.distro.debian_9.utils, - ('SUSE Linux Enterprise Server ', '12', 'x86_64'): + ('debian', '10.3', ''): google_compute_engine.distro.debian_9.utils, + ('SUSE Linux Enterprise Server', '11', 'x86_64'): + google_compute_engine.distro.sles_11.utils, + ('SUSE Linux Enterprise Server', '12', 'x86_64'): + google_compute_engine.distro.sles_12.utils, + ('SUSE Linux Enterprise Server', '13', 'x86_64'): + google_compute_engine.distro.sles_12.utils, ('CentOS Linux', '6.4.3', 'Core'): google_compute_engine.distro.el_6.utils, ('CentOS Linux', '7.4.1708', 'Core'): google_compute_engine.distro.el_7.utils, ('CentOS Linux', '8.4.3', 'Core'): - google_compute_engine.distro.debian_9.utils, + google_compute_engine.distro.el_7.utils, ('Red Hat Enterprise Linux Server', '6.3.2', ''): google_compute_engine.distro.el_6.utils, ('Red Hat Enterprise Linux Server', '7.4', ''): google_compute_engine.distro.el_7.utils, ('Red Hat Enterprise Linux Server', '8.5.1', ''): - google_compute_engine.distro.debian_9.utils, + google_compute_engine.distro.el_7.utils, ('', '', ''): google_compute_engine.distro.debian_9.utils, ('xxxx', 'xxxx', 'xxxx'): @@ -99,7 +105,7 @@ class CompatTest(unittest.TestCase): mock_call.return_value = distro reload_import(google_compute_engine.compat) self.assertEqual( - test_cases[distro], google_compute_engine.compat.distro_utils) + test_cases[distro], google_compute_engine.compat.distro_utils) if __name__ == '__main__': @@ -25,7 +25,7 @@ setuptools.setup( author_email='gc-team@google.com', description='Google Compute Engine', include_package_data=True, - install_requires=['boto', 'setuptools'], + install_requires=['boto', 'setuptools', 'distro;python_version>="3.5"'], license='Apache Software License', long_description='Google Compute Engine guest environment.', name='google-compute-engine', @@ -3,6 +3,8 @@ envlist = py26,py27,py32,py33,py34,py35,pypy,pypy3 [testenv] deps = + py35: distro + setuptools>=20 pytest pytest-cov mock @@ -32,6 +34,7 @@ commands = deps = flake8 flake8-import-order + setuptools>=20 commands = flake8 --import-order-style=google |