Repository: Azure/WALinuxAgent
Pull request #315 (2016-07-12 20:36:50)

Set the umask prior to file creation

msftclas: Hi __@edneville__, I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution!
    <span>
        This seems like a small (but important) contribution, so no Contribution License Agreement is required at this point. Real humans will now evaluate your PR.
    </span>

TTYL, MSBOT;


Patch:
diff --git a/MAINTENANCE.md b/MAINTENANCE.md
new file mode 100644
index 00000000..00f00528
--- /dev/null
+++ b/MAINTENANCE.md
@@ -0,0 +1,12 @@
+## Microsoft Azure Linux Agent Maintenance Guide
+
+### Version rules
+
+  * Production releases are public
+  * Test releases are for internal use
+  * Production versions use only [major].[minor].[revision]
+  * Test versions use [major].[minor].[revision].[build]
+  * Test a.b.c.0 is equivalent to Prod a.b.c
+  * Publishing to Production requires incrementing the revision and dropping the build number
+  * We do not use pre-release labels on any builds
+  
\ No newline at end of file
diff --git a/azurelinuxagent/common/rdma.py b/azurelinuxagent/common/rdma.py
index c9451a21..0c17e388 100644
--- a/azurelinuxagent/common/rdma.py
+++ b/azurelinuxagent/common/rdma.py
@@ -24,9 +24,12 @@ import re
 import time
 import threading
 
+import azurelinuxagent.common.conf as conf
 import azurelinuxagent.common.logger as logger
 import azurelinuxagent.common.utils.fileutil as fileutil
 import azurelinuxagent.common.utils.shellutil as shellutil
+from azurelinuxagent.common.utils.textutil import parse_doc, find, getattrib
+
 
 from azurelinuxagent.common.protocol.wire import SHARED_CONF_FILE_NAME
 
@@ -36,7 +39,7 @@ dapl_config_paths = [
     '/usr/local/etc/dat.conf'
 ]
 
-def setup_rdma_device(self):
+def setup_rdma_device():
     logger.verbose("Parsing SharedConfig XML contents for RDMA details")
     xml_doc = parse_doc(
         fileutil.read_file(os.path.join(conf.get_lib_dir(), SHARED_CONF_FILE_NAME)))
diff --git a/azurelinuxagent/common/version.py b/azurelinuxagent/common/version.py
index 4f3e5972..123d2e95 100644
--- a/azurelinuxagent/common/version.py
+++ b/azurelinuxagent/common/version.py
@@ -50,7 +50,7 @@ def get_distro():
 
 AGENT_NAME = "WALinuxAgent"
 AGENT_LONG_NAME = "Azure Linux Agent"
-AGENT_VERSION = '2.1.5.rc5'
+AGENT_VERSION = '2.1.5.0'
 AGENT_LONG_VERSION = "{0}-{1}".format(AGENT_NAME, AGENT_VERSION)
 AGENT_DESCRIPTION = """\
 The Azure Linux Agent supports the provisioning and running of Linux
@@ -66,6 +66,9 @@ AGENT_NAME_PATTERN = re.compile(AGENT_PATTERN)
 AGENT_DIR_PATTERN = re.compile(".*/{0}".format(AGENT_PATTERN))
 
 
+# Set the CURRENT_AGENT and CURRENT_VERSION to match the agent directory name
+# - This ensures the agent will "see itself" using the same name and version
+#   as the code that downloads agents.
 def set_current_agent():
     path = os.getcwd()
     lib_dir = conf.get_lib_dir()
@@ -80,6 +83,9 @@ def set_current_agent():
     return agent, FlexibleVersion(version)
 CURRENT_AGENT, CURRENT_VERSION = set_current_agent()
 
+def is_current_agent_installed():
+    return CURRENT_AGENT == AGENT_LONG_VERSION
+
 
 __distro__ = get_distro()
 DISTRO_NAME = __distro__[0]
diff --git a/azurelinuxagent/daemon/resourcedisk/default.py b/azurelinuxagent/daemon/resourcedisk/default.py
index d2e400ab..f08c84ef 100644
--- a/azurelinuxagent/daemon/resourcedisk/default.py
+++ b/azurelinuxagent/daemon/resourcedisk/default.py
@@ -200,13 +200,13 @@ class ResourceDiskHandler(object):
 
         # fallocate command
         fn_sh = shellutil.quote((filename,))
-        ret = shellutil.run(u"fallocate -l {0} {1}".format(nbytes, fn_sh))
+        ret = shellutil.run(u"umask 0077 && fallocate -l {0} {1}".format(nbytes, fn_sh))
         if ret != 127:  # 127 = command not found
             return ret
 
         # dd fallback
         dd_maxbs = 64 * 1024 ** 2
-        dd_cmd = "dd if=/dev/zero bs={0} count={1} conv=notrunc of={2}"
+        dd_cmd = "umask 0077 && dd if=/dev/zero bs={0} count={1} conv=notrunc of={2}"
 
         blocks = int(nbytes / dd_maxbs)
         if blocks > 0:
diff --git a/azurelinuxagent/ga/exthandlers.py b/azurelinuxagent/ga/exthandlers.py
index 8ed2f65f..d3c8f329 100644
--- a/azurelinuxagent/ga/exthandlers.py
+++ b/azurelinuxagent/ga/exthandlers.py
@@ -230,7 +230,7 @@ class ExtHandlersHandler(object):
     def report_ext_handlers_status(self):
         """Go thru handler_state dir, collect and report status"""
         vm_status = VMStatus()
-        vm_status.vmAgent.version = AGENT_VERSION
+        vm_status.vmAgent.version = str(CURRENT_VERSION)
         vm_status.vmAgent.status = "Ready"
         vm_status.vmAgent.message = "Guest Agent is running"
 
diff --git a/azurelinuxagent/ga/monitor.py b/azurelinuxagent/ga/monitor.py
index 0ac86d4a..f49cef89 100644
--- a/azurelinuxagent/ga/monitor.py
+++ b/azurelinuxagent/ga/monitor.py
@@ -36,7 +36,8 @@ from azurelinuxagent.common.protocol.restapi import TelemetryEventParam, \
                                                     set_properties
 from azurelinuxagent.common.utils.textutil import parse_doc, findall, find, getattrib
 from azurelinuxagent.common.version import DISTRO_NAME, DISTRO_VERSION, \
-                                            DISTRO_CODE_NAME, AGENT_LONG_VERSION
+                                            DISTRO_CODE_NAME, AGENT_LONG_VERSION, \
+                                            CURRENT_AGENT, CURRENT_VERSION
 
 
 def parse_event(data_str):
@@ -106,7 +107,7 @@ class MonitorHandler(object):
                                                  platform.release())
         self.sysinfo.append(TelemetryEventParam("OSVersion", osversion))
         self.sysinfo.append(
-            TelemetryEventParam("GAVersion", AGENT_LONG_VERSION))
+            TelemetryEventParam("GAVersion", CURRENT_AGENT))
 
         try:
             ram = self.osutil.get_total_mem()
diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py
index 749071d6..e89608a0 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -44,7 +44,8 @@ from azurelinuxagent.common.utils.flexible_version import FlexibleVersion
 from azurelinuxagent.common.version import AGENT_NAME, AGENT_VERSION, AGENT_LONG_VERSION, \
                                             AGENT_DIR_GLOB, AGENT_PKG_GLOB, \
                                             AGENT_PATTERN, AGENT_NAME_PATTERN, AGENT_DIR_PATTERN, \
-                                            CURRENT_AGENT, CURRENT_VERSION
+                                            CURRENT_AGENT, CURRENT_VERSION, \
+                                            is_current_agent_installed
 
 from azurelinuxagent.ga.exthandlers import HandlerManifest
 
@@ -52,8 +53,10 @@ from azurelinuxagent.ga.exthandlers import HandlerManifest
 AGENT_ERROR_FILE = "error.json" # File name for agent error record
 AGENT_MANIFEST_FILE = "HandlerManifest.json"
 
+CHILD_HEALTH_INTERVAL = 15 * 60
 CHILD_LAUNCH_INTERVAL = 5 * 60
 CHILD_LAUNCH_RESTART_MAX = 3
+CHILD_POLL_INTERVAL = 60
 
 MAX_FAILURE = 3 # Max failure allowed for agent before blacklisted
 
@@ -87,6 +90,7 @@ class UpdateHandler(object):
         self.child_launch_time = None
         self.child_launch_attempts = 0
         self.child_process = None
+
         self.signal_handler = None
         return
 
@@ -98,25 +102,29 @@ class UpdateHandler(object):
         Note:
         - Most events should be tagged to the launched agent (agent_version)
         """
+
+        if self.child_process is not None:
+            raise Exception("Illegal attempt to launch multiple goal state Agent processes")
+
+        if self.signal_handler is None:
+            self.signal_handler = signal.signal(signal.SIGTERM, self.forward_signal)
+
         latest_agent = self.get_latest_agent()
-        if latest_agent == None:
+        if latest_agent is None:
+            logger.info(u"Installed Agent {0} is the most current agent", CURRENT_AGENT)
             agent_cmd = "python -u {0} -run-exthandlers".format(sys.argv[0])
             agent_dir = os.getcwd()
             agent_name = CURRENT_AGENT
             agent_version = CURRENT_VERSION
         else:
+            logger.info(u"Determined Agent {0} to be the latest agent", latest_agent.name)
             agent_cmd = latest_agent.get_agent_cmd()
             agent_dir = latest_agent.get_agent_dir()
             agent_name = latest_agent.name
             agent_version = latest_agent.version
 
-        if self.child_process is not None:
-            raise Exception("Illegal attempt to launch multiple child processes")
-
         try:
 
-            self.signal_handler = signal.signal(signal.SIGTERM, self.forward_signal)
-
             # Launch the correct Python version for python-based agents
             cmds = shlex.split(agent_cmd)
             if cmds[0].lower() == "python":
@@ -131,41 +139,55 @@ class UpdateHandler(object):
                 stdout=sys.stdout,
                 stderr=sys.stderr)
 
-            msg = u"Agent {0} launched with command '{1}'".format(agent_name, agent_cmd)
-            logger.info(msg)
-            add_event(AGENT_NAME, version=agent_version, message=msg)
+            logger.info(u"Agent {0} launched with command '{1}'", agent_name, agent_cmd)
 
-            ret = self.child_process.wait()
-            if ret == None:
-                ret = 1
-            if ret > 0:
-                msg = u"Agent {0} launched with command '{1}' failed with code: {2}".format(
+            ret = None
+            start_time = time.time()
+            while (time.time() - start_time) < CHILD_HEALTH_INTERVAL:
+                time.sleep(CHILD_POLL_INTERVAL)
+                ret = self.child_process.poll()
+                if ret is not None:
+                    break
+
+            if ret is None or ret <= 0:
+                msg = u"Agent {0} launched with command '{1}' is successfully running".format(
                     agent_name,
-                    agent_cmd,
-                    ret)
-                logger.warn(msg)
+                    agent_cmd)
+                logger.info(msg)
                 add_event(
                     AGENT_NAME,
                     version=agent_version,
                     op=WALAEventOperation.Enable,
-                    is_success=False,
+                    is_success=True,
                     message=msg)
-                if latest_agent is not None:
-                    latest_agent.mark_failure()
+
+                if ret is None:
+                    ret = self.child_process.wait()
+
             else:
-                msg = u"Agent {0} launched with command '{1}' returned {2}".format(
+                msg = u"Agent {0} launched with command '{1}' failed with return code: {2}".format(
                     agent_name,
                     agent_cmd,
                     ret)
-                logger.info(msg)
+                logger.warn(msg)
                 add_event(
                     AGENT_NAME,
                     version=agent_version,
                     op=WALAEventOperation.Enable,
-                    is_success=True,
+                    is_success=False,
                     message=msg)
+
+            if ret is not None and ret > 0:
+                msg = u"Agent {0} launched with command '{1}' returned code: {2}".format(
+                    agent_name,
+                    agent_cmd,
+                    ret)
+                logger.warn(msg)
+                if latest_agent is not None:
+                    latest_agent.mark_failure()
+
         except Exception as e:
-            msg = u"Agent {0} launch failed with command '{1}' failed with exception: {2}".format(
+            msg = u"Agent {0} launched with command '{1}' failed with exception: {2}".format(
                 agent_name,
                 agent_cmd,
                 ustr(e))
@@ -178,14 +200,6 @@ class UpdateHandler(object):
                 message=msg)
             if latest_agent is not None:
                 latest_agent.mark_failure(is_fatal=True)
-                msg = u"Agent {0} is blacklisted".format(agent_name)
-                logger.info(msg)
-                add_event(
-                    AGENT_NAME,
-                    version=agent_version,
-                    op=WALAEventOperation.Enable,
-                    is_success=False,
-                    message=msg)
 
         self.child_process = None
         return
@@ -195,10 +209,7 @@ class UpdateHandler(object):
         This is the main loop which watches for agent and extension updates.
         """
 
-        msg = u"Agent {0} is running as the current agent".format(
-            CURRENT_AGENT)
-        logger.info(msg)
-        add_event(AGENT_NAME, version=CURRENT_VERSION, is_success=True, message=msg)
+        logger.info(u"Agent {0} is running as the goal state agent", CURRENT_AGENT)
 
         # Launch monitoring threads
         from azurelinuxagent.ga.monitor import get_monitor_handler
@@ -213,34 +224,20 @@ class UpdateHandler(object):
         # TODO: Add means to stop running
         try:
             while self.running:
-                # Check for a new agent.
-                # If a new agent exists (that is, ensure_latest_agent returns
-                # true), exit to allow the daemon to respawn using that agent.
                 if self._ensure_latest_agent():
-                    msg = u"Agent {0} discovered agent update and will exit".format(
-                        CURRENT_AGENT)
-                    logger.info(msg)
-                    add_event(
-                        AGENT_NAME,
-                        version=CURRENT_VERSION,
-                        is_success=True,
-                        message=msg)
+                    if len(self.agents) > 0:
+                        logger.info(
+                            u"Agent {0} discovered {1} as an update and will exit",
+                            CURRENT_AGENT,
+                            self.agents[0].name)
                     break
 
-                # Process extensions
                 exthandlers_handler.run()
                 
                 time.sleep(25)
 
         except Exception as e:
-            msg = u"Agent {0} failed with exception: {1}".format(CURRENT_AGENT, ustr(e))
-            logger.warn(msg)
-            add_event(
-                AGENT_NAME,
-                version=CURRENT_VERSION,
-                op=WALAEventOperation.Enable,
-                is_success=False,
-                message=msg)
+            logger.warn(u"Agent {0} failed with exception: {1}", CURRENT_AGENT, ustr(e))
             sys.exit(1)
 
         sys.exit(0)
@@ -250,9 +247,14 @@ class UpdateHandler(object):
         if self.child_process is None:
             return
         
+        logger.info(
+            u"Agent {0} forwarding signal {1} to {2}",
+            CURRENT_AGENT,
+            signum,
+            self.child_agent.name if self.child_agent is not None else CURRENT_AGENT)
         self.child_process.send_signal(signum)
 
-        if not self.signal_handler in (None, signal.SIG_IGN, signal.SIG_DFL):
+        if self.signal_handler not in (None, signal.SIG_IGN, signal.SIG_DFL):
             self.signal_handler(signum, frame)
         elif self.signal_handler is signal.SIG_DFL:
             if signum == signal.SIGTERM:
@@ -286,6 +288,9 @@ class UpdateHandler(object):
         if next_attempt_time > now:
             return False
 
+        family = conf.get_autoupdate_gafamily()
+        logger.info("Checking for agent family {0} updates", family)
+
         self.last_attempt_time = now
         try:
             protocol = self.protocol_util.get_protocol()
@@ -295,49 +300,43 @@ class UpdateHandler(object):
             logger.warn(msg)
             add_event(
                 AGENT_NAME,
+                op=WALAEventOperation.Download,
                 version=CURRENT_VERSION,
                 is_success=False,
                 message=msg)
             return False
 
         if self.last_etag is not None and self.last_etag == etag:
-            msg = u"Incarnation {0} has no agent updates".format(etag)
-            logger.info(msg)
-            add_event(AGENT_NAME, version=CURRENT_VERSION, message=msg)
+            logger.info(u"Incarnation {0} has no agent updates", etag)
             return False
 
-        logger.info("Check for agent updates")
-
-        family = conf.get_autoupdate_gafamily()
         manifests = [m for m in manifest_list.vmAgentManifests if m.family == family]
         if len(manifests) == 0:
-            msg = u"Incarnation {0} has no agent family {1} updates".format(etag, family)
-            logger.info(msg)
-            add_event(AGENT_NAME, version=CURRENT_VERSION, message=msg)
+            logger.info(u"Incarnation {0} has no agent family {1} updates", etag, family)
             return False
 
         try:
             pkg_list = protocol.get_vmagent_pkgs(manifests[0])
         except ProtocolError as e:
-            msg= u"Incarnation {0} failed to get {1} package list: {1}".format(etag,
-                                                                               family,
-                                                                               ustr(e))
+            msg= u"Incarnation {0} failed to get {1} package list: {2}".format(
+                etag,
+                family,
+                ustr(e))
             logger.warn(msg)
             add_event(
                 AGENT_NAME,
+                op=WALAEventOperation.Download,
                 version=CURRENT_VERSION,
                 is_success=False,
                 message=msg)
             return False
 
         # Set the agents to those available for download at least as current as the existing agent
-        # and remove from disk any agent no longer report to the VM.
+        # and remove from disk any agent no longer reported to the VM.
         # Note:
         #  The code leaves on disk available, but blacklisted, agents so as to preserve the state.
         #  Otherwise, those agents could be again downloaded and inappropriately retried.
-        self._set_agents([GuestAgent(pkg=pkg) for pkg in
-                            [pkg for pkg in pkg_list.versions
-                                if FlexibleVersion(pkg.version) > base_version]])
+        self._set_agents([GuestAgent(pkg=pkg) for pkg in pkg_list.versions])
         self._purge_agents()
         self._filter_blacklisted_agents()
 
@@ -350,6 +349,7 @@ class UpdateHandler(object):
         too frequently, raise an Exception to force blacklisting.
         """
         if latest_agent is None:
+            self.child_agent = None
             return
 
         if self.child_agent is None or latest_agent.version != self.child_agent.version:
@@ -386,12 +386,7 @@ class UpdateHandler(object):
                                     for agent_dir in glob.iglob(path) if os.path.isdir(agent_dir)])
                 self._filter_blacklisted_agents()
             except Exception as e:
-                msg = u"Exception occurred loading available agents: {0}".format(ustr(e))
-                add_event(
-                    AGENT_NAME,
-                    version=CURRENT_VERSION,
-                    is_success=False,
-                    message=msg)
+                logger.warn(u"Exception occurred loading available agents: {0}", ustr(e))
         return
 
     def _purge_agents(self):
@@ -400,25 +395,27 @@ class UpdateHandler(object):
         (without removing the current, running agent).
         """
         path = os.path.join(conf.get_lib_dir(), "{0}-*".format(AGENT_NAME))
+
         known_versions = [agent.version for agent in self.agents]
-        known_versions.append(CURRENT_VERSION)
+        if not is_current_agent_installed() and CURRENT_VERSION not in known_versions:
+            logger.warn(
+                u"Running Agent {0} was not found in the agent manifest - adding to list",
+                CURRENT_VERSION)
+            known_versions.append(CURRENT_VERSION)
+
         for agent_path in glob.iglob(path):
             try:
                 name = fileutil.trim_ext(agent_path, "zip")
                 m = AGENT_DIR_PATTERN.match(name)
-                if m is not None and not FlexibleVersion(m.group(1)) in known_versions:
+                if m is not None and FlexibleVersion(m.group(1)) not in known_versions:
                     if os.path.isfile(agent_path):
+                        logger.info(u"Purging outdated Agent file {0}", agent_path)
                         os.remove(agent_path)
                     else:
+                        logger.info(u"Purging outdated Agent directory {0}", agent_path)
                         shutil.rmtree(agent_path)
             except Exception as e:
-                msg = u"Exception purging {0}: {1}".format(agent_path, ustr(e))
-                logger.warn(msg)
-                add_event(
-                    AGENT_NAME,
-                    version=CURRENT_VERSION,
-                    is_success=False,
-                    message=msg)
+                logger.warn(u"Purging {0} raised exception: {1}", agent_path, ustr(e))
         return
 
     def _set_agents(self, agents=[]):
@@ -430,6 +427,7 @@ class UpdateHandler(object):
 class GuestAgent(object):
     def __init__(self, path=None, pkg=None):
         self.pkg = pkg
+        version = None
         if path is not None:
             m = AGENT_DIR_PATTERN.match(path)
             if m == None:
@@ -442,9 +440,13 @@ class GuestAgent(object):
             raise UpdateError(u"Illegal agent version: {0}".format(version))
         self.version = FlexibleVersion(version)
 
+        location = u"disk" if path is not None else u"package"
+        logger.info(u"Instantiating Agent {0} from {1}", self.name, location)
+
         self.error = None
         self._load_error()
         self._ensure_downloaded()
+        return
 
     @property
     def name(self):
@@ -479,33 +481,30 @@ class GuestAgent(object):
 
     @property
     def is_downloaded(self):
-        return os.path.isfile(self.get_agent_manifest_path())
+        return self.is_blacklisted or os.path.isfile(self.get_agent_manifest_path())
 
     def mark_failure(self, is_fatal=False):
         try:
             if not os.path.isdir(self.get_agent_dir()):
                 os.makedirs(self.get_agent_dir())
-            self.error.mark_failure(is_fatal)
+            self.error.mark_failure(is_fatal=is_fatal)
             self.error.save()
             if is_fatal:
-                msg = u"Agent {0} is permanently blacklisted".format(self.name)
-                logger.warn(msg)
-                add_event(AGENT_NAME, version=self.version, is_success=False, message=msg)
+                logger.warn(u"Agent {0} is permanently blacklisted", self.name)
         except Exception as e:
-            msg = u"Agent {0} failed recording error state: {1}".format(ustr(e))
-            logger.warn(msg)
-            add_event(AGENT_NAME, version=self.version, is_success=False, message=msg)
+            logger.warn(u"Agent {0} failed recording error state: {1}", self.name, ustr(e))
         return
 
     def _ensure_downloaded(self):
         try:
+            logger.info(u"Ensuring Agent {0} is downloaded", self.name)
+
             if self.is_blacklisted:
-                msg = u"Agent {0} is blacklisted - skipping download".format(self.name)
-                logger.info(msg)
-                add_event(AGENT_NAME, version=self.version, is_success=True, message=msg)
+                logger.info(u"Agent {0} is blacklisted - skipping download", self.name)
                 return
 
             if self.is_downloaded:
+                logger.info(u"Agent {0} was previously downloaded - skipping download", self.name)
                 self._load_manifest()
                 return
 
@@ -544,9 +543,6 @@ class GuestAgent(object):
         return
 
     def _download(self):
-        msg = u"Initiating download of Agent {0}".format(self.name)
-        logger.info(msg)
-        add_event(AGENT_NAME, version=self.version, message=msg)
         package = None
 
         for uri in self.pkg.uris:
@@ -555,14 +551,19 @@ class GuestAgent(object):
                 if resp.status == restutil.httpclient.OK:
                     package = resp.read()
                     fileutil.write_file(self.get_agent_pkg_path(), bytearray(package), asbin=True)
+                    logger.info(u"Agent {0} downloaded from {1}", self.name, uri.uri)
                     break
             except restutil.HttpError as e:
-                msg = u"Agent {0} download from {1} failed".format(self.name, uri.uri)
-                logger.warn(msg)
-                add_event(AGENT_NAME, version=self.version, is_success=False, message=msg)
+                logger.warn(u"Agent {0} download from {1} failed", self.name, uri.uri)
 
         if not os.path.isfile(self.get_agent_pkg_path()):
             msg = u"Unable to download Agent {0} from any URI".format(self.name)
+            add_event(
+                AGENT_NAME,
+                op=WALAEventOperation.Download,
+                version=CURRENT_VERSION,
+                is_success=False,
+                message=msg)
             raise UpdateError(msg)
         return
 
@@ -571,15 +572,12 @@ class GuestAgent(object):
             if self.error is None:
                 self.error = GuestAgentError(self.get_agent_error_file())
             self.error.load()
+            logger.info(u"Agent {0} error state: {1}", self.name, ustr(self.error))
         except Exception as e:
-            msg = u"Agent {0} failed loading error state: {1}".format(ustr(e))
-            logger.warn(msg)
-            add_event(AGENT_NAME, version=self.version, is_success=False, message=msg)
+            logger.warn(u"Agent {0} failed loading error state: {1}", self.name, ustr(e))
         return
 
     def _load_manifest(self):
-        logger.info(u"Loading Agent manifest from {0}", self.get_agent_manifest_path())
-
         path = self.get_agent_manifest_path()
         if not os.path.isfile(path):
             msg = u"Agent {0} is missing the {1} file".format(self.name, AGENT_MANIFEST_FILE)
@@ -610,6 +608,10 @@ class GuestAgent(object):
                 ustr(e))
             raise UpdateError(msg)
 
+        logger.info(
+            u"Agent {0} loaded manifest from {1}",
+            self.name,
+            self.get_agent_manifest_path())
         logger.verbose(u"Successfully loaded Agent {0} {1}: {2}",
             self.name,
             AGENT_MANIFEST_FILE,
@@ -617,13 +619,12 @@ class GuestAgent(object):
         return
 
     def _unpack(self):
-        logger.info(u"Unpacking agent package {0}", self.name)
-
         try:
             if os.path.isdir(self.get_agent_dir()):
                 shutil.rmtree(self.get_agent_dir())
 
             zipfile.ZipFile(self.get_agent_pkg_path()).extractall(self.get_agent_dir())
+
         except Exception as e:
             msg = u"Exception unpacking Agent {0} from {1}: {2}".format(
                 self.name,
@@ -633,13 +634,14 @@ class GuestAgent(object):
 
         if not os.path.isdir(self.get_agent_dir()):
             msg = u"Unpacking Agent {0} failed to create directory {1}".format(
-                        self.name,
-                        self.get_agent_dir())
+                self.name,
+                self.get_agent_dir())
             raise UpdateError(msg)
 
-        msg = u"Agent {0} successfully unpacked".format(self.name)
-        logger.info(msg)
-        add_event(AGENT_NAME, version=self.version, message=msg)
+        logger.info(
+            u"Agent {0} unpacked successfully to {1}",
+            self.name,
+            self.get_agent_dir())
         return
 
 
@@ -705,3 +707,9 @@ class GuestAgentError(object):
             u"was_fatal" : self.was_fatal
         }  
         return data
+
+    def __str__(self):
+        return "Last Failure: {0}, Total Failures: {1}, Fatal: {2}".format(
+            self.last_failure,
+            self.failure_count,
+            self.was_fatal)
diff --git a/bin/waagent2.0 b/bin/waagent2.0
index abc4448d..fde8db2e 100644
--- a/bin/waagent2.0
+++ b/bin/waagent2.0
@@ -561,9 +561,8 @@ class AbstractDistro(object):
         if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024):
             os.remove(mountpoint + "/swapfile")
         if not os.path.isfile(mountpoint + "/swapfile"):
-            Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB))
+            Run("umask 0077 && dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB))
             Run("mkswap " + mountpoint + "/swapfile")
-        Run("chmod 600 " + mountpoint + "/swapfile")
         if not Run("swapon " + mountpoint + "/swapfile"):
             Log("Enabled " + str(sizeKB) + " KB of swap at " + mountpoint + "/swapfile")
         else:
@@ -1769,7 +1768,7 @@ sizeKB = int(Config.get("ResourceDisk.SwapSizeMB")) * 1024
 if os.path.isfile(mountpoint + "/swapfile") and os.path.getsize(mountpoint + "/swapfile") != (sizeKB * 1024):
     os.remove(mountpoint + "/swapfile")
 if not os.path.isfile(mountpoint + "/swapfile"):
-    Run("dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB))
+    Run("umask 0077 && dd if=/dev/zero of=" + mountpoint + "/swapfile bs=1024 count=" + str(sizeKB))
 if Run("mdconfig -a -t vnode -f " + mountpoint + "/swapfile -u 0"):
     waagent.Error("ActivateResourceDisk: Configuring swap - Failed to create md0")
 if not Run("swapon /dev/md0"):
diff --git a/config/coreos/waagent.conf b/config/coreos/waagent.conf
index 6535578b..05095f38 100644
--- a/config/coreos/waagent.conf
+++ b/config/coreos/waagent.conf
@@ -17,19 +17,19 @@ Role.TopologyConsumer=None
 Provisioning.Enabled=y
 
 # Password authentication for root account will be unavailable.
-Provisioning.DeleteRootPassword=y
+Provisioning.DeleteRootPassword=n
 
 # Generate fresh host key pair.
-Provisioning.RegenerateSshHostKeyPair=y
+Provisioning.RegenerateSshHostKeyPair=n
 
 # Supported values are "rsa", "dsa" and "ecdsa".
-Provisioning.SshHostKeyPairType=rsa
+Provisioning.SshHostKeyPairType=ed25519
 
 # Monitor host name changes and publish changes via DHCP requests.
 Provisioning.MonitorHostName=y
 
 # Decode CustomData from Base64.
-Provisioning.DecodeCustomData=y
+Provisioning.DecodeCustomData=n
 
 # Execute CustomData after provisioning.
 Provisioning.ExecuteCustomData=n
@@ -59,6 +59,9 @@ ResourceDisk.EnableSwap=n
 # Size of the swapfile.
 ResourceDisk.SwapSizeMB=0
 
+# Respond to load balancer probes if requested by Windows Azure.
+LBProbeResponder=y
+
 # Enable verbose logging (y|n)
 Logs.Verbose=n
 
@@ -92,3 +95,7 @@ OS.OpensslPath=None
 
 # Enable RDMA management and set up, should only be used in HPC images
 # OS.EnableRDMA=y
+
+# Enable or disable self-update, default is enabled
+AutoUpdate.Enabled=y
+AutoUpdate.GAFamily=Prod
diff --git a/config/freebsd/waagent.conf b/config/freebsd/waagent.conf
index 416dcbb4..907b2194 100644
--- a/config/freebsd/waagent.conf
+++ b/config/freebsd/waagent.conf
@@ -93,3 +93,7 @@ OS.SudoersDir=/usr/local/etc/sudoers.d
 
 # Enable RDMA management and set up, should only be used in HPC images
 # OS.EnableRDMA=y
+
+# Enable or disable self-update, default is enabled
+AutoUpdate.Enabled=y
+AutoUpdate.GAFamily=Prod
diff --git a/config/suse/waagent.conf b/config/suse/waagent.conf
index 2fc42a18..8ea3195b 100644
--- a/config/suse/waagent.conf
+++ b/config/suse/waagent.conf
@@ -80,3 +80,7 @@ OS.OpensslPath=None
 
 # Enable RDMA management and set up, should only be used in HPC images
 # OS.EnableRDMA=y
+
+# Enable or disable self-update, default is enabled
+AutoUpdate.Enabled=y
+AutoUpdate.GAFamily=Prod
diff --git a/config/ubuntu/waagent.conf b/config/ubuntu/waagent.conf
index 1bde3172..ca680449 100644
--- a/config/ubuntu/waagent.conf
+++ b/config/ubuntu/waagent.conf
@@ -80,3 +80,7 @@ OS.OpensslPath=None
 
 # Enable RDMA management and set up, should only be used in HPC images
 # OS.EnableRDMA=y
+
+# Enable or disable self-update, default is enabled
+AutoUpdate.Enabled=y
+AutoUpdate.GAFamily=Prod
diff --git a/config/waagent.conf b/config/waagent.conf
index d183d2fb..c28a6517 100644
--- a/config/waagent.conf
+++ b/config/waagent.conf
@@ -92,3 +92,7 @@ OS.OpensslPath=None
 
 # Enable RDMA management and set up, should only be used in HPC images
 # OS.EnableRDMA=y
+
+# Enable or disable self-update, default is enabled
+AutoUpdate.Enabled=y
+AutoUpdate.GAFamily=Prod
diff --git a/debian/docs b/debian/docs
index b924fd1e..16299a9e 100644
--- a/debian/docs
+++ b/debian/docs
@@ -1,3 +1,3 @@
 NOTICE
 LICENSE-2.0.txt
-README
+README.md
diff --git a/debian/install b/debian/install
index 0c4c4928..197b8cc9 100644
--- a/debian/install
+++ b/debian/install
@@ -1,1 +1,2 @@
-waagent usr/sbin
+usr
+etc


Test patch:
diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py
index 36241b16..74804fb9 100644
--- a/tests/ga/test_update.py
+++ b/tests/ga/test_update.py
@@ -134,6 +134,16 @@ class UpdateTestCase(AgentTestCase):
         v.sort(reverse=True)
         return v
 
+    def get_error_file(self, error_data=NO_ERROR):
+        fp = tempfile.NamedTemporaryFile(mode="w")
+        json.dump(error_data if error_data is not None else NO_ERROR, fp)
+        fp.seek(0)
+        return fp
+
+    def create_error(self, error_data=NO_ERROR):
+        with self.get_error_file(error_data) as path:
+            return GuestAgentError(path.name)
+
     def copy_agents(self, *agents):
         if len(agents) <= 0:
             agents = get_agent_pkgs()
@@ -206,16 +216,6 @@ class UpdateTestCase(AgentTestCase):
 
 
 class TestGuestAgentError(UpdateTestCase):
-    def get_error_file(self, error_data=NO_ERROR):
-        fp = tempfile.NamedTemporaryFile(mode="w")
-        json.dump(error_data if error_data is not None else NO_ERROR, fp)
-        fp.seek(0)
-        return fp
-
-    def create_error(self, error_data=NO_ERROR):
-        with self.get_error_file(error_data) as path:
-            return GuestAgentError(path.name)
-
     def test_creation(self):
         self.assertRaises(TypeError, GuestAgentError)
         self.assertRaises(UpdateError, GuestAgentError, None)
@@ -297,6 +297,22 @@ class TestGuestAgentError(UpdateTestCase):
         self.assertTrue(err.failure_count < MAX_FAILURE)
         return
 
+    def test_str(self):
+        err = self.create_error(error_data=NO_ERROR)
+        s = "Last Failure: {0}, Total Failures: {1}, Fatal: {2}".format(
+            NO_ERROR["last_failure"],
+            NO_ERROR["failure_count"],
+            NO_ERROR["was_fatal"])
+        self.assertEqual(s, str(err))
+
+        err = self.create_error(error_data=WITH_ERROR)
+        s = "Last Failure: {0}, Total Failures: {1}, Fatal: {2}".format(
+            WITH_ERROR["last_failure"],
+            WITH_ERROR["failure_count"],
+            WITH_ERROR["was_fatal"])
+        self.assertEqual(s, str(err))
+        return
+
 
 class TestGuestAgent(UpdateTestCase):
     def setUp(self):
@@ -331,7 +347,9 @@ class TestGuestAgent(UpdateTestCase):
         path = ".".join((os.path.join(conf.get_lib_dir(), get_agent_name()), "zip"))
         self.assertEqual(path, agent.get_agent_pkg_path())
 
-        self.assertFalse(agent.is_downloaded)
+        self.assertTrue(agent.is_downloaded)
+        # Note: Agent will get blacklisted since the package for this test is invalid
+        self.assertTrue(agent.is_blacklisted)
         self.assertFalse(agent.is_available)
         return
 
@@ -455,6 +473,14 @@ class TestGuestAgent(UpdateTestCase):
         self.assertRaises(UpdateError, agent._load_manifest)
         return
 
+    def test_load_error(self):
+        agent = GuestAgent(path=self.agent_path)
+        agent.error = None
+
+        agent._load_error()
+        self.assertTrue(agent.error is not None)
+        return
+
     @patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
     @patch("azurelinuxagent.ga.update.restutil.http_get")
     def test_download(self, mock_http_get, mock_ensure):
@@ -575,6 +601,22 @@ class TestUpdate(UpdateTestCase):
         self.update_handler = get_update_handler()
         return
 
+    def test_creation(self):
+        self.assertTrue(self.update_handler.running)
+
+        self.assertEqual(None, self.update_handler.last_etag)
+        self.assertEqual(None, self.update_handler.last_attempt_time)
+
+        self.assertEqual(0, len(self.update_handler.agents))
+
+        self.assertEqual(None, self.update_handler.child_agent)
+        self.assertEqual(None, self.update_handler.child_launch_time)
+        self.assertEqual(0, self.update_handler.child_launch_attempts)
+        self.assertEqual(None, self.update_handler.child_process)
+
+        self.assertEqual(None, self.update_handler.signal_handler)
+        return
+
     def _test_ensure_latest_agent(
             self,
             base_version=FlexibleVersion(AGENT_VERSION),
@@ -585,7 +627,6 @@ class TestUpdate(UpdateTestCase):
         if versions is None or len(versions) <= 0:
             versions = [latest_version]
 
-        self.update_handler.protocol_util = Mock(return_value=ProtocolMock)
         etag = self.update_handler.last_etag if self.update_handler.last_etag is not None else 42
         if protocol is None:
             protocol = ProtocolMock(etag=etag, versions=versions)
@@ -599,7 +640,7 @@ class TestUpdate(UpdateTestCase):
         self.assertTrue(self._test_ensure_latest_agent())
         return
 
-    def test_ensure_latest_agent_ignores_old_agents(self):
+    def test_ensure_latest_agent_includes_old_agents(self):
         self.prepare_agents()
 
         old_count = FlexibleVersion(AGENT_VERSION).version[-1]
@@ -609,7 +650,7 @@ class TestUpdate(UpdateTestCase):
         all_count = len(self.agent_versions())
 
         self.assertTrue(self._test_ensure_latest_agent(versions=self.agent_versions()))
-        self.assertEqual(all_count - old_count, len(self.update_handler.agents))
+        self.assertEqual(all_count, len(self.update_handler.agents))
         return
 
     def test_ensure_lastest_agent_purges_old_agents(self):
@@ -787,7 +828,7 @@ class TestUpdate(UpdateTestCase):
         kept_agents = self.update_handler.agents[1::2]
         purged_agents = self.update_handler.agents[::2]
 
-        # Reload and assert only the kept agents remain on disk        
+        # Reload and assert only the kept agents remain on disk
         self.update_handler.agents = kept_agents
         self.update_handler._purge_agents()
         self.update_handler._load_agents()
@@ -808,14 +849,19 @@ class TestUpdate(UpdateTestCase):
             self.assertTrue(os.path.exists(agent_path + ".zip"))
         return
 
-    def _test_run_latest(self, return_value=0, side_effect=None, child_calls=1):
-        mock_child = Mock()
-        mock_child.wait = Mock(return_value=return_value, side_effect=side_effect)
+    def _test_run_latest(self, mock_child=None, mock_time=None):
+        if mock_child is None:
+            mock_child = ChildMock()
+        if mock_time is None:
+            mock_time = TimeMock()
+
         with patch('subprocess.Popen', return_value=mock_child) as mock_popen:
-            self.update_handler.run_latest()
-            self.assertEqual(child_calls, mock_popen.call_count)
+            with patch('time.time', side_effect=mock_time.time):
+                with patch('time.sleep', return_value=mock_time.sleep):
+                    self.update_handler.run_latest()
+                    self.assertEqual(1, mock_popen.call_count)
 
-            return mock_popen.call_args
+                    return mock_popen.call_args
 
     def test_run_latest(self):
         self.prepare_agents()
@@ -831,6 +877,31 @@ class TestUpdate(UpdateTestCase):
         self.assertEqual(agent.get_agent_dir(), kwargs['cwd'])
         return
 
+    def test_run_latest_polls_and_waits_for_success(self):
+        mock_child = ChildMock(return_value=None)
+        mock_time = TimeMock(time_increment=CHILD_HEALTH_INTERVAL/3)
+        self._test_run_latest(mock_child=mock_child, mock_time=mock_time)
+        self.assertEqual(2, mock_child.poll.call_count)
+        self.assertEqual(1, mock_child.wait.call_count)
+        return
+
+    def test_run_latest_polling_stops_at_success(self):
+        mock_child = ChildMock(return_value=0)
+        mock_time = TimeMock(time_increment=CHILD_HEALTH_INTERVAL/3)
+        self._test_run_latest(mock_child=mock_child, mock_time=mock_time)
+        self.assertEqual(1, mock_child.poll.call_count)
+        self.assertEqual(0, mock_child.wait.call_count)
+        return
+
+    def test_run_latest_polling_stops_at_failure(self):
+        mock_child = ChildMock(return_value=42)
+        mock_time = TimeMock()
+        self._test_run_latest(mock_child=mock_child, mock_time=mock_time)
+        self.assertEqual(1, mock_child.poll.call_count)
+        self.assertEqual(0, mock_child.wait.call_count)
+        self.assertEqual(2, mock_time.time_call_count)
+        return
+
     def test_run_latest_defaults_to_current(self):
         self.assertEqual(None, self.update_handler.get_latest_agent())
 
@@ -852,7 +923,7 @@ class TestUpdate(UpdateTestCase):
                     saved_stdout, sys.stdout = sys.stdout, stdout
                     saved_stderr, sys.stderr = sys.stderr, stderr
                     try:
-                        self._test_run_latest(side_effect=faux_logger)
+                        self._test_run_latest(mock_child=ChildMock(side_effect=faux_logger))
                     finally:
                         sys.stdout = saved_stdout
                         sys.stderr = saved_stderr
@@ -874,23 +945,7 @@ class TestUpdate(UpdateTestCase):
         self.assertEqual(0.0, latest_agent.error.last_failure)
         self.assertEqual(0, latest_agent.error.failure_count)
 
-        self._test_run_latest(return_value=1)
-
-        self.assertTrue(latest_agent.is_available)
-        self.assertNotEqual(0.0, latest_agent.error.last_failure)
-        self.assertEqual(1, latest_agent.error.failure_count)
-        return
-
-    def test_run_latest_missing_code_marks_failures(self):
-        # logger.add_logger_appender(logger.AppenderType.STDOUT)
-        self.prepare_agents()
-
-        latest_agent = self.update_handler.get_latest_agent()
-        self.assertTrue(latest_agent.is_available)
-        self.assertEqual(0.0, latest_agent.error.last_failure)
-        self.assertEqual(0, latest_agent.error.failure_count)
-
-        self._test_run_latest(return_value=None)
+        self._test_run_latest(mock_child=ChildMock(return_value=1))
 
         self.assertTrue(latest_agent.is_available)
         self.assertNotEqual(0.0, latest_agent.error.last_failure)
@@ -906,7 +961,7 @@ class TestUpdate(UpdateTestCase):
         self.assertEqual(0.0, latest_agent.error.last_failure)
         self.assertEqual(0, latest_agent.error.failure_count)
 
-        self._test_run_latest(side_effect=Exception("Force blacklisting"))
+        self._test_run_latest(mock_child=ChildMock(side_effect=Exception("Force blacklisting")))
 
         self.assertFalse(latest_agent.is_available)
         self.assertTrue(latest_agent.error.is_blacklisted)
@@ -914,6 +969,19 @@ class TestUpdate(UpdateTestCase):
         self.assertEqual(1, latest_agent.error.failure_count)
         return
 
+    @patch('signal.signal')
+    def test_run_latest_captures_signals(self, mock_signal):
+        self._test_run_latest()
+        self.assertEqual(1, mock_signal.call_count)
+        return
+
+    @patch('signal.signal')
+    def test_run_latest_creates_only_one_signal_handler(self, mock_signal):
+        self.update_handler.signal_handler = "Not None"
+        self._test_run_latest()
+        self.assertEqual(0, mock_signal.call_count)
+        return
+
     def _test_run(self, invocations=1, calls=[call.run()], enable_updates=False):
         conf.get_autoupdate_enabled = Mock(return_value=enable_updates)
 
@@ -961,11 +1029,18 @@ class TestUpdate(UpdateTestCase):
         self._test_run(invocations=0, calls=[], enable_updates=True)
         return
 
-    def test_set_agents(self):
+    def test_set_agents_sets_agents(self):
         self.prepare_agents()
 
         self.update_handler._set_agents([GuestAgent(path=path) for path in self.agent_dirs()])
+        self.assertTrue(len(self.update_handler.agents) > 0)
         self.assertEqual(len(self.agent_dirs()), len(self.update_handler.agents))
+        return
+
+    def test_set_agents_sorts_agents(self):
+        self.prepare_agents()
+
+        self.update_handler._set_agents([GuestAgent(path=path) for path in self.agent_dirs()])
 
         v = FlexibleVersion("100000")
         for a in self.update_handler.agents:
@@ -974,6 +1049,15 @@ class TestUpdate(UpdateTestCase):
         return
 
 
+class ChildMock(Mock):
+    def __init__(self, return_value=0, side_effect=None):
+        Mock.__init__(self, return_value=return_value, side_effect=side_effect)
+
+        self.poll = Mock(return_value=return_value, side_effect=side_effect)
+        self.wait = Mock(return_value=return_value, side_effect=side_effect)
+        return
+
+
 class ProtocolMock(object):
     def __init__(self, family="TestAgent", etag=42, versions=None):
         self.family = family
@@ -1030,5 +1114,22 @@ class ResponseMock(Mock):
         return self.response
 
 
+class TimeMock(Mock):
+    def __init__(self, time_increment=1):
+        Mock.__init__(self)
+        self.next_time = time.time()
+        self.time_call_count = 0
+        self.time_increment = time_increment
+
+        self.sleep = Mock(return_value=0)
+        return
+
+    def time(self):
+        self.time_call_count += 1
+        current_time = self.next_time
+        self.next_time += self.time_increment
+        return current_time
+
+
 if __name__ == '__main__':
     unittest.main()