Repository: Azure/WALinuxAgent
Pull request #1036 (2018-02-08 21:05:02)

- Obtain platform information in Python version dependent way
  + The platform module has deprecated functionality in Python 3.5 and
    the functionality will be removed in 3.7. For Python versions 3.5
    and greater depend on the distro module to obtain linux distribution
    information


Patch:
diff --git a/azurelinuxagent/common/event.py b/azurelinuxagent/common/event.py
index 7fc084d9..84a439f5 100644
--- a/azurelinuxagent/common/event.py
+++ b/azurelinuxagent/common/event.py
@@ -254,11 +254,7 @@ __event_logger__ = EventLogger()
 
 
 def elapsed_milliseconds(utc_start):
-    now = datetime.utcnow()
-    if now < utc_start:
-        return 0
-
-    d = now - utc_start
+    d = datetime.utcnow() - utc_start
     return int(((d.days * 24 * 60 * 60 + d.seconds) * 1000) + \
                     (d.microseconds / 1000.0))
 
diff --git a/azurelinuxagent/common/future.py b/azurelinuxagent/common/future.py
index 8389cd32..e630f26d 100644
--- a/azurelinuxagent/common/future.py
+++ b/azurelinuxagent/common/future.py
@@ -1,5 +1,13 @@
+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.
 """
@@ -24,3 +32,43 @@ 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/utils/restutil.py b/azurelinuxagent/common/utils/restutil.py
index c521f63a..807be29c 100644
--- a/azurelinuxagent/common/utils/restutil.py
+++ b/azurelinuxagent/common/utils/restutil.py
@@ -28,7 +28,8 @@ import azurelinuxagent.common.utils.textutil as textutil
 
 from azurelinuxagent.common.exception import HttpError, ResourceGoneError
 from azurelinuxagent.common.future import httpclient, urlparse, ustr
-from azurelinuxagent.common.version import PY_VERSION_MAJOR, AGENT_NAME, GOAL_STATE_AGENT_VERSION
+from azurelinuxagent.common.version import PY_VERSION_MAJOR
+
 
 SECURE_WARNING_EMITTED = False
 
@@ -77,7 +78,6 @@ RETRY_EXCEPTIONS = [
 
 HTTP_PROXY_ENV = "http_proxy"
 HTTPS_PROXY_ENV = "https_proxy"
-HTTP_USER_AGENT = "{0}/{1}".format(AGENT_NAME, GOAL_STATE_AGENT_VERSION)
 
 DEFAULT_PROTOCOL_ENDPOINT='168.63.129.16'
 HOST_PLUGIN_PORT = 32526
@@ -175,13 +175,11 @@ def _http_request(method, host, rel_uri, port=None, data=None, secure=False,
     if port is None:
         port = 443 if secure else 80
 
-    if 'User-Agent' not in headers:
-        headers['User-Agent'] = HTTP_USER_AGENT
-
     if use_proxy:
         conn_host, conn_port = proxy_host, proxy_port
         scheme = "https" if secure else "http"
         url = "{0}://{1}:{2}{3}".format(scheme, host, port, rel_uri)
+
     else:
         conn_host, conn_port = host, port
         url = rel_uri
@@ -192,6 +190,7 @@ def _http_request(method, host, rel_uri, port=None, data=None, secure=False,
                                         timeout=10)
         if use_proxy:
             conn.set_tunnel(host, port)
+
     else:
         conn = httpclient.HTTPConnection(conn_host,
                                         conn_port,
diff --git a/azurelinuxagent/common/version.py b/azurelinuxagent/common/version.py
index f743f115..98e7c8e4 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
+from azurelinuxagent.common.future import ustr, get_linux_distribution
 
 
 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_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)
+    elif 'Linux' in platform.system():
+        osinfo = get_linux_distribution(0, 'alpine')
     else:
-        osinfo = platform.dist()
+        try:
+            # dist() removed in Python 3.7
+            osinfo = platform.dist()
+        except:
+            osinfo = ('UNKNOWN', 'FFFF', '')
 
     # The platform.py lib has issue with detecting oracle linux distribution.
     # Merge the following patch provided by oracle as a temporary fix.
diff --git a/setup.py b/setup.py
index f59f8292..5be2c42b 100755
--- a/setup.py
+++ b/setup.py
@@ -26,6 +26,7 @@ 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)
@@ -131,7 +132,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':
+    elif name == 'suse' or name == 'opensuse':
         set_bin_files(data_files)
         set_conf_files(data_files, src=["config/suse/waagent.conf"])
         set_logrotate_files(data_files)
@@ -198,6 +199,14 @@ 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,
@@ -210,6 +219,7 @@ 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/test_event.py b/tests/common/test_event.py
index 4d9afeff..01bcd7b9 100644
--- a/tests/common/test_event.py
+++ b/tests/common/test_event.py
@@ -17,10 +17,14 @@
 
 from __future__ import print_function
 
-from datetime import datetime, timedelta
+from datetime import datetime
+
+import azurelinuxagent.common.event as event
+import azurelinuxagent.common.logger as logger
 
 from azurelinuxagent.common.event import add_event, \
-    WALAEventOperation, elapsed_milliseconds
+                                    mark_event_status, should_emit_event, \
+                                    WALAEventOperation
 from azurelinuxagent.common.future import ustr
 from azurelinuxagent.common.version import CURRENT_VERSION
 
@@ -213,7 +217,3 @@ class TestEvent(AgentTestCase):
         with open(last_event) as last_fh:
             last_event_text = last_fh.read()
             self.assertTrue('last event' in last_event_text)
-
-    def test_elapsed_milliseconds(self):
-        utc_start = datetime.utcnow() + timedelta(days=1)
-        self.assertEqual(0, elapsed_milliseconds(utc_start))
diff --git a/tests/utils/test_rest_util.py b/tests/utils/test_rest_util.py
index 4f993227..bde0c3d0 100644
--- a/tests/utils/test_rest_util.py
+++ b/tests/utils/test_rest_util.py
@@ -15,11 +15,13 @@
 # Requires Python 2.4+ and Openssl 1.0+
 #
 
+import os
+import unittest
+
 from azurelinuxagent.common.exception import HttpError, \
+                                            ProtocolError, \
                                             ResourceGoneError
-
 import azurelinuxagent.common.utils.restutil as restutil
-from azurelinuxagent.common.utils.restutil import HTTP_USER_AGENT
 
 from azurelinuxagent.common.future import httpclient, ustr
 
@@ -195,7 +197,7 @@ class TestHttpOperations(AgentTestCase):
         ])
         HTTPSConnection.assert_not_called()
         mock_conn.request.assert_has_calls([
-            call(method="GET", url="/bar", body=None, headers={'User-Agent': HTTP_USER_AGENT})
+            call(method="GET", url="/bar", body=None, headers={})
         ])
         mock_conn.getresponse.assert_called_once()
         self.assertNotEquals(None, resp)
@@ -218,7 +220,7 @@ class TestHttpOperations(AgentTestCase):
             call("foo", 443, timeout=10)
         ])
         mock_conn.request.assert_has_calls([
-            call(method="GET", url="/bar", body=None, headers={'User-Agent': HTTP_USER_AGENT})
+            call(method="GET", url="/bar", body=None, headers={})
         ])
         mock_conn.getresponse.assert_called_once()
         self.assertNotEquals(None, resp)
@@ -242,7 +244,7 @@ class TestHttpOperations(AgentTestCase):
         ])
         HTTPSConnection.assert_not_called()
         mock_conn.request.assert_has_calls([
-            call(method="GET", url="http://foo:80/bar", body=None, headers={'User-Agent': HTTP_USER_AGENT})
+            call(method="GET", url="http://foo:80/bar", body=None, headers={})
         ])
         mock_conn.getresponse.assert_called_once()
         self.assertNotEquals(None, resp)
@@ -267,7 +269,7 @@ class TestHttpOperations(AgentTestCase):
             call("foo.bar", 23333, timeout=10)
         ])
         mock_conn.request.assert_has_calls([
-            call(method="GET", url="https://foo:443/bar", body=None, headers={'User-Agent': HTTP_USER_AGENT})
+            call(method="GET", url="https://foo:443/bar", body=None, headers={})
         ])
         mock_conn.getresponse.assert_called_once()
         self.assertNotEquals(None, resp)