Repository: Azure/WALinuxAgent
Pull request #670 (2017-04-15 17:35:39)

Add Arch distro support
Add Arch distro support. This fixes the issues #653 and #120 .

Looking forward to official Arch images in Azure!


www.archlinux.org


msftclas: 
@__enihcam__,
Thanks for your contribution.
To ensure that the project team has proper rights to use your work, please complete the Contribution License Agreement at https://cla.microsoft.com.

It will cover your contributions to all Microsoft-managed open source projects.
_Thanks,
Microsoft Pull Request Bot_
msftclas: __@enihcam__, thanks for signing the contribution license agreement. We will now validate the agreement and then the pull request.
<br />_Thanks, Microsoft Pull Request Bot_

Patch:
diff --git a/azurelinuxagent/common/osutil/arch.py b/azurelinuxagent/common/osutil/arch.py
new file mode 100644
index 00000000..83d3b47e
--- /dev/null
+++ b/azurelinuxagent/common/osutil/arch.py
@@ -0,0 +1,55 @@
+#
+# Copyright 2014 Microsoft Corporation
+#
+# 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.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import os
+import azurelinuxagent.common.utils.shellutil as shellutil
+from azurelinuxagent.common.osutil.default import DefaultOSUtil
+
+class ArchUtil(DefaultOSUtil):
+    def is_dhcp_enabled(self):
+        return True
+
+    def start_network(self):
+        return shellutil.run("systemctl start systemd-networkd", chk_err=False)
+
+    def restart_if(self, iface):
+        shellutil.run("systemctl restart systemd-networkd")
+
+    def restart_ssh_service(self):
+        # SSH is socket activated on CoreOS.  No need to restart it.
+        pass
+
+    def stop_dhcp_service(self):
+        return shellutil.run("systemctl stop systemd-networkd", chk_err=False)
+
+    def start_dhcp_service(self):
+        return shellutil.run("systemctl start systemd-networkd", chk_err=False)
+
+    def start_agent_service(self):
+        return shellutil.run("systemctl start waagent", chk_err=False)
+
+    def stop_agent_service(self):
+        return shellutil.run("systemctl stop waagent", chk_err=False)
+
+    def get_dhcp_pid(self):
+        ret= shellutil.run_get_output("pidof systemd-networkd")
+        return ret[1] if ret[0] == 0 else None
+
+    def conf_sshd(self, disable_password):
+        # Don't whack the system default sshd conf
+        pass
\ No newline at end of file
diff --git a/azurelinuxagent/common/osutil/factory.py b/azurelinuxagent/common/osutil/factory.py
index eee9f97a..df3a006f 100644
--- a/azurelinuxagent/common/osutil/factory.py
+++ b/azurelinuxagent/common/osutil/factory.py
@@ -19,6 +19,7 @@ import azurelinuxagent.common.logger as logger
 from azurelinuxagent.common.utils.textutil import Version
 from azurelinuxagent.common.version import *
 from .default import DefaultOSUtil
+from .arch import ArchUtil
 from .clearlinux import ClearLinuxUtil
 from .coreos import CoreOSUtil
 from .debian import DebianOSUtil
@@ -35,6 +36,9 @@ def get_osutil(distro_name=DISTRO_NAME,
                distro_version=DISTRO_VERSION,
                distro_full_name=DISTRO_FULL_NAME):
 
+    if distro_name == "arch":
+        return ArchUtil()
+
     if distro_name == "clear linux software for intel architecture":
         return ClearLinuxUtil()
 
diff --git a/azurelinuxagent/common/version.py b/azurelinuxagent/common/version.py
index beb22928..8a81974e 100644
--- a/azurelinuxagent/common/version.py
+++ b/azurelinuxagent/common/version.py
@@ -91,7 +91,7 @@ def get_distro():
 
 AGENT_NAME = "WALinuxAgent"
 AGENT_LONG_NAME = "Azure Linux Agent"
-AGENT_VERSION = '2.2.8'
+AGENT_VERSION = '2.2.9'
 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/update.py b/azurelinuxagent/ga/update.py
index fc19d486..203bb417 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -146,10 +146,19 @@ class UpdateHandler(object):
 
             logger.verbose(u"Agent {0} launched with command '{1}'", agent_name, agent_cmd)
 
+            # If the most current agent is the installed agent and update is enabled,
+            # assume updates are likely available and poll every second.
+            # This reduces the start-up impact of finding / launching agent updates on
+            # fresh VMs.
+            if latest_agent is None and conf.get_autoupdate_enabled():
+                poll_interval = 1
+            else:
+                poll_interval = CHILD_POLL_INTERVAL
+
             ret = None
             start_time = time.time()
             while (time.time() - start_time) < CHILD_HEALTH_INTERVAL:
-                time.sleep(CHILD_POLL_INTERVAL)
+                time.sleep(poll_interval)
                 ret = self.child_process.poll()
                 if ret is not None:
                     break
diff --git a/azurelinuxagent/pa/deprovision/arch.py b/azurelinuxagent/pa/deprovision/arch.py
new file mode 100644
index 00000000..e661f79e
--- /dev/null
+++ b/azurelinuxagent/pa/deprovision/arch.py
@@ -0,0 +1,33 @@
+# Microsoft Azure Linux Agent
+#
+# Copyright 2014 Microsoft Corporation
+#
+# 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.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+import azurelinuxagent.common.utils.fileutil as fileutil
+from azurelinuxagent.pa.deprovision.default import DeprovisionHandler, \
+                                                   DeprovisionAction
+
+class ArchDeprovisionHandler(DeprovisionHandler):
+    def __init__(self):
+        super(ArchDeprovisionHandler, self).__init__()
+
+    def setup(self, deluser):
+        warnings, actions = super(ArchDeprovisionHandler, self).setup(deluser)
+        warnings.append("WARNING! /etc/machine-id will be removed.")
+        files_to_del = ['/etc/machine-id']
+        actions.append(DeprovisionAction(fileutil.rm_files, files_to_del))
+        return warnings, actions
diff --git a/azurelinuxagent/pa/deprovision/factory.py b/azurelinuxagent/pa/deprovision/factory.py
index 72a5be19..5ac35a76 100644
--- a/azurelinuxagent/pa/deprovision/factory.py
+++ b/azurelinuxagent/pa/deprovision/factory.py
@@ -21,6 +21,7 @@ from azurelinuxagent.common.version import DISTRO_NAME, DISTRO_VERSION, \
                                      DISTRO_FULL_NAME
 
 from .default import DeprovisionHandler
+from .arch import ArchDeprovisionHandler
 from .clearlinux import ClearLinuxDeprovisionHandler
 from .coreos import CoreOSDeprovisionHandler
 from .ubuntu import UbuntuDeprovisionHandler
@@ -28,6 +29,8 @@ from .ubuntu import UbuntuDeprovisionHandler
 def get_deprovision_handler(distro_name=DISTRO_NAME, 
                             distro_version=DISTRO_VERSION,
                             distro_full_name=DISTRO_FULL_NAME):
+    if distro_name == "arch":
+        return ArchDeprovisionHandler()
     if distro_name == "ubuntu":
         return UbuntuDeprovisionHandler()
     if distro_name == "coreos":
diff --git a/config/arch/waagent.conf b/config/arch/waagent.conf
new file mode 100644
index 00000000..8bd53291
--- /dev/null
+++ b/config/arch/waagent.conf
@@ -0,0 +1,100 @@
+#
+# Microsoft Azure Linux Agent Configuration
+#
+
+# Enable instance creation
+Provisioning.Enabled=y
+
+# Password authentication for root account will be unavailable.
+Provisioning.DeleteRootPassword=n
+
+# Generate fresh host key pair.
+Provisioning.RegenerateSshHostKeyPair=y
+
+# Supported values are "rsa", "dsa" and "ecdsa".
+Provisioning.SshHostKeyPairType=rsa
+
+# Monitor host name changes and publish changes via DHCP requests.
+Provisioning.MonitorHostName=y
+
+# Decode CustomData from Base64.
+Provisioning.DecodeCustomData=n
+
+# Execute CustomData after provisioning.
+Provisioning.ExecuteCustomData=n
+
+# Algorithm used by crypt when generating password hash.
+#Provisioning.PasswordCryptId=6
+
+# Length of random salt used when generating password hash.
+#Provisioning.PasswordCryptSaltLength=10
+
+# Allow reset password of sys user
+Provisioning.AllowResetSysUser=n
+
+# Format if unformatted. If 'n', resource disk will not be mounted.
+ResourceDisk.Format=y
+
+# File system on the resource disk
+# Typically ext3 or ext4. FreeBSD images should use 'ufs2' here.
+ResourceDisk.Filesystem=ext4
+
+# Mount point for the resource disk
+ResourceDisk.MountPoint=/mnt/resource
+
+# Create and use swapfile on resource disk.
+ResourceDisk.EnableSwap=n
+
+# Size of the swapfile.
+ResourceDisk.SwapSizeMB=0
+
+# Comma-seperated list of mount options. See man(8) for valid options.
+ResourceDisk.MountOptions=None
+
+# Respond to load balancer probes if requested by Windows Azure.
+LBProbeResponder=y
+
+# Enable verbose logging (y|n)
+Logs.Verbose=n
+
+# Root device timeout in seconds.
+OS.RootDeviceScsiTimeout=300
+
+# If "None", the system default version is used.
+OS.OpensslPath=None
+
+# If set, agent will use proxy server to access internet
+#HttpProxy.Host=None
+#HttpProxy.Port=None
+
+# Detect Scvmm environment, default is n
+# DetectScvmmEnv=n
+
+#
+# Lib.Dir=/var/lib/waagent
+
+#
+# DVD.MountPoint=/mnt/cdrom/secure
+
+#
+# Pid.File=/var/run/waagent.pid
+
+#
+# Extension.LogDir=/var/log/azure
+
+#
+# Home.Dir=/home
+
+# Enable RDMA management and set up, should only be used in HPC images
+# OS.EnableRDMA=y
+
+# Enable or disable goal state processing auto-update, default is enabled
+# AutoUpdate.Enabled=y
+
+# Determine the update family, this should not be changed
+# AutoUpdate.GAFamily=Prod
+
+# Determine if the overprovisioning feature is enabled. If yes, hold extension
+# handling until inVMArtifactsProfile.OnHold is false.
+# Default is disabled
+# EnableOverProvisioning=n
diff --git a/init/arch/waagent.service b/init/arch/waagent.service
new file mode 100644
index 00000000..d426eb21
--- /dev/null
+++ b/init/arch/waagent.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=Azure Linux Agent
+Wants=network-online.target sshd.service sshd-keygen.service
+After=network-online.target
+
+ConditionFileIsExecutable=/usr/bin/waagent
+ConditionPathExists=/etc/waagent.conf
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/python -u /usr/bin/waagent -daemon
+Restart=always
+RestartSec=5
+
+[Install]
+WantedBy=multi-user.target
diff --git a/setup.py b/setup.py
index 68f70b6e..9941f325 100755
--- a/setup.py
+++ b/setup.py
@@ -88,6 +88,12 @@ def get_data_files(name, version, fullname):
                 # TODO this is a mitigation to systemctl bug on 7.1
                 set_sysv_files(data_files)
 
+    elif name == 'arch':
+        set_bin_files(data_files, dest="/usr/bin")
+        set_conf_files(data_files, src=["config/arch/waagent.conf"])
+        set_udev_files(data_files)
+        set_systemd_files(data_files, dest='/usr/lib/systemd/system',
+                          src=["init/arch/waagent.service"])
     elif name == 'coreos':
         set_bin_files(data_files, dest="/usr/share/oem/bin")
         set_conf_files(data_files, dest="/usr/share/oem",


Test patch:
diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py
index 46d06735..e7a7af4e 100644
--- a/tests/ga/test_update.py
+++ b/tests/ga/test_update.py
@@ -1040,7 +1040,7 @@ class TestUpdate(UpdateTestCase):
 
         with patch('subprocess.Popen', return_value=mock_child) as mock_popen:
             with patch('time.time', side_effect=mock_time.time):
-                with patch('time.sleep', return_value=mock_time.sleep):
+                with patch('time.sleep', side_effect=mock_time.sleep):
                     self.update_handler.run_latest()
                     self.assertEqual(1, mock_popen.call_count)
 
@@ -1085,6 +1085,22 @@ class TestUpdate(UpdateTestCase):
         self.assertEqual(0, mock_child.wait.call_count)
         return
 
+    def test_run_latest_polls_frequently_if_installed_is_latest(self):
+        mock_child = ChildMock(return_value=0)
+        mock_time = TimeMock(time_increment=CHILD_HEALTH_INTERVAL/2)
+        self._test_run_latest(mock_time=mock_time)
+        self.assertEqual(1, mock_time.sleep_interval)
+        return
+
+    def test_run_latest_polls_moderately_if_installed_not_latest(self):
+        self.prepare_agents()
+
+        mock_child = ChildMock(return_value=0)
+        mock_time = TimeMock(time_increment=CHILD_HEALTH_INTERVAL/2)
+        self._test_run_latest(mock_time=mock_time)
+        self.assertNotEqual(1, mock_time.sleep_interval)
+        return
+
     def test_run_latest_defaults_to_current(self):
         self.assertEqual(None, self.update_handler.get_latest_agent())
 
@@ -1137,7 +1153,6 @@ class TestUpdate(UpdateTestCase):
         return
 
     def test_run_latest_exception_blacklists(self):
-        # logger.add_logger_appender(logger.AppenderType.STDOUT)
         self.prepare_agents()
 
         latest_agent = self.update_handler.get_latest_agent()
@@ -1389,7 +1404,11 @@ class TimeMock(Mock):
         self.time_call_count = 0
         self.time_increment = time_increment
 
-        self.sleep = Mock(return_value=0)
+        self.sleep_interval = None
+        return
+
+    def sleep(self, n):
+        self.sleep_interval = n
         return
 
     def time(self):