summaryrefslogtreecommitdiff
path: root/google_compute_engine
diff options
context:
space:
mode:
authorryanwe <ryanwe@google.com>2018-01-30 13:08:12 -0800
committerMax Illfelder <illfelder@users.noreply.github.com>2018-01-30 13:08:12 -0800
commit1db5329f4a728d1505976d7d87430b9f6da99108 (patch)
tree5b34dccdb2993c8dcc7f79be854ae4980829cfe6 /google_compute_engine
parent8ff26d9198e9c60e6c8416e88ce25f19403d99eb (diff)
downloadgoogle-compute-image-packages-1db5329f4a728d1505976d7d87430b9f6da99108.tar.gz
Create directories in the Python package for distro-specific logic. (#535)
- Finish creating distro directories. - Import correct module based on distro type. - Refactor 'network_setup' to use these new directories.
Diffstat (limited to 'google_compute_engine')
-rw-r--r--google_compute_engine/compat.py23
-rw-r--r--google_compute_engine/distro/__init__.py0
-rw-r--r--google_compute_engine/distro/debian_8/__init__.py0
-rw-r--r--google_compute_engine/distro/debian_8/tests/__init__.py0
-rw-r--r--google_compute_engine/distro/debian_8/tests/utils_test.py37
-rw-r--r--google_compute_engine/distro/debian_8/utils.py34
-rw-r--r--google_compute_engine/distro/debian_9/__init__.py0
-rw-r--r--google_compute_engine/distro/debian_9/tests/__init__.py0
-rw-r--r--google_compute_engine/distro/debian_9/tests/utils_test.py37
-rw-r--r--google_compute_engine/distro/debian_9/utils.py34
-rw-r--r--google_compute_engine/distro/el_6/__init__.py0
-rw-r--r--google_compute_engine/distro/el_6/tests/__init__.py0
-rw-r--r--google_compute_engine/distro/el_6/tests/utils_test.py43
-rw-r--r--google_compute_engine/distro/el_6/utils.py34
-rw-r--r--google_compute_engine/distro/el_7/__init__.py0
-rw-r--r--google_compute_engine/distro/el_7/tests/__init__.py0
-rw-r--r--google_compute_engine/distro/el_7/tests/utils_test.py122
-rw-r--r--google_compute_engine/distro/el_7/utils.py91
-rw-r--r--google_compute_engine/distro/helpers.py42
-rw-r--r--google_compute_engine/distro/tests/helpers_test.py66
-rw-r--r--google_compute_engine/distro/utils.py49
-rwxr-xr-xgoogle_compute_engine/network_setup/network_setup.py76
-rw-r--r--google_compute_engine/network_setup/tests/network_setup_test.py115
-rw-r--r--google_compute_engine/test_compat.py6
-rw-r--r--google_compute_engine/tests/compat_test.py37
-rw-r--r--google_compute_engine/tests/file_utils_test.py2
26 files changed, 671 insertions, 177 deletions
diff --git a/google_compute_engine/compat.py b/google_compute_engine/compat.py
index 00d9d45..bafb1f7 100644
--- a/google_compute_engine/compat.py
+++ b/google_compute_engine/compat.py
@@ -16,9 +16,32 @@
"""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()
+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':
+ 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':
+ 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':
+ import google_compute_engine.distro.debian_9.utils as distro_utils
+else:
+ # Default to Debian 9.
+ import google_compute_engine.distro.debian_9.utils as distro_utils
+
RETRY_LIMIT = 3
TIMEOUT = 10
diff --git a/google_compute_engine/distro/__init__.py b/google_compute_engine/distro/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/google_compute_engine/distro/__init__.py
diff --git a/google_compute_engine/distro/debian_8/__init__.py b/google_compute_engine/distro/debian_8/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/google_compute_engine/distro/debian_8/__init__.py
diff --git a/google_compute_engine/distro/debian_8/tests/__init__.py b/google_compute_engine/distro/debian_8/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/google_compute_engine/distro/debian_8/tests/__init__.py
diff --git a/google_compute_engine/distro/debian_8/tests/utils_test.py b/google_compute_engine/distro/debian_8/tests/utils_test.py
new file mode 100644
index 0000000..0bb6d39
--- /dev/null
+++ b/google_compute_engine/distro/debian_8/tests/utils_test.py
@@ -0,0 +1,37 @@
+#!/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."""
+
+from google_compute_engine.distro.debian_8 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)
+
+ @mock.patch('google_compute_engine.distro.helpers.CallDhclient')
+ def testEnableNetworkInterfaces(self, mock_call):
+ mocks = mock.Mock()
+ mocks.attach_mock(mock_call, 'call')
+
+ utils.Utils.EnableNetworkInterfaces(
+ self.mock_setup, ['A', 'B'], self.mock_logger)
+ expected_calls = [mock.call.call(['A', 'B'], mock.ANY)]
+ self.assertEqual(mocks.mock_calls, expected_calls)
diff --git a/google_compute_engine/distro/debian_8/utils.py b/google_compute_engine/distro/debian_8/utils.py
new file mode 100644
index 0000000..b4aade5
--- /dev/null
+++ b/google_compute_engine/distro/debian_8/utils.py
@@ -0,0 +1,34 @@
+#!/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 Debian 8."""
+
+from google_compute_engine.distro import helpers
+from google_compute_engine.distro import utils
+
+
+class Utils(utils.Utils):
+ """Utilities used by Linux guest services on Debian 8."""
+
+ 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.
+ """
+ helpers.CallDhclient(interfaces, logger)
diff --git a/google_compute_engine/distro/debian_9/__init__.py b/google_compute_engine/distro/debian_9/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/google_compute_engine/distro/debian_9/__init__.py
diff --git a/google_compute_engine/distro/debian_9/tests/__init__.py b/google_compute_engine/distro/debian_9/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/google_compute_engine/distro/debian_9/tests/__init__.py
diff --git a/google_compute_engine/distro/debian_9/tests/utils_test.py b/google_compute_engine/distro/debian_9/tests/utils_test.py
new file mode 100644
index 0000000..633ba9b
--- /dev/null
+++ b/google_compute_engine/distro/debian_9/tests/utils_test.py
@@ -0,0 +1,37 @@
+#!/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."""
+
+from google_compute_engine.distro.debian_9 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)
+
+ @mock.patch('google_compute_engine.distro.helpers.CallDhclient')
+ def testEnableNetworkInterfaces(self, mock_call):
+ mocks = mock.Mock()
+ mocks.attach_mock(mock_call, 'call')
+
+ utils.Utils.EnableNetworkInterfaces(
+ self.mock_setup, ['A', 'B'], self.mock_logger)
+ expected_calls = [mock.call.call(['A', 'B'], mock.ANY)]
+ self.assertEqual(mocks.mock_calls, expected_calls)
diff --git a/google_compute_engine/distro/debian_9/utils.py b/google_compute_engine/distro/debian_9/utils.py
new file mode 100644
index 0000000..f235723
--- /dev/null
+++ b/google_compute_engine/distro/debian_9/utils.py
@@ -0,0 +1,34 @@
+#!/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 Debian 9."""
+
+from google_compute_engine.distro import helpers
+from google_compute_engine.distro import utils
+
+
+class Utils(utils.Utils):
+ """Utilities used by Linux guest services on Debian 9."""
+
+ 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.
+ """
+ helpers.CallDhclient(interfaces, logger)
diff --git a/google_compute_engine/distro/el_6/__init__.py b/google_compute_engine/distro/el_6/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/google_compute_engine/distro/el_6/__init__.py
diff --git a/google_compute_engine/distro/el_6/tests/__init__.py b/google_compute_engine/distro/el_6/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/google_compute_engine/distro/el_6/tests/__init__.py
diff --git a/google_compute_engine/distro/el_6/tests/utils_test.py b/google_compute_engine/distro/el_6/tests/utils_test.py
new file mode 100644
index 0000000..363046c
--- /dev/null
+++ b/google_compute_engine/distro/el_6/tests/utils_test.py
@@ -0,0 +1,43 @@
+#!/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."""
+
+from google_compute_engine.distro.el_6 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 tearDown(self):
+ pass
+
+ @mock.patch('google_compute_engine.distro.helpers.CallDhclient')
+ def testEnableNetworkInterfaces(self, mock_call):
+ mocks = mock.Mock()
+ mocks.attach_mock(mock_call, 'call')
+
+ utils.Utils.EnableNetworkInterfaces(
+ self.mock_setup, ['A', 'B'], self.mock_logger,
+ dhclient_script='test_script')
+ expected_calls = [
+ mock.call.call(['A', 'B'], mock.ANY, dhclient_script='test_script'),
+ ]
+ self.assertEqual(mocks.mock_calls, expected_calls)
diff --git a/google_compute_engine/distro/el_6/utils.py b/google_compute_engine/distro/el_6/utils.py
new file mode 100644
index 0000000..fc70541
--- /dev/null
+++ b/google_compute_engine/distro/el_6/utils.py
@@ -0,0 +1,34 @@
+#!/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 EL 6."""
+
+from google_compute_engine.distro import helpers
+from google_compute_engine.distro import utils
+
+
+class Utils(utils.Utils):
+ """Utilities used by Linux guest services on Debian 8."""
+
+ 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.
+ """
+ helpers.CallDhclient(interfaces, logger, dhclient_script=dhclient_script)
diff --git a/google_compute_engine/distro/el_7/__init__.py b/google_compute_engine/distro/el_7/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/google_compute_engine/distro/el_7/__init__.py
diff --git a/google_compute_engine/distro/el_7/tests/__init__.py b/google_compute_engine/distro/el_7/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/google_compute_engine/distro/el_7/tests/__init__.py
diff --git a/google_compute_engine/distro/el_7/tests/utils_test.py b/google_compute_engine/distro/el_7/tests/utils_test.py
new file mode 100644
index 0000000..95ef298
--- /dev/null
+++ b/google_compute_engine/distro/el_7/tests/utils_test.py
@@ -0,0 +1,122 @@
+#!/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 os
+import shutil
+import tempfile
+
+from google_compute_engine.distro.el_7 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):
+ # Create a temporary directory.
+ self.test_dir = tempfile.mkdtemp()
+ self.mock_logger = mock.Mock()
+ self.mock_setup = mock.create_autospec(utils.Utils)
+ self.mock_setup.network_path = '/etc/sysconfig/network-scripts'
+
+ def tearDown(self):
+ # Remove the directory after the test.
+ shutil.rmtree(self.test_dir)
+
+ def testModifyInterface(self):
+ config_file = os.path.join(self.test_dir, 'config.cfg')
+ config_content = [
+ '# File comment.\n',
+ 'A="apple"\n',
+ 'B=banana\n',
+ 'B=banana\n',
+ ]
+ with open(config_file, 'w') as config:
+ for line in config_content:
+ config.write(line)
+
+ # Write a value for an existing config without overriding it.
+ utils.Utils._ModifyInterface(
+ self.mock_setup, config_file, 'A', 'aardvark', replace=False)
+ self.assertEquals(open(config_file).readlines(), config_content)
+ # Write a value for a config that is not already set.
+ utils.Utils._ModifyInterface(
+ self.mock_setup, config_file, 'C', 'none', replace=False)
+ config_content.append('C=none\n')
+ self.assertEquals(open(config_file).readlines(), config_content)
+ # Write a value for an existing config with replacement.
+ utils.Utils._ModifyInterface(
+ self.mock_setup, config_file, 'A', 'aardvark', replace=True)
+ config_content[1] = 'A=aardvark\n'
+ self.assertEquals(open(config_file).readlines(), config_content)
+ # Write a value for an existing config with multiple occurrences.
+ utils.Utils._ModifyInterface(
+ self.mock_setup, config_file, 'B', '"banana"', replace=True)
+ config_content[2] = config_content[3] = 'B="banana"\n'
+ self.assertEquals(open(config_file).readlines(), config_content)
+
+ @mock.patch('google_compute_engine.distro.el_7.utils.os.path.exists')
+ def testDisableNetworkManager(self, mock_exists):
+ mock_open = mock.mock_open()
+ mocks = mock.Mock()
+ mocks.attach_mock(mock_exists, 'exists')
+ mocks.attach_mock(mock_open, 'open')
+ mocks.attach_mock(self.mock_logger, 'logger')
+ mocks.attach_mock(self.mock_setup._ModifyInterface, 'modify')
+ mock_exists.side_effect = [True, False]
+
+ with mock.patch('%s.open' % builtin, mock_open, create=False):
+ utils.Utils._DisableNetworkManager(
+ self.mock_setup, ['eth0', 'eth1'], self.mock_logger)
+ expected_calls = [
+ mock.call.exists('/etc/sysconfig/network-scripts/ifcfg-eth0'),
+ mock.call.modify(mock.ANY, 'DEVICE', 'eth0', replace=False),
+ mock.call.modify(mock.ANY, 'NM_CONTROLLED', 'no', replace=True),
+ mock.call.exists('/etc/sysconfig/network-scripts/ifcfg-eth1'),
+ mock.call.open('/etc/sysconfig/network-scripts/ifcfg-eth1', 'w'),
+ mock.call.open().__enter__(),
+ mock.call.open().write(mock.ANY),
+ mock.call.open().__exit__(None, None, None),
+ mock.call.logger.info(mock.ANY, 'eth1'),
+ ]
+ self.assertEqual(mocks.mock_calls, expected_calls)
+
+ @mock.patch('google_compute_engine.distro.el_7.utils.os.path.exists')
+ @mock.patch('google_compute_engine.distro.helpers.CallDhclient')
+ def testEnableNetworkInterfaces(self, mock_call, mock_exists):
+ mocks = mock.Mock()
+ mocks.attach_mock(mock_exists, 'exists')
+ mocks.attach_mock(self.mock_logger, 'logger')
+ mocks.attach_mock(self.mock_setup._DisableNetworkManager, 'disable')
+ mocks.attach_mock(mock_call, 'call_dhclient')
+ mock_exists.side_effect = [True, False]
+
+ # Enable interfaces with network manager enabled.
+ utils.Utils.EnableNetworkInterfaces(
+ self.mock_setup, ['A', 'B'], self.mock_logger)
+ # Enable interfaces with network manager is not present.
+ utils.Utils.EnableNetworkInterfaces(
+ self.mock_setup, ['C', 'D'], self.mock_logger)
+ expected_calls = [
+ mock.call.exists('/etc/sysconfig/network-scripts'),
+ mock.call.disable(['A', 'B'], mock.ANY),
+ mock.call.call_dhclient(['A', 'B'], mock.ANY),
+ mock.call.exists('/etc/sysconfig/network-scripts'),
+ mock.call.call_dhclient(['C', 'D'], mock.ANY),
+ ]
+ self.assertEqual(mocks.mock_calls, expected_calls)
diff --git a/google_compute_engine/distro/el_7/utils.py b/google_compute_engine/distro/el_7/utils.py
new file mode 100644
index 0000000..a17cf56
--- /dev/null
+++ b/google_compute_engine/distro/el_7/utils.py
@@ -0,0 +1,91 @@
+#!/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 EL 7."""
+
+import fileinput
+import os
+import re
+
+from google_compute_engine import constants
+from google_compute_engine.distro import helpers
+from google_compute_engine.distro import utils
+
+
+class Utils(utils.Utils):
+ """Utilities used by Linux guest services on EL 7."""
+
+ network_path = constants.LOCALBASE + '/etc/sysconfig/network-scripts'
+
+ 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.
+ """
+ # Should always exist in EL 7.
+ if os.path.exists(self.network_path):
+ self._DisableNetworkManager(interfaces, logger)
+ helpers.CallDhclient(interfaces, logger)
+
+ def _DisableNetworkManager(self, interfaces, logger):
+ """Disable network manager management on a list of network interfaces.
+
+ Args:
+ interfaces: list of string, the output device names 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)
+ if os.path.exists(interface_config):
+ self._ModifyInterface(
+ interface_config, 'DEVICE', interface, replace=False)
+ self._ModifyInterface(
+ interface_config, 'NM_CONTROLLED', 'no', replace=True)
+ else:
+ with open(interface_config, 'w') as interface_file:
+ interface_content = [
+ '# Added by Google.',
+ 'BOOTPROTO=none',
+ 'DEFROUTE=no',
+ 'DEVICE=%s' % interface,
+ 'IPV6INIT=no',
+ 'NM_CONTROLLED=no',
+ 'NOZEROCONF=yes',
+ '',
+ ]
+ interface_file.write('\n'.join(interface_content))
+ logger.info('Created config file for interface %s.', interface)
+
+ def _ModifyInterface(
+ self, interface_config, config_key, config_value, replace=False):
+ """Write a value to a config file if not already present.
+
+ Args:
+ interface_config: string, the path to a config file.
+ config_key: string, the configuration key to set.
+ config_value: string, the value to set for the configuration key.
+ replace: bool, replace the configuration option if already present.
+ """
+ config_entry = '%s=%s' % (config_key, config_value)
+ if not open(interface_config).read().count(config_key):
+ with open(interface_config, 'a') as config:
+ config.write('%s\n' % config_entry)
+ elif replace:
+ for line in fileinput.input(interface_config, inplace=True):
+ print(re.sub(r'%s=.*' % config_key, config_entry, line.rstrip()))
diff --git a/google_compute_engine/distro/helpers.py b/google_compute_engine/distro/helpers.py
new file mode 100644
index 0000000..7e7272c
--- /dev/null
+++ b/google_compute_engine/distro/helpers.py
@@ -0,0 +1,42 @@
+#!/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.
+
+"""Distro helpers."""
+
+import os
+import subprocess
+
+
+def CallDhclient(
+ interfaces, logger, dhclient_script=None):
+ """Configure the network interfaces using dhclient.
+
+ 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.
+ """
+ logger.info('Enabling the Ethernet interfaces %s.', interfaces)
+
+ dhclient_command = ['dhclient']
+
+ if dhclient_script and os.path.exists(dhclient_script):
+ dhclient_command += ['-sf', dhclient_script]
+
+ try:
+ subprocess.check_call(dhclient_command + ['-x'] + interfaces)
+ subprocess.check_call(dhclient_command + interfaces)
+ except subprocess.CalledProcessError:
+ logger.warning('Could not enable interfaces %s.', interfaces)
diff --git a/google_compute_engine/distro/tests/helpers_test.py b/google_compute_engine/distro/tests/helpers_test.py
new file mode 100644
index 0000000..75a366d
--- /dev/null
+++ b/google_compute_engine/distro/tests/helpers_test.py
@@ -0,0 +1,66 @@
+#!/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 helpers.py module."""
+
+import subprocess
+
+from google_compute_engine.distro import helpers
+from google_compute_engine.test_compat import mock
+from google_compute_engine.test_compat import unittest
+
+
+class HelpersTest(unittest.TestCase):
+
+ def setUp(self):
+ self.mock_logger = mock.Mock()
+
+ @mock.patch('google_compute_engine.distro.helpers.os.path.exists')
+ @mock.patch('google_compute_engine.distro.helpers.subprocess.check_call')
+ def testCallDhclient(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')
+
+ mock_exists.side_effect = [False, True]
+ mock_call.side_effect = [
+ None, None, None, None, None, None,
+ subprocess.CalledProcessError(1, 'Test'),
+ ]
+
+ helpers.CallDhclient(['a', 'b'], self.mock_logger, 'test_script')
+ helpers.CallDhclient(['c', 'd'], self.mock_logger, 'test_script')
+ helpers.CallDhclient(['e', 'f'], self.mock_logger, None)
+ helpers.CallDhclient(['g', 'h'], self.mock_logger, None)
+
+ expected_calls = [
+ mock.call.logger.info(mock.ANY, ['a', 'b']),
+ mock.call.exists('test_script'),
+ mock.call.call(['dhclient', '-x', 'a', 'b']),
+ mock.call.call(['dhclient', 'a', 'b']),
+ mock.call.logger.info(mock.ANY, ['c', 'd']),
+ mock.call.exists('test_script'),
+ mock.call.call(['dhclient', '-sf', 'test_script', '-x', 'c', 'd']),
+ mock.call.call(['dhclient', '-sf', 'test_script', 'c', 'd']),
+ mock.call.logger.info(mock.ANY, ['e', 'f']),
+ mock.call.call(['dhclient', '-x', 'e', 'f']),
+ mock.call.call(['dhclient', 'e', 'f']),
+ mock.call.logger.info(mock.ANY, ['g', 'h']),
+ mock.call.call(['dhclient', '-x', 'g', 'h']),
+ mock.call.logger.warning(mock.ANY, ['g', 'h']),
+ ]
+
+ self.assertEqual(mocks.mock_calls, expected_calls)
diff --git a/google_compute_engine/distro/utils.py b/google_compute_engine/distro/utils.py
new file mode 100644
index 0000000..08f856c
--- /dev/null
+++ b/google_compute_engine/distro/utils.py
@@ -0,0 +1,49 @@
+#!/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."""
+
+import logging.handlers
+
+from google_compute_engine import logger as utils_logger
+from google_compute_engine import network_utils
+
+
+class Utils(object):
+ """Utilities used by Linux guest services."""
+
+ def __init__(self, debug=False, logger=None):
+ """Constructor.
+
+ Args:
+ debug: bool, True if debug output should write to the console.
+ logger: logger object, used to write to SysLog and serial port.
+ """
+ self.debug = debug
+ facility = logging.handlers.SysLogHandler.LOG_DAEMON
+ self.logger = logger or utils_logger.Logger(
+ name='google-utils', debug=self.debug, facility=facility)
+ self.network_utils = network_utils.NetworkUtils(logger=self.logger)
+
+ 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.
+ """
+ pass
diff --git a/google_compute_engine/network_setup/network_setup.py b/google_compute_engine/network_setup/network_setup.py
index 8058458..954fda4 100755
--- a/google_compute_engine/network_setup/network_setup.py
+++ b/google_compute_engine/network_setup/network_setup.py
@@ -15,11 +15,8 @@
"""Enables the network interfaces provided in metadata."""
-import fileinput
import logging.handlers
import optparse
-import os
-import re
import subprocess
from google_compute_engine import config_manager
@@ -27,6 +24,7 @@ from google_compute_engine import constants
from google_compute_engine import logger
from google_compute_engine import metadata_watcher
from google_compute_engine import network_utils
+from google_compute_engine.compat import distro_utils
class NetworkSetup(object):
@@ -50,70 +48,9 @@ class NetworkSetup(object):
name='network-setup', debug=debug, facility=facility)
self.watcher = metadata_watcher.MetadataWatcher(logger=self.logger)
self.network_utils = network_utils.NetworkUtils(logger=self.logger)
+ self.distro_utils = distro_utils.Utils(debug=debug)
self._SetupNetworkInterfaces()
- def _ModifyInterface(
- self, interface_config, config_key, config_value, replace=False):
- """Write a value to a config file if not already present.
-
- Args:
- interface_config: string, the path to a config file.
- config_key: string, the configuration key to set.
- config_value: string, the value to set for the configuration key.
- replace: bool, replace the configuration option if already present.
- """
- config_entry = '%s=%s' % (config_key, config_value)
- if not open(interface_config).read().count(config_key):
- with open(interface_config, 'a') as config:
- config.write('%s\n' % config_entry)
- elif replace:
- for line in fileinput.input(interface_config, inplace=True):
- print(re.sub(r'%s=.*' % config_key, config_entry, line.rstrip()))
-
- def _DisableNetworkManager(self, interfaces):
- """Disable network manager management on a list of network interfaces.
-
- Args:
- interfaces: list of string, the output device names enable.
- """
- for interface in interfaces:
- interface_config = os.path.join(self.network_path, 'ifcfg-%s' % interface)
- if os.path.exists(interface_config):
- self._ModifyInterface(
- interface_config, 'DEVICE', interface, replace=False)
- self._ModifyInterface(
- interface_config, 'NM_CONTROLLED', 'no', replace=True)
- else:
- with open(interface_config, 'w') as interface_file:
- interface_content = [
- '# Added by Google.',
- 'BOOTPROTO=none',
- 'DEFROUTE=no',
- 'DEVICE=%s' % interface,
- 'IPV6INIT=no',
- 'NM_CONTROLLED=no',
- 'NOZEROCONF=yes',
- '',
- ]
- interface_file.write('\n'.join(interface_content))
- self.logger.info('Created config file for interface %s.', interface)
-
- def _ConfigureNetwork(self, interfaces):
- """Enable the list of network interfaces.
-
- Args:
- interfaces: list of string, the output device names enable.
- """
- self.logger.info('Enabling the Ethernet interfaces %s.', interfaces)
- dhclient_command = ['dhclient']
- if os.path.exists(self.dhclient_script):
- dhclient_command += ['-sf', self.dhclient_script]
- try:
- subprocess.check_call(dhclient_command + ['-x'] + interfaces)
- subprocess.check_call(dhclient_command + interfaces)
- except subprocess.CalledProcessError:
- self.logger.warning('Could not enable interfaces %s.', interfaces)
-
def _EnableNetworkInterfaces(self, interfaces):
"""Enable the list of network interfaces.
@@ -130,10 +67,11 @@ class NetworkSetup(object):
subprocess.check_call([self.dhcp_command])
except subprocess.CalledProcessError:
self.logger.warning('Could not enable Ethernet interfaces.')
- else:
- if os.path.exists(self.network_path):
- self._DisableNetworkManager(interfaces)
- self._ConfigureNetwork(interfaces)
+ return
+
+ # Distro-specific setup for network interfaces.
+ self.distro_utils.EnableNetworkInterfaces(
+ interfaces, self.logger, dhclient_script=self.dhclient_script)
def _SetupNetworkInterfaces(self):
"""Get network interfaces metadata and enable each Ethernet interface."""
diff --git a/google_compute_engine/network_setup/tests/network_setup_test.py b/google_compute_engine/network_setup/tests/network_setup_test.py
index ad6af9c..072dc00 100644
--- a/google_compute_engine/network_setup/tests/network_setup_test.py
+++ b/google_compute_engine/network_setup/tests/network_setup_test.py
@@ -15,13 +15,11 @@
"""Unittest for network_setup.py module."""
-import os
import shutil
import subprocess
import tempfile
from google_compute_engine.network_setup import network_setup
-from google_compute_engine.test_compat import builtin
from google_compute_engine.test_compat import mock
from google_compute_engine.test_compat import unittest
@@ -37,12 +35,14 @@ class NetworkSetupTest(unittest.TestCase):
self.mock_ip_forwarding_utils = mock.Mock()
self.mock_network_utils = mock.Mock()
self.metadata_key = 'metadata_key'
+ self.mock_distro_utils = mock.Mock()
self.mock_setup = mock.create_autospec(network_setup.NetworkSetup)
self.mock_setup.logger = self.mock_logger
self.mock_setup.watcher = self.mock_watcher
self.mock_setup.network_utils = self.mock_network_utils
self.mock_setup.network_interfaces = self.metadata_key
+ self.mock_setup.distro_utils = self.mock_distro_utils
self.mock_setup.network_path = '/etc/sysconfig/network-scripts'
self.mock_setup.dhclient_script = '/bin/script'
self.mock_setup.dhcp_command = ''
@@ -63,6 +63,7 @@ class NetworkSetupTest(unittest.TestCase):
mocks.attach_mock(mock_network_utils, 'network')
with mock.patch.object(
network_setup.NetworkSetup, '_SetupNetworkInterfaces'):
+
network_setup.NetworkSetup(debug=True)
expected_calls = [
mock.call.logger.Logger(name=mock.ANY, debug=True, facility=mock.ANY),
@@ -71,107 +72,12 @@ class NetworkSetupTest(unittest.TestCase):
]
self.assertEqual(mocks.mock_calls, expected_calls)
- def testModifyInterface(self):
- config_file = os.path.join(self.test_dir, 'config.cfg')
- config_content = [
- '# File comment.\n',
- 'A="apple"\n',
- 'B=banana\n',
- 'B=banana\n',
- ]
- with open(config_file, 'w') as config:
- for line in config_content:
- config.write(line)
-
- # Write a value for an existing config without overriding it.
- network_setup.NetworkSetup._ModifyInterface(
- self.mock_setup, config_file, 'A', 'aardvark', replace=False)
- self.assertEquals(open(config_file).readlines(), config_content)
- # Write a value for a config that is not already set.
- network_setup.NetworkSetup._ModifyInterface(
- self.mock_setup, config_file, 'C', 'none', replace=False)
- config_content.append('C=none\n')
- self.assertEquals(open(config_file).readlines(), config_content)
- # Write a value for an existing config with replacement.
- network_setup.NetworkSetup._ModifyInterface(
- self.mock_setup, config_file, 'A', 'aardvark', replace=True)
- config_content[1] = 'A=aardvark\n'
- self.assertEquals(open(config_file).readlines(), config_content)
- # Write a value for an existing config with multiple occurrences.
- network_setup.NetworkSetup._ModifyInterface(
- self.mock_setup, config_file, 'B', '"banana"', replace=True)
- config_content[2] = config_content[3] = 'B="banana"\n'
- self.assertEquals(open(config_file).readlines(), config_content)
-
- @mock.patch('google_compute_engine.network_setup.network_setup.os.path.exists')
- def testDisableNetworkManager(self, mock_exists):
- mock_open = mock.mock_open()
- mocks = mock.Mock()
- mocks.attach_mock(mock_exists, 'exists')
- mocks.attach_mock(mock_open, 'open')
- mocks.attach_mock(self.mock_logger, 'logger')
- mocks.attach_mock(self.mock_setup._ModifyInterface, 'modify')
- mock_exists.side_effect = [True, False]
-
- with mock.patch('%s.open' % builtin, mock_open, create=False):
- network_setup.NetworkSetup._DisableNetworkManager(
- self.mock_setup, ['eth0', 'eth1'])
- expected_calls = [
- mock.call.exists('/etc/sysconfig/network-scripts/ifcfg-eth0'),
- mock.call.modify(mock.ANY, 'DEVICE', 'eth0', replace=False),
- mock.call.modify(mock.ANY, 'NM_CONTROLLED', 'no', replace=True),
- mock.call.exists('/etc/sysconfig/network-scripts/ifcfg-eth1'),
- mock.call.open('/etc/sysconfig/network-scripts/ifcfg-eth1', 'w'),
- mock.call.open().__enter__(),
- mock.call.open().write(mock.ANY),
- mock.call.open().__exit__(None, None, None),
- mock.call.logger.info(mock.ANY, 'eth1'),
- ]
- self.assertEqual(mocks.mock_calls, expected_calls)
-
@mock.patch('google_compute_engine.network_setup.network_setup.subprocess.check_call')
- @mock.patch('google_compute_engine.network_setup.network_setup.os.path.exists')
- def testConfigureNetwork(self, mock_exists, mock_call):
+ def testEnableNetworkInterfaces(self, mock_call):
mocks = mock.Mock()
- mocks.attach_mock(mock_exists, 'exists')
mocks.attach_mock(mock_call, 'call')
mocks.attach_mock(self.mock_logger, 'logger')
- mock_exists.side_effect = [True, False, False]
- mock_call.side_effect = [
- None, None, None, None, subprocess.CalledProcessError(1, 'Test')]
-
- network_setup.NetworkSetup._ConfigureNetwork(self.mock_setup, ['a', 'b'])
- network_setup.NetworkSetup._ConfigureNetwork(self.mock_setup, ['c'])
- network_setup.NetworkSetup._ConfigureNetwork(self.mock_setup, [])
- expected_calls = [
- # Successfully configure the network using a managed dhclient script.
- mock.call.logger.info(mock.ANY, ['a', 'b']),
- mock.call.exists('/bin/script'),
- mock.call.call(['dhclient', '-sf', '/bin/script', '-x', 'a', 'b']),
- mock.call.call(['dhclient', '-sf', '/bin/script', 'a', 'b']),
- # Successfully configure the network using the default dhclient script.
- mock.call.logger.info(mock.ANY, ['c']),
- mock.call.exists('/bin/script'),
- mock.call.call(['dhclient', '-x', 'c']),
- mock.call.call(['dhclient', 'c']),
- # Exception while enabling the network interface.
- mock.call.logger.info(mock.ANY, []),
- mock.call.exists('/bin/script'),
- mock.call.call(['dhclient', '-x']),
- mock.call.logger.warning(mock.ANY, []),
- ]
- self.assertEqual(mocks.mock_calls, expected_calls)
-
- @mock.patch('google_compute_engine.network_setup.network_setup.subprocess.check_call')
- @mock.patch('google_compute_engine.network_setup.network_setup.os.path.exists')
- def testEnableNetworkInterfaces(self, mock_exists, mock_call):
- mocks = mock.Mock()
- mocks.attach_mock(mock_exists, 'exists')
- mocks.attach_mock(mock_call, 'call')
- mocks.attach_mock(self.mock_logger, 'logger')
- mocks.attach_mock(self.mock_setup._DisableNetworkManager, 'disable')
- mocks.attach_mock(self.mock_setup._ConfigureNetwork, 'configure')
- mock_exists.side_effect = [True, False]
+ mocks.attach_mock(self.mock_setup.distro_utils.EnableNetworkInterfaces, 'enable')
mock_call.side_effect = [None, subprocess.CalledProcessError(1, 'Test')]
# Return immediately with fewer than two interfaces.
@@ -191,13 +97,10 @@ class NetworkSetupTest(unittest.TestCase):
self.mock_setup.dhcp_command = 'failure'
network_setup.NetworkSetup._EnableNetworkInterfaces(
self.mock_setup, ['G', 'H'])
-
expected_calls = [
- mock.call.exists('/etc/sysconfig/network-scripts'),
- mock.call.disable(['A', 'B']),
- mock.call.configure(['A', 'B']),
- mock.call.exists('/etc/sysconfig/network-scripts'),
- mock.call.configure(['C', 'D']),
+ # First calls with empty `interfaces` were no-ops.
+ mock.call.enable(['A', 'B'], mock.ANY, dhclient_script='/bin/script'),
+ mock.call.enable(['C', 'D'], mock.ANY, dhclient_script='/bin/script'),
mock.call.call(['success']),
mock.call.call(['failure']),
mock.call.logger.warning(mock.ANY),
@@ -214,10 +117,10 @@ class NetworkSetupTest(unittest.TestCase):
{'mac': '1'}, {'mac': '2'}, {'mac': '3'}, {}]
self.mock_network_utils.GetNetworkInterface.side_effect = [
'eth0', 'eth1', None, None]
-
with mock.patch.object(
network_setup.NetworkSetup, '_EnableNetworkInterfaces'):
self.mock_setup.dhcp_command = 'command'
+
network_setup.NetworkSetup._SetupNetworkInterfaces(self.mock_setup)
expected_calls = [
mock.call.watcher.GetMetadata(
diff --git a/google_compute_engine/test_compat.py b/google_compute_engine/test_compat.py
index 232be4c..71fb12a 100644
--- a/google_compute_engine/test_compat.py
+++ b/google_compute_engine/test_compat.py
@@ -37,3 +37,9 @@ else:
import unittest2 as unittest
builtin = 'builtins' if sys.version_info >= (3,) else '__builtin__'
+
+# Import the reload module to re-import modules for testing compat.
+if sys.version_info >= (3, 4):
+ from importlib import reload as reload_import
+else:
+ from imp import reload as reload_import
diff --git a/google_compute_engine/tests/compat_test.py b/google_compute_engine/tests/compat_test.py
index a25972f..6f7671d 100644
--- a/google_compute_engine/tests/compat_test.py
+++ b/google_compute_engine/tests/compat_test.py
@@ -13,11 +13,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Unittest for logger.py module."""
+"""Unittest for compat.py module."""
import sys
+import google_compute_engine.compat
from google_compute_engine.test_compat import mock
+from google_compute_engine.test_compat import reload_import
from google_compute_engine.test_compat import unittest
from google_compute_engine.test_compat import urlretrieve
@@ -66,6 +68,39 @@ class CompatTest(unittest.TestCase):
else:
pass
+ @mock.patch('google_compute_engine.compat.platform.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'):
+ google_compute_engine.distro.debian_9.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,
+ ('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.debian_9.utils,
+ ('xxxx', 'xxxx', 'xxxx'):
+ google_compute_engine.distro.debian_9.utils,
+ }
+
+ for distro in test_cases:
+ mock_call.return_value = distro
+ reload_import(google_compute_engine.compat)
+ self.assertEqual(
+ test_cases[distro], google_compute_engine.compat.distro_utils)
+
if __name__ == '__main__':
unittest.main()
diff --git a/google_compute_engine/tests/file_utils_test.py b/google_compute_engine/tests/file_utils_test.py
index 0d67807..1b170a3 100644
--- a/google_compute_engine/tests/file_utils_test.py
+++ b/google_compute_engine/tests/file_utils_test.py
@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Unittest for file_utils_test.py module."""
+"""Unittest for file_utils.py module."""
from google_compute_engine import file_utils
from google_compute_engine.test_compat import mock