Repository: Azure/WALinuxAgent
Pull request #1084 (2018-03-30 02:36:04)

Refactor finding IP address for primary interface


Patch:
diff --git a/azurelinuxagent/common/exception.py b/azurelinuxagent/common/exception.py
index 5960e6a7..bb8ab109 100644
--- a/azurelinuxagent/common/exception.py
+++ b/azurelinuxagent/common/exception.py
@@ -114,6 +114,15 @@ class ProtocolNotFoundError(ProtocolError):
         super(ProtocolNotFoundError, self).__init__(msg, inner)
 
 
+class RestartError(ProtocolError):
+    """
+    Variant of ProtocolError used to restart processing if the GoalState
+    becomes stale.
+    """
+
+    pass
+
+
 class HttpError(AgentError):
     """
     Http request failure
diff --git a/azurelinuxagent/common/future.py b/azurelinuxagent/common/future.py
index e630f26d..8389cd32 100644
--- a/azurelinuxagent/common/future.py
+++ b/azurelinuxagent/common/future.py
@@ -1,13 +1,5 @@
-import platform
 import sys
 
-# Note broken dependency handling to avoid potential backward
-# compatibility issues on different distributions
-try:
-    import distro
-except Exception:
-    pass
-    
 """
 Add alias for python2 and python3 libs and functions.
 """
@@ -32,43 +24,3 @@ elif sys.version_info[0] == 2:
 
 else:
     raise ImportError("Unknown python version: {0}".format(sys.version_info))
-
-
-def get_linux_distribution(get_full_name, supported_dists):
-    """Abstract platform.linux_distribution() call which is deprecated as of
-       Python 3.5 and removed in Python 3.7"""
-    try:
-        supported = platform._supported_dists + (supported_dists,)
-        osinfo = list(
-            platform.linux_distribution(
-                full_distribution_name=get_full_name,
-                supported_dists=supported
-            )
-        )
-        if not osinfo or osinfo == ['','','']:
-            return get_linux_distribution_from_distro(get_full_name)
-        full_name = platform.linux_distribution()[0].strip()
-        osinfo.append(full_name)
-    except AttributeError:
-        return get_linux_distribution_from_distro(get_full_name)
-
-    return osinfo
-
-def get_linux_distribution_from_distro(get_full_name):
-    """Get the distribution information from the distro Python module."""
-    # If we get here we have to have the distro module, thus we do
-    # not wrap the call in a try-except block as it would mask the problem
-    # and result in a broken agent installation
-    osinfo = list(
-        distro.linux_distribution(
-            full_distribution_name=get_full_name
-        )
-    )
-    full_name = distro.linux_distribution()[0].strip()
-    osinfo.append(full_name)
-    return osinfo
-
-
-
-
-    
diff --git a/azurelinuxagent/common/osutil/default.py b/azurelinuxagent/common/osutil/default.py
index 0868783c..982d4a50 100644
--- a/azurelinuxagent/common/osutil/default.py
+++ b/azurelinuxagent/common/osutil/default.py
@@ -80,6 +80,10 @@ UUID_PATTERN = re.compile(
     r'^\s*[A-F0-9]{8}(?:\-[A-F0-9]{4}){3}\-[A-F0-9]{12}\s*$',
     re.IGNORECASE)
 
+IOCTL_SIOCGIFCONF = 0x8912
+IOCTL_SIOCGIFFLAGS = 0x8913
+IOCTL_SIOCGIFHWADDR = 0x8927
+IFNAMSIZ = 16
 
 class DefaultOSUtil(object):
     def __init__(self):
@@ -669,49 +673,71 @@ class DefaultOSUtil(object):
                              socket.SOCK_DGRAM,
                              socket.IPPROTO_UDP)
         param = struct.pack('256s', (ifname[:15]+('\0'*241)).encode('latin-1'))
-        info = fcntl.ioctl(sock.fileno(), 0x8927, param)
+        info = fcntl.ioctl(sock.fileno(), IOCTL_SIOCGIFHWADDR, param)
         return ''.join(['%02X' % textutil.str_to_ord(char) for char in info[18:24]])
 
-    def get_first_if(self):
+    @staticmethod
+    def _get_struct_ifconf_size():
         """
-        Return the interface name, and ip addr of the
-        first active non-loopback interface.
+        Return the sizeof struct ifinfo. On 64-bit platforms the size is 40 bytes;
+        on 32-bit platforms the size is 32 bytes.
         """
-        iface=''
-        expected=16 # how many devices should I expect...
-
-        # for 64bit the size is 40 bytes
-        # for 32bit the size is 32 bytes
         python_arc = platform.architecture()[0]
         struct_size = 32 if python_arc == '32bit' else 40
+        return struct_size
 
-        sock = socket.socket(socket.AF_INET,
-                             socket.SOCK_DGRAM,
-                             socket.IPPROTO_UDP)
-        buff=array.array('B', b'\0' * (expected * struct_size))
-        param = struct.pack('iL',
-                            expected*struct_size,
-                            buff.buffer_info()[0])
-        ret = fcntl.ioctl(sock.fileno(), 0x8912, param)
-        retsize=(struct.unpack('iL', ret)[0])
-        if retsize == (expected * struct_size):
+
+    def _get_all_interfaces(self):
+        """
+        Return a dictionary mapping from interface name to IPv4 address.
+        Interfaces without a name are ignored.
+        """
+        expected=16 # how many devices should I expect...
+        struct_size = DefaultOSUtil._get_struct_ifconf_size()
+        array_size = expected * struct_size
+
+        buff = array.array('B', b'\0' * array_size)
+        param = struct.pack('iL', array_size, buff.buffer_info()[0])
+        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
+        ret = fcntl.ioctl(sock.fileno(), IOCTL_SIOCGIFCONF, param)
+        retsize = (struct.unpack('iL', ret)[0])
+        if retsize == array_size:
             logger.warn(('SIOCGIFCONF returned more than {0} up '
                          'network interfaces.'), expected)
-        sock = buff.tostring()
-        primary = bytearray(self.get_primary_interface(), encoding='utf-8')
-        for i in range(0, struct_size * expected, struct_size):
-            iface=sock[i:i+16].split(b'\0', 1)[0]
-            if len(iface) == 0 or self.is_loopback(iface) or iface != primary:
-                # test the next one
-                if len(iface) != 0 and not self.disable_route_warning:
-                    logger.info('Interface [{0}] skipped'.format(iface))
-                continue
-            else:
-                # use this one
-                logger.info('Interface [{0}] selected'.format(iface))
-                break
+        sock = None
+        ifconf_buff = buff.tostring()
+
+        ifaces = {}
+        for i in range(0, array_size, struct_size):
+            iface = ifconf_buff[i:i+IFNAMSIZ].split(b'\0', 1)[0]
+            if len(iface) > 0:
+                iface_name = iface.decode('latin-1')
+                if iface_name not in ifaces:
+                    ifaces[iface_name] = socket.inet_ntoa(ifconf_buff[i+20:i+24])
+        return ifaces
+
+
+    def get_first_if(self):
+        """
+        Return the interface name, and IPv4 addr of the "primary" interface or,
+        failing that, any active non-loopback interface.
+        """
+        primary = self.get_primary_interface()
+        ifaces = self._get_all_interfaces()
+        if primary in ifaces:
+            return primary, ifaces[primary]
+
+        logger.warn(('Primary interface {0} not found in ifconf list'), primary)
+        for iface_name in ifaces.keys():
+            if not self.is_loopback(iface_name):
+                if not self.disable_route_warning:
+                    logger.info("Choosing non-primary {0}".format(iface_name))
+                return iface_name, ifaces[iface_name]
+
+        msg = 'No non-loopback interface found in ifconf list'
+        logger.warn(msg)
+        raise Exception(msg)
 
-        return iface.decode('latin-1'), socket.inet_ntoa(sock[i+20:i+24])
 
     def get_primary_interface(self):
         """
@@ -783,8 +809,12 @@ class DefaultOSUtil(object):
         return self.get_primary_interface() == ifname
 
     def is_loopback(self, ifname):
+        """
+        Determine if a named interface is loopback.
+        """
         s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
-        result = fcntl.ioctl(s.fileno(), 0x8913, struct.pack('256s', ifname[:15]))
+        ifname_buff = ifname + ('\0'*256)
+        result = fcntl.ioctl(s.fileno(), IOCTL_SIOCGIFFLAGS, ifname_buff)
         flags, = struct.unpack('H', result[16:18])
         isloopback = flags & 8 == 8
         if not self.disable_route_warning:
diff --git a/azurelinuxagent/common/protocol/metadata.py b/azurelinuxagent/common/protocol/metadata.py
index ed4fff98..0cfa5ede 100644
--- a/azurelinuxagent/common/protocol/metadata.py
+++ b/azurelinuxagent/common/protocol/metadata.py
@@ -240,7 +240,7 @@ class MetadataProtocol(Protocol):
             set_properties("extensionHandlers", ext_list.extHandlers, data)
         return ext_list, etag
 
-    def get_ext_handler_pkgs(self, ext_handler):
+    def get_ext_handler_pkgs(self, ext_handler, etag):
         logger.verbose("Get extension handler packages")
         pkg_list = ExtHandlerPackageList()
 
diff --git a/azurelinuxagent/common/protocol/restapi.py b/azurelinuxagent/common/protocol/restapi.py
index 3e6d510b..f3eeff8b 100644
--- a/azurelinuxagent/common/protocol/restapi.py
+++ b/azurelinuxagent/common/protocol/restapi.py
@@ -326,7 +326,7 @@ class Protocol(DataContract):
     def get_ext_handlers(self):
         raise NotImplementedError()
 
-    def get_ext_handler_pkgs(self, extension):
+    def get_ext_handler_pkgs(self, extension, etag):
         raise NotImplementedError()
 
     def get_artifacts_profile(self):
diff --git a/azurelinuxagent/common/protocol/wire.py b/azurelinuxagent/common/protocol/wire.py
index 42a7eb60..850dfedc 100644
--- a/azurelinuxagent/common/protocol/wire.py
+++ b/azurelinuxagent/common/protocol/wire.py
@@ -28,7 +28,7 @@ import azurelinuxagent.common.utils.fileutil as fileutil
 import azurelinuxagent.common.utils.textutil as textutil
 
 from azurelinuxagent.common.exception import ProtocolNotFoundError, \
-                                            ResourceGoneError
+                                            ResourceGoneError, RestartError
 from azurelinuxagent.common.future import httpclient, bytebuffer
 from azurelinuxagent.common.protocol.hostplugin import HostPluginProtocol
 from azurelinuxagent.common.protocol.restapi import *
@@ -145,10 +145,9 @@ class WireProtocol(Protocol):
         # In wire protocol, incarnation is equivalent to ETag
         return ext_conf.ext_handlers, goal_state.incarnation
 
-    def get_ext_handler_pkgs(self, ext_handler):
+    def get_ext_handler_pkgs(self, ext_handler, etag):
         logger.verbose("Get extension handler package")
-        goal_state = self.client.get_goal_state()
-        man = self.client.get_ext_manifest(ext_handler, goal_state)
+        man = self.client.get_ext_manifest(ext_handler, etag)
         return man.pkg_list
 
     def get_artifacts_profile(self):
@@ -800,25 +799,35 @@ class WireClient(object):
                 self.ext_conf = ExtensionsConfig(xml_text)
         return self.ext_conf
 
-    def get_ext_manifest(self, ext_handler, goal_state):
+    def get_ext_manifest(self, ext_handler, incarnation):
+
         for update_goal_state in [False, True]:
             try:
                 if update_goal_state:
                     self.update_goal_state(forced=True)
-                    goal_state = self.get_goal_state()
+                    incarnation = self.get_goal_state().incarnation
 
                 local_file = MANIFEST_FILE_NAME.format(
                                 ext_handler.name,
-                                goal_state.incarnation)
+                                incarnation)
                 local_file = os.path.join(conf.get_lib_dir(), local_file)
-                xml_text = self.fetch_manifest(ext_handler.versionUris)
-                self.save_cache(local_file, xml_text)
+
+                xml_text = None
+                if not update_goal_state:
+                    try:
+                        xml_text = self.fetch_cache(local_file)
+                    except ProtocolError:
+                        pass
+
+                if xml_text is None:
+                    xml_text = self.fetch_manifest(ext_handler.versionUris)
+                    self.save_cache(local_file, xml_text)
                 return ExtensionManifest(xml_text)
 
             except ResourceGoneError:
                 continue
 
-        raise ProtocolError("Failed to retrieve extension manifest")
+        raise RestartError("Failed to retrieve extension manifest")
 
     def filter_package_list(self, family, ga_manifest, goal_state):
         complete_list = ga_manifest.pkg_list
@@ -862,10 +871,6 @@ class WireClient(object):
                     self.update_goal_state(forced=True)
                     goal_state = self.get_goal_state()
 
-                self._remove_stale_agent_manifest(
-                    vmagent_manifest.family,
-                    goal_state.incarnation)
-
                 local_file = MANIFEST_FILE_NAME.format(
                                 vmagent_manifest.family,
                                 goal_state.incarnation)
@@ -880,25 +885,6 @@ class WireClient(object):
 
         raise ProtocolError("Failed to retrieve GAFamily manifest")
 
-    def _remove_stale_agent_manifest(self, family, incarnation):
-        """
-        The incarnation number can reset at any time, which means there
-        could be a stale agentsManifest on disk.  Stale files are cleaned
-        on demand as new goal states arrive from WireServer. If the stale
-        file is not removed agent upgrade may be delayed.
-
-        :param family: GA family, e.g. Prod or Test
-        :param incarnation: incarnation of the current goal state
-        """
-        fn = AGENTS_MANIFEST_FILE_NAME.format(
-            family,
-            incarnation)
-
-        agent_manifest = os.path.join(conf.get_lib_dir(), fn)
-
-        if os.path.exists(agent_manifest):
-            fileutil.rm_files([agent_manifest])
-
     def check_wire_protocol_version(self):
         uri = VERSION_INFO_URI.format(self.endpoint)
         version_info_xml = self.fetch_config(uri, None)
diff --git a/azurelinuxagent/common/version.py b/azurelinuxagent/common/version.py
index 417142cc..f743f115 100644
--- a/azurelinuxagent/common/version.py
+++ b/azurelinuxagent/common/version.py
@@ -23,7 +23,7 @@ import sys
 import azurelinuxagent.common.conf as conf
 import azurelinuxagent.common.utils.fileutil as fileutil
 from azurelinuxagent.common.utils.flexible_version import FlexibleVersion
-from azurelinuxagent.common.future import ustr, get_linux_distribution
+from azurelinuxagent.common.future import ustr
 
 
 def get_f5_platform():
@@ -80,14 +80,14 @@ def get_distro():
     elif 'OpenBSD' in platform.system():
         release = re.sub('\-.*\Z', '', ustr(platform.release()))
         osinfo = ['openbsd', release, '', 'openbsd']
-    elif 'Linux' in platform.system():
-        osinfo = get_linux_distribution(0, 'alpine')
+    elif 'linux_distribution' in dir(platform):
+        supported = platform._supported_dists + ('alpine',)
+        osinfo = list(platform.linux_distribution(full_distribution_name=0,
+                                                  supported_dists=supported))
+        full_name = platform.linux_distribution()[0].strip()
+        osinfo.append(full_name)
     else:
-        try:
-            # dist() removed in Python 3.7
-            osinfo = platform.dist()
-        except:
-            osinfo = ('UNKNOWN', 'FFFF', '')
+        osinfo = platform.dist()
 
     # The platform.py lib has issue with detecting oracle linux distribution.
     # Merge the following patch provided by oracle as a temporary fix.
@@ -113,7 +113,7 @@ def get_distro():
 
 AGENT_NAME = "WALinuxAgent"
 AGENT_LONG_NAME = "Azure Linux Agent"
-AGENT_VERSION = '2.2.25'
+AGENT_VERSION = '2.2.21'
 AGENT_LONG_VERSION = "{0}-{1}".format(AGENT_NAME, AGENT_VERSION)
 AGENT_DESCRIPTION = """
 The Azure Linux Agent supports the provisioning and running of Linux
diff --git a/azurelinuxagent/ga/exthandlers.py b/azurelinuxagent/ga/exthandlers.py
index 6c7357ed..0743c82d 100644
--- a/azurelinuxagent/ga/exthandlers.py
+++ b/azurelinuxagent/ga/exthandlers.py
@@ -38,7 +38,7 @@ import azurelinuxagent.common.version as version
 from azurelinuxagent.common.errorstate import ErrorState, ERROR_STATE_DELTA
 
 from azurelinuxagent.common.event import add_event, WALAEventOperation, elapsed_milliseconds
-from azurelinuxagent.common.exception import ExtensionError, ProtocolError
+from azurelinuxagent.common.exception import ExtensionError, ProtocolError, RestartError
 from azurelinuxagent.common.future import ustr
 from azurelinuxagent.common.protocol.restapi import ExtHandlerStatus, \
                                                     ExtensionStatus, \
@@ -208,6 +208,8 @@ class ExtHandlersHandler(object):
 
             self.report_ext_handlers_status()
             self.cleanup_outdated_handlers()
+        except RestartError:
+            raise
         except Exception as e:
             msg = u"Exception processing extension handlers: {0}".format(
                 ustr(e))
@@ -346,7 +348,7 @@ class ExtHandlersHandler(object):
                 return
 
             self.set_log_upgrade_guid(ext_handler, True)
-            ext_handler_i.decide_version(target_state=state)
+            ext_handler_i.decide_version(etag=etag, target_state=state)
             if not ext_handler_i.is_upgrade and self.last_etag == etag:
                 if self.log_etag:
                     ext_handler_i.logger.verbose("Version {0} is current for etag {1}",
@@ -374,6 +376,11 @@ class ExtHandlersHandler(object):
             else:
                 message = u"Unknown ext handler state:{0}".format(state)
                 raise ExtensionError(message)
+        except RestartError:
+            ext_handler_i.logger.info("GoalState became stale during "
+                                      "processing. Restarting with new "
+                                      "GoalState")
+            raise
         except Exception as e:
             ext_handler_i.set_handler_status(message=ustr(e), code=-1)
             ext_handler_i.report_event(message=ustr(e), is_success=False)
@@ -518,12 +525,9 @@ class ExtHandlerInstance(object):
         self.logger.add_appender(logger.AppenderType.FILE,
                                  logger.LogLevel.INFO, log_file)
 
-    def decide_version(self, target_state=None):
+    def decide_version(self, etag, target_state=None):
         self.logger.verbose("Decide which version to use")
-        try:
-            pkg_list = self.protocol.get_ext_handler_pkgs(self.ext_handler)
-        except ProtocolError as e:
-            raise ExtensionError("Failed to get ext handler pkgs", e)
+        pkg_list = self.protocol.get_ext_handler_pkgs(self.ext_handler, etag)
 
         # Determine the desired and installed versions
         requested_version = FlexibleVersion(
diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py
index 68570549..dcd2955d 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -45,6 +45,7 @@ from azurelinuxagent.common.event import add_event, add_periodic, \
                                     WALAEventOperation
 from azurelinuxagent.common.exception import ProtocolError, \
                                             ResourceGoneError, \
+                                            RestartError, \
                                             UpdateError
 from azurelinuxagent.common.future import ustr
 from azurelinuxagent.common.osutil import get_osutil
diff --git a/setup.py b/setup.py
index 5be2c42b..f59f8292 100755
--- a/setup.py
+++ b/setup.py
@@ -26,7 +26,6 @@ from azurelinuxagent.common.osutil import get_osutil
 import setuptools
 from setuptools import find_packages
 from setuptools.command.install import install as  _install
-import sys
 
 root_dir = os.path.dirname(os.path.abspath(__file__))
 os.chdir(root_dir)
@@ -132,7 +131,7 @@ def get_data_files(name, version, fullname):
             # Ubuntu15.04+ uses systemd
             set_systemd_files(data_files,
                               src=["init/ubuntu/walinuxagent.service"])
-    elif name == 'suse' or name == 'opensuse':
+    elif name == 'suse':
         set_bin_files(data_files)
         set_conf_files(data_files, src=["config/suse/waagent.conf"])
         set_logrotate_files(data_files)
@@ -199,14 +198,6 @@ class install(_install):
             osutil.stop_agent_service()
             osutil.start_agent_service()
 
-# Note to packagers and users from source.
-# In version 3.5 of Python distribution information handling in the platform
-# module was deprecated. Depending on the Linux distribution the
-# implementation may be broken prior to Python 3.7 wher the functionality
-# will be removed from Python 3
-requires = []
-if float(sys.version[:3]) >= 3.7:
-    requires = ['distro']
 
 setuptools.setup(
     name=AGENT_NAME,
@@ -219,7 +210,6 @@ setuptools.setup(
     license='Apache License Version 2.0',
     packages=find_packages(exclude=["tests"]),
     py_modules=["__main__"],
-    install_requires=requires,
     cmdclass={
         'install': install
     }


Test patch:
diff --git a/tests/common/osutil/test_default.py b/tests/common/osutil/test_default.py
index c9fa1dea..481536b8 100644
--- a/tests/common/osutil/test_default.py
+++ b/tests/common/osutil/test_default.py
@@ -97,9 +97,27 @@ class TestOSUtil(AgentTestCase):
         except socket.error:
             self.fail("not a valid ip address")
 
+    def test_get_first_if_nosuchprimary(self):
+        fake_ifaces = {'eth0':'192.0.2.100','lo':'127.0.0.1'}
+        with patch.object(osutil.DefaultOSUtil, 'get_primary_interface', return_value='bogus0'):
+            with patch.object(osutil.DefaultOSUtil, '_get_all_interfaces', return_value=fake_ifaces):
+                ifname, ipaddr = osutil.DefaultOSUtil().get_first_if()
+                self.assertTrue(ifname.startswith('eth'))
+                self.assertTrue(ipaddr is not None)
+                try:
+                    socket.inet_aton(ipaddr)
+                except socket.error:
+                    self.fail("not a valid ip address")
+
+    def test_get_first_if_all_loopback(self):
+        fake_ifaces = {'lo':'127.0.0.1'}
+        with patch.object(osutil.DefaultOSUtil, 'get_primary_interface', return_value='bogus0'):
+            with patch.object(osutil.DefaultOSUtil, '_get_all_interfaces', return_value=fake_ifaces):
+                self.assertRaises(Exception, osutil.DefaultOSUtil().get_first_if)
+
     def test_isloopback(self):
-        self.assertTrue(osutil.DefaultOSUtil().is_loopback(b'lo'))
-        self.assertFalse(osutil.DefaultOSUtil().is_loopback(b'eth0'))
+        self.assertTrue(osutil.DefaultOSUtil().is_loopback('lo'))
+        self.assertFalse(osutil.DefaultOSUtil().is_loopback('eth0'))
 
     def test_isprimary(self):
         routing_table = "\
diff --git a/tests/ga/test_extension.py b/tests/ga/test_extension.py
index d922a263..82667857 100644
--- a/tests/ga/test_extension.py
+++ b/tests/ga/test_extension.py
@@ -645,12 +645,12 @@ class TestExtension(AgentTestCase):
                         decision_version = '1.0.0'
 
                 _, protocol = self._create_mock(WireProtocolData(datafile), *args)
-                ext_handlers, _ = protocol.get_ext_handlers()
+                ext_handlers, etag = protocol.get_ext_handlers()
                 self.assertEqual(1, len(ext_handlers.extHandlers))
                 ext_handler = ext_handlers.extHandlers[0]
                 self.assertEqual('OSTCExtensions.ExampleHandlerLinux', ext_handler.name)
                 self.assertEqual(config_version, ext_handler.properties.version, "config version.")
-                ExtHandlerInstance(ext_handler, protocol).decide_version()
+                ExtHandlerInstance(ext_handler, protocol).decide_version(etag)
                 self.assertEqual(decision_version, ext_handler.properties.version, "decision version.")
 
     def test_ext_handler_version_decide_between_minor_versions(self, *args):
@@ -677,6 +677,7 @@ class TestExtension(AgentTestCase):
         _, protocol = self._create_mock(WireProtocolData(DATA_FILE), *args)
         version_uri = Mock()
         version_uri.uri = 'http://some/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml'
+        incarnation = 1
 
         for (installed_version, config_version, expected_version, autoupgrade_expected_version) in cases:
             ext_handler = Mock()
@@ -688,8 +689,9 @@ class TestExtension(AgentTestCase):
             ext_handler_instance = ExtHandlerInstance(ext_handler, protocol)
             ext_handler_instance.get_installed_version = Mock(return_value=installed_version)
 
-            ext_handler_instance.decide_version()
+            ext_handler_instance.decide_version(incarnation)
             self.assertEqual(expected_version, ext_handler.properties.version)
+            incarnation += 1
 
             ext_handler.properties.version = config_version
             ext_handler.properties.upgradePolicy = 'auto'
@@ -697,8 +699,9 @@ class TestExtension(AgentTestCase):
             ext_handler_instance = ExtHandlerInstance(ext_handler, protocol)
             ext_handler_instance.get_installed_version = Mock(return_value=installed_version)
 
-            ext_handler_instance.decide_version()
+            ext_handler_instance.decide_version(incarnation)
             self.assertEqual(autoupgrade_expected_version, ext_handler.properties.version)
+            incarnation += 1
 
 
 if __name__ == '__main__':
diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py
index e03b3245..0726d4c2 100644
--- a/tests/ga/test_update.py
+++ b/tests/ga/test_update.py
@@ -176,7 +176,7 @@ class UpdateTestCase(AgentTestCase):
         return
 
     def prepare_agents(self,
-                       count=20,
+                       count=5,
                        is_available=True):
 
         # Ensure the test data is copied over
@@ -699,7 +699,7 @@ class TestUpdate(UpdateTestCase):
             pass
         self.event_patch.stop()
 
-    def _create_protocol(self, count=20, versions=None):
+    def _create_protocol(self, count=5, versions=None):
         latest_version = self.prepare_agents(count=count)
         if versions is None or len(versions) <= 0:
             versions = [latest_version]
@@ -861,8 +861,8 @@ class TestUpdate(UpdateTestCase):
         self.update_handler._set_agents([GuestAgent(path=path) for path in self.agent_dirs()])
         self.assertEqual(len(self.agent_dirs()), len(self.update_handler.agents))
 
-        kept_agents = self.update_handler.agents[::2]
-        blacklisted_agents = self.update_handler.agents[1::2]
+        kept_agents = self.update_handler.agents[1::2]
+        blacklisted_agents = self.update_handler.agents[::2]
         for agent in blacklisted_agents:
             agent.mark_failure(is_fatal=True)
         self.update_handler._filter_blacklisted_agents()
@@ -898,7 +898,6 @@ class TestUpdate(UpdateTestCase):
         protocol = WireProtocol('12.34.56.78')
         mock_get_host.return_value = "faux host"
         host = self.update_handler._get_host_plugin(protocol=protocol)
-        print("mock_get_host call cound={0}".format(mock_get_host.call_count))
         mock_get_host.assert_called_once()
         self.assertEqual("faux host", host)
 
@@ -1019,8 +1018,8 @@ class TestUpdate(UpdateTestCase):
         self.assertTrue(2 < len(self.update_handler.agents))
 
         # Purge every other agent
-        kept_agents = self.update_handler.agents[1::2]
-        purged_agents = self.update_handler.agents[::2]
+        purged_agents = self.update_handler.agents[1::2]
+        kept_agents = self.update_handler.agents[::2]
 
         # Reload and assert only the kept agents remain on disk
         self.update_handler.agents = kept_agents
@@ -1331,7 +1330,7 @@ class TestUpdate(UpdateTestCase):
             base_version=FlexibleVersion(AGENT_VERSION),
             protocol=None,
             versions=None,
-            count=20):
+            count=5):
 
         if protocol is None:
             protocol = self._create_protocol(count=count, versions=versions)
@@ -1379,7 +1378,7 @@ class TestUpdate(UpdateTestCase):
     def test_upgrade_available_purges_old_agents(self):
         self.prepare_agents()
         agent_count = self.agent_count()
-        self.assertEqual(20, agent_count)
+        self.assertEqual(5, agent_count)
 
         agent_versions = self.agent_versions()[:3]
         self.assertTrue(self._test_upgrade_available(versions=agent_versions))
diff --git a/tests/protocol/test_metadata.py b/tests/protocol/test_metadata.py
index 5047b86e..5f90f128 100644
--- a/tests/protocol/test_metadata.py
+++ b/tests/protocol/test_metadata.py
@@ -39,7 +39,7 @@ class TestMetadataProtocolGetters(AgentTestCase):
             protocol.get_certs()
             ext_handlers, etag = protocol.get_ext_handlers()
             for ext_handler in ext_handlers.extHandlers:
-                protocol.get_ext_handler_pkgs(ext_handler)
+                protocol.get_ext_handler_pkgs(ext_handler, etag)
 
     def test_getters(self, *args):
         test_data = MetadataProtocolData(DATA_FILE)
diff --git a/tests/protocol/test_wire.py b/tests/protocol/test_wire.py
index 77a08821..9e475ec1 100644
--- a/tests/protocol/test_wire.py
+++ b/tests/protocol/test_wire.py
@@ -43,8 +43,9 @@ class TestWireProtocol(AgentTestCase):
             protocol.get_vminfo()
             protocol.get_certs()
             ext_handlers, etag = protocol.get_ext_handlers()
+            self.assertEqual("1", etag)
             for ext_handler in ext_handlers.extHandlers:
-                protocol.get_ext_handler_pkgs(ext_handler)
+                protocol.get_ext_handler_pkgs(ext_handler, etag)
 
             crt1 = os.path.join(self.tmp_dir,
                                 '33B0ABCE4673538650971C10F7D7397E71561F35.crt')
@@ -93,6 +94,7 @@ class TestWireProtocol(AgentTestCase):
         #    HostingEnvironmentConfig, will be retrieved the expected number
         self.assertEqual(2, test_data.call_counts["hostingenvuri"])
 
+
     def test_call_storage_kwargs(self,
                                  mock_cryptutil,
                                  mock_sleep):