summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorryanwe <ryanwe@google.com>2018-02-12 12:12:51 -0800
committerGitHub <noreply@github.com>2018-02-12 12:12:51 -0800
commit1de8560846a00fe428b6f2ffcb099b0eaf4e6000 (patch)
tree30dd9f597ecf478ae02285023958dcb6add2a12c
parentc50252a91a13b5dfbd9212f2464a23a93f73057a (diff)
downloadgoogle-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.yml1
-rw-r--r--google_compute_engine/compat.py19
-rw-r--r--google_compute_engine/distro/sles_11/__init__.py0
-rw-r--r--google_compute_engine/distro/sles_11/tests/__init__.py0
-rw-r--r--google_compute_engine/distro/sles_11/tests/utils_test.py73
-rw-r--r--google_compute_engine/distro/sles_11/utils.py60
-rw-r--r--google_compute_engine/distro/sles_12/__init__.py0
-rw-r--r--google_compute_engine/distro/sles_12/tests/__init__.py0
-rw-r--r--google_compute_engine/distro/sles_12/tests/utils_test.py93
-rw-r--r--google_compute_engine/distro/sles_12/utils.py82
-rw-r--r--google_compute_engine/tests/compat_test.py16
-rwxr-xr-xsetup.py2
-rw-r--r--tox.ini3
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__':
diff --git a/setup.py b/setup.py
index 3eee1cb..49b57c4 100755
--- a/setup.py
+++ b/setup.py
@@ -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',
diff --git a/tox.ini b/tox.ini
index d3c13f0..6d543ee 100644
--- a/tox.ini
+++ b/tox.ini
@@ -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