Merge branch 'development'
diff --git a/README.md b/README.md
index 940e456..8e1cc3b 100644
--- a/README.md
+++ b/README.md
@@ -247,6 +247,7 @@
InstanceSetup | set_host_keys | `false` skips generating host keys on first boot.
InstanceSetup | set_multiqueue | `false` skips multiqueue driver support.
IpForwarding | ethernet_proto_id | Protocol ID string for daemon added routes.
+MetadataScripts | run_dir | String base directory where metadata scripts are executed.
MetadataScripts | startup | `false` disables startup script execution.
MetadataScripts | shutdown | `false` disables shutdown script execution.
NetworkInterfaces | dhcp_command | String to execute to enable network interfaces.
diff --git a/build_packages.sh b/build_packages.sh
index a2c5cef..6205077 100755
--- a/build_packages.sh
+++ b/build_packages.sh
@@ -42,6 +42,7 @@
fpm \
-s python \
-t "${pkg_type}" \
+ --conflicts 'irqbalance' \
--depends "${depends}" \
--depends 'python-boto' \
--depends 'python-setuptools' \
diff --git a/google_compute_engine/accounts/accounts_utils.py b/google_compute_engine/accounts/accounts_utils.py
index fbdd5ef..09b1761 100644
--- a/google_compute_engine/accounts/accounts_utils.py
+++ b/google_compute_engine/accounts/accounts_utils.py
@@ -167,6 +167,8 @@
Raises:
IOError, raised when there is an exception updating a file.
+ OSError, raised when setting permissions or writing to a read-only
+ file system.
"""
pw_entry = self._GetUser(user)
if not pw_entry:
@@ -302,7 +304,7 @@
try:
self._UpdateAuthorizedKeys(user, ssh_keys)
- except IOError as e:
+ except (IOError, OSError) as e:
message = 'Could not update the authorized keys file for user %s. %s.'
self.logger.warning(message, user, str(e))
return False
diff --git a/google_compute_engine/instance_setup/instance_config.py b/google_compute_engine/instance_setup/instance_config.py
index 675db3a..10e7c71 100644
--- a/google_compute_engine/instance_setup/instance_config.py
+++ b/google_compute_engine/instance_setup/instance_config.py
@@ -62,6 +62,7 @@
'ethernet_proto_id': '66',
},
'MetadataScripts': {
+ 'run_dir': '',
'startup': 'true',
'shutdown': 'true',
},
diff --git a/google_compute_engine/metadata_scripts/script_manager.py b/google_compute_engine/metadata_scripts/script_manager.py
index 2993c36..5cd0ebe 100755
--- a/google_compute_engine/metadata_scripts/script_manager.py
+++ b/google_compute_engine/metadata_scripts/script_manager.py
@@ -29,16 +29,17 @@
@contextlib.contextmanager
-def _CreateTempDir(prefix):
+def _CreateTempDir(prefix, run_dir=None):
"""Context manager for creating a temporary directory.
Args:
prefix: string, the prefix for the temporary directory.
+ run_dir: string, the base directory location of the temporary directory.
Yields:
string, the temporary directory created.
"""
- temp_dir = tempfile.mkdtemp(prefix=prefix + '-')
+ temp_dir = tempfile.mkdtemp(prefix=prefix + '-', dir=run_dir)
try:
yield temp_dir
finally:
@@ -48,11 +49,12 @@
class ScriptManager(object):
"""A class for retrieving and executing metadata scripts."""
- def __init__(self, script_type, debug=False):
+ def __init__(self, script_type, run_dir=None, debug=False):
"""Constructor.
Args:
script_type: string, the metadata script type to run.
+ run_dir: string, the base directory location of the temporary directory.
debug: bool, True if debug output should write to the console.
"""
self.script_type = script_type
@@ -61,11 +63,15 @@
self.logger = logger.Logger(name=name, debug=debug, facility=facility)
self.retriever = script_retriever.ScriptRetriever(self.logger, script_type)
self.executor = script_executor.ScriptExecutor(self.logger, script_type)
- self._RunScripts()
+ self._RunScripts(run_dir=run_dir)
- def _RunScripts(self):
- """Retrieve metadata scripts and execute them."""
- with _CreateTempDir(self.script_type) as dest_dir:
+ def _RunScripts(self, run_dir=None):
+ """Retrieve metadata scripts and execute them.
+
+ Args:
+ run_dir: string, the base directory location of the temporary directory.
+ """
+ with _CreateTempDir(self.script_type, run_dir=run_dir) as dest_dir:
try:
self.logger.info('Starting %s scripts.', self.script_type)
script_dict = self.retriever.GetScripts(dest_dir)
@@ -92,7 +98,10 @@
instance_config = config_manager.ConfigManager()
if instance_config.GetOptionBool('MetadataScripts', script_type):
- ScriptManager(script_type, debug=bool(options.debug))
+ ScriptManager(
+ script_type,
+ run_dir=instance_config.GetOptionString('MetadataScripts', 'run_dir'),
+ debug=bool(options.debug))
if __name__ == '__main__':
diff --git a/google_compute_engine/metadata_scripts/tests/script_manager_test.py b/google_compute_engine/metadata_scripts/tests/script_manager_test.py
index 83fa879..ec7d61f 100644
--- a/google_compute_engine/metadata_scripts/tests/script_manager_test.py
+++ b/google_compute_engine/metadata_scripts/tests/script_manager_test.py
@@ -40,6 +40,7 @@
mocks.attach_mock(mock_executor, 'executor')
mocks.attach_mock(mock_logger, 'logger')
mocks.attach_mock(mock_retriever, 'retriever')
+ run_dir = '/var/run'
script_type = 'test'
script_name = '%s-script' % script_type
script_prefix = '%s-' % script_type
@@ -48,13 +49,13 @@
mock_mkdir.return_value = test_dir
mock_retriever_instance.GetScripts.return_value = test_dict
- script_manager.ScriptManager(script_type)
+ script_manager.ScriptManager(script_type, run_dir=run_dir)
expected_calls = [
mock.call.logger.Logger(
name=script_name, debug=False, facility=mock.ANY),
mock.call.retriever.ScriptRetriever(mock_logger_instance, script_type),
mock.call.executor.ScriptExecutor(mock_logger_instance, script_type),
- mock.call.mkdir(prefix=script_prefix),
+ mock.call.mkdir(prefix=script_prefix, dir=run_dir),
mock.call.logger.Logger().info(mock.ANY, script_type),
mock.call.retriever.ScriptRetriever().GetScripts(test_dir),
mock.call.executor.ScriptExecutor().RunScripts(test_dict),
diff --git a/google_compute_engine/metadata_watcher.py b/google_compute_engine/metadata_watcher.py
index 3ff648d..c24ac81 100644
--- a/google_compute_engine/metadata_watcher.py
+++ b/google_compute_engine/metadata_watcher.py
@@ -48,11 +48,14 @@
while True:
try:
response = func(*args, **kwargs)
- except urlerror.HTTPError as e:
- if e.getcode() == httpclient.SERVICE_UNAVAILABLE:
- time.sleep(5)
- else:
- raise
+ except (httpclient.HTTPException, socket.error, urlerror.URLError) as e:
+ time.sleep(5)
+ if (isinstance(e, urlerror.HTTPError) and
+ e.getcode() == httpclient.SERVICE_UNAVAILABLE):
+ continue
+ elif isinstance(e, socket.timeout):
+ continue
+ raise
else:
if response.getcode() == httpclient.OK:
return response
@@ -173,7 +176,7 @@
continue
else:
exception = e
- self.logger.exception('GET request error retrieving metadata.')
+ self.logger.error('GET request error retrieving metadata. %s.', e)
def WatchMetadata(
self, handler, metadata_key='', recursive=True, timeout=None):
diff --git a/google_compute_engine/tests/metadata_watcher_test.py b/google_compute_engine/tests/metadata_watcher_test.py
index 2245711..e07ea94 100644
--- a/google_compute_engine/tests/metadata_watcher_test.py
+++ b/google_compute_engine/tests/metadata_watcher_test.py
@@ -94,12 +94,14 @@
mock_unavailable = mock.Mock()
mock_unavailable.getcode.return_value = (
metadata_watcher.httpclient.SERVICE_UNAVAILABLE)
+ mock_timeout = metadata_watcher.socket.timeout('Test timeout')
mock_success = mock.Mock()
mock_success.getcode.return_value = metadata_watcher.httpclient.OK
# Retry after a service unavailable error response.
mock_open.open.side_effect = [
metadata_watcher.StatusException(mock_unavailable),
+ mock_timeout,
mock_success,
]
request_url = '%s?' % self.url
@@ -117,14 +119,20 @@
mock.call.proxy({}),
mock.call.opener(mock_handler),
mock.call.open.open(mock_request, timeout=timeout),
+ mock.call.time.sleep(mock.ANY),
+ mock.call.request(request_url, headers=headers),
+ mock.call.proxy({}),
+ mock.call.opener(mock_handler),
+ mock.call.open.open(mock_request, timeout=timeout),
]
self.assertEqual(mocks.mock_calls, expected_calls)
+ @mock.patch('google_compute_engine.metadata_watcher.time')
@mock.patch('google_compute_engine.metadata_watcher.urlrequest.build_opener')
@mock.patch('google_compute_engine.metadata_watcher.urlrequest.ProxyHandler')
@mock.patch('google_compute_engine.metadata_watcher.urlrequest.Request')
def testGetMetadataRequestHttpException(
- self, mock_request, mock_proxy, mock_opener):
+ self, mock_request, mock_proxy, mock_opener, mock_time):
mock_open = mock.Mock()
mock_handler = mock.Mock()
mock_response = mock.Mock()
@@ -263,7 +271,7 @@
timeout=None),
] * 4
self.assertEqual(mock_response.mock_calls, expected_calls)
- expected_calls = [mock.call.exception(mock.ANY)] * 2
+ expected_calls = [mock.call.error(mock.ANY, mock.ANY)] * 2
self.assertEqual(self.mock_logger.mock_calls, expected_calls)
def testWatchMetadata(self):
@@ -285,7 +293,7 @@
mock_response = mock.Mock()
mock_response.side_effect = metadata_watcher.socket.timeout()
self.mock_watcher._GetMetadataUpdate = mock_response
- self.mock_logger.exception.side_effect = RuntimeError()
+ self.mock_logger.error.side_effect = RuntimeError()
metadata_key = 'instance/id'
recursive = False
diff --git a/scripts/set_multiqueue b/scripts/set_multiqueue
index 5c722e3..9dfe45f 100755
--- a/scripts/set_multiqueue
+++ b/scripts/set_multiqueue
@@ -1,5 +1,5 @@
#!/bin/bash
-# Copyright 2016 Google Inc. All Rights Reserved.
+# Copyright 2017 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.
@@ -35,17 +35,17 @@
ethtool -L "${1}" combined "${2}" > /dev/null 2>&1
}
-echo "Running $(basename "$0")."
+echo "Running $(basename $0)."
NET_DEVS=/sys/bus/virtio/drivers/virtio_net/virtio*
# Loop through all the virtionet devices and enable multi-queue
-if [ -x /sbin/ethtool ]; then
+if [ -x "$(command -v ethtool)" ]; then
for dev in $NET_DEVS; do
ETH_DEVS=${dev}/net/*
for eth_dev in $ETH_DEVS; do
eth_dev=$(basename "$eth_dev")
if ! errormsg=$(ethtool -l "$eth_dev" 2>&1); then
- echo "/sbin/ethtool says that $eth_dev does not support virtionet multiqueue: $errormsg."
+ echo "ethtool says that $eth_dev does not support virtionet multiqueue: $errormsg."
continue
fi
num_max_channels=$(ethtool -l "$eth_dev" | grep -m 1 Combined | cut -f2)
@@ -59,7 +59,7 @@
done
done
else
- echo "/sbin/ethtool not found: cannot configure virtionet multiqueue."
+ echo "ethtool not found: cannot configure virtionet multiqueue."
fi
for dev in $NET_DEVS
@@ -68,7 +68,7 @@
irq_dir=/proc/irq/*
for irq in $irq_dir
do
- smp_affinity="${irq}/smp_affinity"
+ smp_affinity="${irq}/smp_affinity_list"
[ ! -f "${smp_affinity}" ] && continue
# Classify this IRQ as virtionet intx, virtionet MSI-X, or non-virtionet
# If the IRQ type is virtionet intx, a subdirectory with the same name as
@@ -78,7 +78,7 @@
# a decimal integer ranging from 0 to K - 1 where K is the number of
# input (output) queues in the virtionet device.
virtionet_intx_dir="${irq}/${dev}"
- virtionet_msix_dir_regex=".*/${dev}-(input|output)\.[0-9]+$"
+ virtionet_msix_dir_regex=".*/${dev}-(input|output)\.([0-9]+)$"
if [ -d "${virtionet_intx_dir}" ]; then
# All virtionet intx IRQs are delivered to CPU 0
echo "Setting ${smp_affinity} to 01 for device ${dev}."
@@ -90,44 +90,46 @@
for entry in ${irq}/${dev}*; do
if [[ "$entry" =~ ${virtionet_msix_dir_regex} ]]; then
virtionet_msix_found=1
+ queue_num=${BASH_REMATCH[2]}
fi
done
affinity_hint="${irq}/affinity_hint"
[ "$virtionet_msix_found" -eq 0 -o ! -f "${affinity_hint}" ] && continue
- # The affinity hint file contains a CPU mask, consisting of
- # groups of up to 8 hexadecimal digits, separated by a comma. Each bit
- # position in the CPU mask hex value specifies whether this interrupt
- # should be delivered to the corresponding CPU. For example, if bits 0
- # and 3 are set in the affinity hint CPU mask hex value, then the
- # interrupt should be delivered to CPUs 0 and 3. The virtionet device
- # driver should set only a single bit in the affinity hint per MSI-X
- # interrupt, ensuring each TX (RX) queue is used only by a single CPU.
- # The virtionet driver will only specify an affinity hint if the number of
- # TX (RX) queues equals the number of online CPUs. If no affinity hint is
- # specified for an IRQ, the affinity hint file will contain all zeros.
- affinity_cpumask=$(cat "${affinity_hint}")
- affinity_hint_enabled=0
- # Parse the affinity hint, skip if mask is invalid or is empty (all-zeros)
- OIFS=${IFS}
- IFS=","
- for cpu_bitmap in ${affinity_cpumask}; do
- bitmap_val=$(printf "%d" "0x${cpu_bitmap}" 2>/dev/null)
- if [ "$?" -ne 0 ]; then
- echo "Invalid affinity hint ${affinity_hint}: ${affinity_cpumask}."
- affinity_hint_enabled=0
- break
- elif [ "${bitmap_val}" -ne 0 ]; then
- affinity_hint_enabled=1
- fi
- done
- IFS=${OIFS}
- if [ "${affinity_hint_enabled}" -eq 0 ]; then
- echo "Cannot set IRQ affinity ${smp_affinity}, affinity hint disabled."
- else
- # Set the IRQ CPU affinity to the virtionet-initialized affinity hint
- echo "Setting ${smp_affinity} to ${affinity_cpumask} for device ${dev}."
- echo "${affinity_cpumask}" > "${smp_affinity}"
- fi
+ # Set the IRQ CPU affinity to the virtionet-initialized affinity hint
+ echo "Setting ${smp_affinity} to ${queue_num} for device ${dev}."
+ echo "${queue_num}" > "${smp_affinity}"
+ real_affinity=`cat ${smp_affinity}`
+ echo "${smp_affinity}: real affinity ${real_affinity}"
done
done
+
+XPS=/sys/class/net/e*/queues/tx*/xps_cpus
+num_cpus=$(nproc)
+
+num_queues=0
+for q in $XPS; do
+ num_queues=$((num_queues + 1))
+done
+
+# If we have more CPUs than queues, then stripe CPUs across tx affinity
+# as CPUNumber % queue_count.
+for q in $XPS; do
+ queue_re=".*tx-([0-9]+).*$"
+ if [[ "$q" =~ ${queue_re} ]]; then
+ queue_num=${BASH_REMATCH[1]}
+ fi
+
+ xps=0
+ for cpu in `seq $queue_num $num_queues $((num_cpus - 1))`; do
+ xps=$((xps | (1 << cpu)))
+ done
+
+ # Linux xps_cpus requires a hex number with commas every 32 bits.
+ # It ignores all bits above # cpus, so unconditionally write a
+ # 64 bit hex value, with a comma between dwords.
+ xps_string=`printf "%08x,%08x" $((xps >> 32 & 0xffffffff)) $((xps & 0xffffffff))`
+
+ echo ${xps_string} > $q
+ printf "Queue %d XPS=%s for %s\n" $queue_num `cat $q` $q
+done | sort -n -k2
diff --git a/setup.py b/setup.py
index 405682a..38c7470 100755
--- a/setup.py
+++ b/setup.py
@@ -32,7 +32,7 @@
packages=setuptools.find_packages(),
scripts=glob.glob('scripts/*'),
url='https://github.com/GoogleCloudPlatform/compute-image-packages',
- version='2.3.2',
+ version='2.3.3',
# Entry points create scripts in /usr/bin that call a function.
entry_points={
'console_scripts': [