summaryrefslogtreecommitdiff
path: root/azurelinuxagent/ga
diff options
context:
space:
mode:
Diffstat (limited to 'azurelinuxagent/ga')
-rw-r--r--azurelinuxagent/ga/env.py8
-rw-r--r--azurelinuxagent/ga/exthandlers.py170
-rw-r--r--azurelinuxagent/ga/monitor.py7
-rw-r--r--azurelinuxagent/ga/update.py252
4 files changed, 326 insertions, 111 deletions
diff --git a/azurelinuxagent/ga/env.py b/azurelinuxagent/ga/env.py
index 2d67d4b..5d8da5c 100644
--- a/azurelinuxagent/ga/env.py
+++ b/azurelinuxagent/ga/env.py
@@ -45,7 +45,8 @@ class EnvHandler(object):
self.stopped = True
self.hostname = None
self.dhcpid = None
- self.server_thread=None
+ self.server_thread = None
+ self.dhcp_warning_enabled = True
def run(self):
if not self.stopped:
@@ -87,8 +88,11 @@ class EnvHandler(object):
def handle_dhclient_restart(self):
if self.dhcpid is None:
- logger.warn("Dhcp client is not running. ")
+ if self.dhcp_warning_enabled:
+ logger.warn("Dhcp client is not running. ")
self.dhcpid = self.osutil.get_dhcp_pid()
+ # disable subsequent error logging
+ self.dhcp_warning_enabled = self.dhcpid is not None
return
#The dhcp process hasn't changed since last check
diff --git a/azurelinuxagent/ga/exthandlers.py b/azurelinuxagent/ga/exthandlers.py
index d3c8f32..c9e6b5f 100644
--- a/azurelinuxagent/ga/exthandlers.py
+++ b/azurelinuxagent/ga/exthandlers.py
@@ -21,6 +21,7 @@ import glob
import json
import os
import shutil
+import stat
import subprocess
import time
import zipfile
@@ -108,6 +109,45 @@ def parse_ext_status(ext_status, data):
for substatus in substatus_list:
ext_status.substatusList.append(parse_ext_substatus(substatus))
+# This code migrates, if it exists, handler state and status from an
+# agent-owned directory into the handler-owned config directory
+#
+# Notes:
+# - The v2.0.x branch wrote all handler-related state into the handler-owned
+# config directory (e.g., /var/lib/waagent/Microsoft.Azure.Extensions.LinuxAsm-2.0.1/config).
+# - The v2.1.x branch original moved that state into an agent-owned handler
+# state directory (e.g., /var/lib/waagent/handler_state).
+# - This move can cause v2.1.x agents to multiply invoke a handler's install
+# command. It also makes clean-up more difficult since the agent must
+# remove the state as well as the handler directory.
+def migrate_handler_state():
+ handler_state_path = os.path.join(conf.get_lib_dir(), "handler_state")
+ if not os.path.isdir(handler_state_path):
+ return
+
+ for handler_path in glob.iglob(os.path.join(handler_state_path, "*")):
+ handler = os.path.basename(handler_path)
+ handler_config_path = os.path.join(conf.get_lib_dir(), handler, "config")
+ if os.path.isdir(handler_config_path):
+ for file in ("State", "Status"):
+ from_path = os.path.join(handler_state_path, handler, file.lower())
+ to_path = os.path.join(handler_config_path, "Handler" + file)
+ if os.path.isfile(from_path) and not os.path.isfile(to_path):
+ try:
+ shutil.move(from_path, to_path)
+ except Exception as e:
+ logger.warn(
+ "Exception occurred migrating {0} {1} file: {2}",
+ handler,
+ file,
+ str(e))
+
+ try:
+ shutil.rmtree(handler_state_path)
+ except Exception as e:
+ logger.warn("Exception occurred removing {0}: {1}", handler_state_path, str(e))
+ return
+
class ExtHandlerState(object):
NotInstalled = "NotInstalled"
Installed = "Installed"
@@ -122,6 +162,7 @@ class ExtHandlersHandler(object):
self.ext_handlers = None
self.last_etag = None
self.log_report = False
+ self.log_etag = True
def run(self):
self.ext_handlers, etag = None, None
@@ -135,16 +176,12 @@ class ExtHandlersHandler(object):
add_event(AGENT_NAME, version=CURRENT_VERSION, is_success=False, message=msg)
return
- if self.last_etag is not None and self.last_etag == etag:
- msg = u"Incarnation {0} has no extension updates".format(etag)
- logger.verbose(msg)
- self.log_report = False
- else:
- msg = u"Handle extensions updates for incarnation {0}".format(etag)
- logger.info(msg)
- self.log_report = True #Log status report success on new config
- self.handle_ext_handlers()
- self.last_etag = etag
+ msg = u"Handle extensions updates for incarnation {0}".format(etag)
+ logger.verbose(msg)
+ # Log status report success on new config
+ self.log_report = True
+ self.handle_ext_handlers(etag)
+ self.last_etag = etag
self.report_ext_handlers_status()
@@ -152,18 +189,36 @@ class ExtHandlersHandler(object):
self.report_ext_handlers_status()
return
- def handle_ext_handlers(self):
+ def handle_ext_handlers(self, etag=None):
if self.ext_handlers.extHandlers is None or \
len(self.ext_handlers.extHandlers) == 0:
- logger.info("No ext handler config found")
+ logger.verbose("No extension handler config found")
return
+ if conf.get_enable_overprovisioning():
+ artifacts_profile = self.protocol.get_artifacts_profile()
+ if artifacts_profile and artifacts_profile.is_on_hold():
+ logger.info("Extension handling is on hold")
+ return
+
for ext_handler in self.ext_handlers.extHandlers:
- #TODO handle install in sequence, enable in parallel
- self.handle_ext_handler(ext_handler)
+ # TODO: handle install in sequence, enable in parallel
+ self.handle_ext_handler(ext_handler, etag)
- def handle_ext_handler(self, ext_handler):
+ def handle_ext_handler(self, ext_handler, etag):
ext_handler_i = ExtHandlerInstance(ext_handler, self.protocol)
+
+ ext_handler_i.decide_version()
+ 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}",
+ ext_handler_i.pkg.version,
+ etag)
+ self.log_etag = False
+ return
+
+ self.log_etag = True
+
try:
state = ext_handler.properties.state
ext_handler_i.logger.info("Expected handler state: {0}", state)
@@ -182,12 +237,10 @@ class ExtHandlersHandler(object):
def handle_enable(self, ext_handler_i):
- ext_handler_i.decide_version()
-
old_ext_handler_i = ext_handler_i.get_installed_ext_handler()
if old_ext_handler_i is not None and \
old_ext_handler_i.version_gt(ext_handler_i):
- raise ExtensionError(u"Downgrade not allowed")
+ raise ExtensionError(u"Downgrade not allowed")
handler_state = ext_handler_i.get_handler_state()
ext_handler_i.logger.info("Current handler state is: {0}", handler_state)
@@ -246,17 +299,14 @@ class ExtHandlersHandler(object):
message=ustr(e))
logger.verbose("Report vm agent status")
-
try:
self.protocol.report_vm_status(vm_status)
+ if self.log_report:
+ logger.verbose("Successfully reported vm agent status")
except ProtocolError as e:
message = "Failed to report vm agent status: {0}".format(e)
add_event(AGENT_NAME, version=CURRENT_VERSION, is_success=False, message=message)
- if self.log_report:
- logger.verbose("Successfully reported vm agent status")
-
-
def report_ext_handler_status(self, vm_status, ext_handler):
ext_handler_i = ExtHandlerInstance(ext_handler, self.protocol)
@@ -287,6 +337,7 @@ class ExtHandlerInstance(object):
self.protocol = protocol
self.operation = None
self.pkg = None
+ self.is_upgrade = False
prefix = "[{0}]".format(self.get_full_name())
self.logger = logger.Logger(logger.DEFAULT_LOGGER, prefix)
@@ -301,7 +352,7 @@ class ExtHandlerInstance(object):
logger.LogLevel.INFO, log_file)
def decide_version(self):
- self.logger.info("Decide which version to use")
+ self.logger.verbose("Decide which version to use")
try:
pkg_list = self.protocol.get_ext_handler_pkgs(self.ext_handler)
except ProtocolError as e:
@@ -309,9 +360,10 @@ class ExtHandlerInstance(object):
# Determine the desired and installed versions
requested_version = FlexibleVersion(self.ext_handler.properties.version)
- installed_version = FlexibleVersion(self.get_installed_version())
- if installed_version is None:
- installed_version = requested_version
+ installed_version_string = self.get_installed_version()
+ installed_version = requested_version \
+ if installed_version_string is None \
+ else FlexibleVersion(installed_version_string)
# Divide packages
# - Find the installed package (its version must exactly match)
@@ -392,10 +444,15 @@ class ExtHandlerInstance(object):
self.pkg = selected_pkg
self.ext_handler.properties.version = selected_pkg.version
+ # Note if the selected package is greater than that installed
+ if installed_pkg is None \
+ or FlexibleVersion(self.pkg.version) > FlexibleVersion(installed_pkg.version):
+ self.is_upgrade = True
+
if self.pkg is None:
raise ExtensionError("Failed to find any valid extension package")
- self.logger.info("Use version: {0}", self.pkg.version)
+ self.logger.verbose("Use version: {0}", self.pkg.version)
return
def version_gt(self, other):
@@ -464,8 +521,10 @@ class ExtHandlerInstance(object):
for uri in self.pkg.uris:
try:
package = self.protocol.download_ext_handler_pkg(uri.uri)
- except ProtocolError as e:
- logger.warn("Failed download extension: {0}", e)
+ if package is not None:
+ break
+ except Exception as e:
+ logger.warn("Error while downloading extension: {0}", e)
if package is None:
raise ExtensionError("Failed to download extension")
@@ -479,8 +538,10 @@ class ExtHandlerInstance(object):
except IOError as e:
raise ExtensionError(u"Failed to write and unzip plugin", e)
- chmod = "find {0} -type f | xargs chmod u+x".format(self.get_base_dir())
- shellutil.run(chmod)
+ #Add user execute permission to all files under the base dir
+ for file in fileutil.get_all_files(self.get_base_dir()):
+ fileutil.chmod(file, os.stat(file).st_mode | stat.S_IXUSR)
+
self.report_event(message="Download succeeded")
self.logger.info("Initialize extension directory")
@@ -547,10 +608,6 @@ class ExtHandlerInstance(object):
def rm_ext_handler_dir(self):
try:
- handler_state_dir = self.get_handler_state_dir()
- if os.path.isdir(handler_state_dir):
- self.logger.info("Remove ext handler dir: {0}", handler_state_dir)
- shutil.rmtree(handler_state_dir)
base_dir = self.get_base_dir()
if os.path.isdir(base_dir):
self.logger.info("Remove ext handler dir: {0}", base_dir)
@@ -746,28 +803,18 @@ class ExtHandlerInstance(object):
fileutil.write_file(self.get_env_file(), json.dumps(env))
except IOError as e:
raise ExtensionError(u"Failed to save handler environment", e)
-
- def get_handler_state_dir(self):
- return os.path.join(conf.get_lib_dir(), "handler_state",
- self.get_full_name())
def set_handler_state(self, handler_state):
- state_dir = self.get_handler_state_dir()
- if not os.path.exists(state_dir):
- try:
- fileutil.mkdir(state_dir, 0o700)
- except IOError as e:
- self.logger.error("Failed to create state dir: {0}", e)
-
+ state_dir = self.get_conf_dir()
try:
- state_file = os.path.join(state_dir, "state")
+ state_file = os.path.join(state_dir, "HandlerState")
fileutil.write_file(state_file, handler_state)
except IOError as e:
self.logger.error("Failed to set state: {0}", e)
def get_handler_state(self):
- state_dir = self.get_handler_state_dir()
- state_file = os.path.join(state_dir, "state")
+ state_dir = self.get_conf_dir()
+ state_file = os.path.join(state_dir, "HandlerState")
if not os.path.isfile(state_file):
return ExtHandlerState.NotInstalled
@@ -777,32 +824,25 @@ class ExtHandlerInstance(object):
self.logger.error("Failed to get state: {0}", e)
return ExtHandlerState.NotInstalled
- def set_handler_status(self, status="NotReady", message="",
- code=0):
- state_dir = self.get_handler_state_dir()
- if not os.path.exists(state_dir):
- try:
- fileutil.mkdir(state_dir, 0o700)
- except IOError as e:
- self.logger.error("Failed to create state dir: {0}", e)
-
+ def set_handler_status(self, status="NotReady", message="", code=0):
+ state_dir = self.get_conf_dir()
+
handler_status = ExtHandlerStatus()
handler_status.name = self.ext_handler.name
handler_status.version = self.ext_handler.properties.version
handler_status.message = message
handler_status.code = code
handler_status.status = status
- status_file = os.path.join(state_dir, "status")
+ status_file = os.path.join(state_dir, "HandlerStatus")
try:
- fileutil.write_file(status_file,
- json.dumps(get_properties(handler_status)))
+ fileutil.write_file(status_file, json.dumps(get_properties(handler_status)))
except (IOError, ValueError, ProtocolError) as e:
self.logger.error("Failed to save handler status: {0}", e)
def get_handler_status(self):
- state_dir = self.get_handler_state_dir()
- status_file = os.path.join(state_dir, "status")
+ state_dir = self.get_conf_dir()
+ status_file = os.path.join(state_dir, "HandlerStatus")
if not os.path.isfile(status_file):
return None
@@ -899,4 +939,4 @@ class HandlerManifest(object):
update_mode = self.data['handlerManifest'].get('updateMode')
if update_mode is None:
return True
- return update_mode.low() == "updatewithinstall"
+ return update_mode.lower() == "updatewithinstall"
diff --git a/azurelinuxagent/ga/monitor.py b/azurelinuxagent/ga/monitor.py
index f49cef8..478a7a3 100644
--- a/azurelinuxagent/ga/monitor.py
+++ b/azurelinuxagent/ga/monitor.py
@@ -183,8 +183,11 @@ class MonitorHandler(object):
while True:
if (datetime.datetime.now() - last_heartbeat) > period:
last_heartbeat = datetime.datetime.now()
- add_event(op=WALAEventOperation.HeartBeat, name="WALA",
- is_success=True)
+ add_event(
+ op=WALAEventOperation.HeartBeat,
+ name=CURRENT_AGENT,
+ version=CURRENT_VERSION,
+ is_success=True)
try:
self.collect_and_send_events()
except Exception as e:
diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py
index e89608a..996484b 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -16,12 +16,12 @@
#
# Requires Python 2.4+ and Openssl 1.0+
#
+
import glob
import json
import os
import platform
import re
-import shlex
import shutil
import signal
import subprocess
@@ -59,10 +59,14 @@ CHILD_LAUNCH_RESTART_MAX = 3
CHILD_POLL_INTERVAL = 60
MAX_FAILURE = 3 # Max failure allowed for agent before blacklisted
+RETAIN_INTERVAL = 24 * 60 * 60 # Retain interval for black list
GOAL_STATE_INTERVAL = 25
REPORT_STATUS_INTERVAL = 15
-RETAIN_INTERVAL = 24 * 60 * 60 # Retain interval for black list
+
+ORPHAN_WAIT_INTERVAL = 15 * 60 * 60
+
+AGENT_SENTINAL_FILE = "current_version"
def get_update_handler():
@@ -81,7 +85,6 @@ class UpdateHandler(object):
self.protocol_util = get_protocol_util()
self.running = True
- self.last_etag = None
self.last_attempt_time = None
self.agents = []
@@ -126,7 +129,7 @@ class UpdateHandler(object):
try:
# Launch the correct Python version for python-based agents
- cmds = shlex.split(agent_cmd)
+ cmds = textutil.safe_shlex_split(agent_cmd)
if cmds[0].lower() == "python":
cmds[0] = get_python_cmd()
agent_cmd = " ".join(cmds)
@@ -218,13 +221,21 @@ class UpdateHandler(object):
from azurelinuxagent.ga.env import get_env_handler
get_env_handler().run()
- from azurelinuxagent.ga.exthandlers import get_exthandlers_handler
+ from azurelinuxagent.ga.exthandlers import get_exthandlers_handler, migrate_handler_state
exthandlers_handler = get_exthandlers_handler()
+ migrate_handler_state()
- # TODO: Add means to stop running
try:
+ self._ensure_no_orphans()
+ self._emit_restart_event()
+
+ # TODO: Add means to stop running
while self.running:
- if self._ensure_latest_agent():
+ if self._is_orphaned:
+ logger.info("Goal state agent {0} was orphaned -- exiting", CURRENT_AGENT)
+ break
+
+ if self._upgrade_available():
if len(self.agents) > 0:
logger.info(
u"Agent {0} discovered {1} as an update and will exit",
@@ -234,16 +245,26 @@ class UpdateHandler(object):
exthandlers_handler.run()
- time.sleep(25)
+ time.sleep(GOAL_STATE_INTERVAL)
except Exception as e:
logger.warn(u"Agent {0} failed with exception: {1}", CURRENT_AGENT, ustr(e))
sys.exit(1)
+ return
+ self._shutdown()
sys.exit(0)
return
def forward_signal(self, signum, frame):
+ # Note:
+ # - At present, the handler is registered only for SIGTERM.
+ # However, clean shutdown is both SIGTERM and SIGKILL.
+ # A SIGKILL handler is not being registered at this time to
+ # minimize perturbing the code.
+ if signum in (signal.SIGTERM, signal.SIGKILL):
+ self._shutdown()
+
if self.child_process is None:
return
@@ -258,13 +279,14 @@ class UpdateHandler(object):
self.signal_handler(signum, frame)
elif self.signal_handler is signal.SIG_DFL:
if signum == signal.SIGTERM:
+ # TODO: This should set self.running to False vs. just exiting
sys.exit(0)
return
def get_latest_agent(self):
"""
If autoupdate is enabled, return the most current, downloaded,
- non-blacklisted agent (if any).
+ non-blacklisted agent which is not the current version (if any).
Otherwise, return None (implying to use the installed agent).
"""
@@ -272,10 +294,27 @@ class UpdateHandler(object):
return None
self._load_agents()
- available_agents = [agent for agent in self.agents if agent.is_available]
+ available_agents = [agent for agent in self.agents
+ if agent.is_available
+ and agent.version > FlexibleVersion(AGENT_VERSION)]
+
return available_agents[0] if len(available_agents) >= 1 else None
- def _ensure_latest_agent(self, base_version=CURRENT_VERSION):
+ def _emit_restart_event(self):
+ if not self._is_clean_start:
+ msg = u"{0} did not terminate cleanly".format(CURRENT_AGENT)
+ logger.info(msg)
+ add_event(
+ AGENT_NAME,
+ version=CURRENT_VERSION,
+ op=WALAEventOperation.Restart,
+ is_success=False,
+ message=msg)
+
+ self._set_sentinal()
+ return
+
+ def _upgrade_available(self, base_version=CURRENT_VERSION):
# Ignore new agents if updating is disabled
if not conf.get_autoupdate_enabled():
return False
@@ -306,11 +345,8 @@ class UpdateHandler(object):
message=msg)
return False
- if self.last_etag is not None and self.last_etag == etag:
- logger.info(u"Incarnation {0} has no agent updates", etag)
- return False
-
- manifests = [m for m in manifest_list.vmAgentManifests if m.family == family]
+ manifests = [m for m in manifest_list.vmAgentManifests \
+ if m.family == family and len(m.versionsManifestUris) > 0]
if len(manifests) == 0:
logger.info(u"Incarnation {0} has no agent family {1} updates", etag, family)
return False
@@ -318,7 +354,8 @@ class UpdateHandler(object):
try:
pkg_list = protocol.get_vmagent_pkgs(manifests[0])
except ProtocolError as e:
- msg= u"Incarnation {0} failed to get {1} package list: {2}".format(
+ msg = u"Incarnation {0} failed to get {1} package list: " \
+ u"{2}".format(
etag,
family,
ustr(e))
@@ -331,18 +368,51 @@ class UpdateHandler(object):
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 reported to the VM.
+ # Set the agents to those available for download at least as current
+ # as the existing agent 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_list.versions])
+ # 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.
+ host = None
+ if protocol and protocol.client:
+ host = protocol.client.get_host_plugin()
+ self._set_agents([GuestAgent(pkg=pkg, host=host) for pkg in pkg_list.versions])
self._purge_agents()
self._filter_blacklisted_agents()
# Return True if agents more recent than the current are available
return len(self.agents) > 0 and self.agents[0].version > base_version
+ def _ensure_no_orphans(self, orphan_wait_interval=ORPHAN_WAIT_INTERVAL):
+ previous_pid_file, pid_file = self._write_pid_file()
+ if previous_pid_file is not None:
+ try:
+ pid = fileutil.read_file(previous_pid_file)
+ wait_interval = orphan_wait_interval
+ while self.osutil.check_pid_alive(pid):
+ wait_interval -= GOAL_STATE_INTERVAL
+ if wait_interval <= 0:
+ logger.warn(
+ u"{0} forcibly terminated orphan process {1}",
+ CURRENT_AGENT,
+ pid)
+ os.kill(pid, signal.SIGKILL)
+ break
+
+ logger.info(
+ u"{0} waiting for orphan process {1} to terminate",
+ CURRENT_AGENT,
+ pid)
+ time.sleep(GOAL_STATE_INTERVAL)
+
+ except Exception as e:
+ logger.warn(
+ u"Exception occurred waiting for orphan agent to terminate: {0}",
+ ustr(e))
+ return
+
def _evaluate_agent_health(self, latest_agent):
"""
Evaluate the health of the selected agent: If it is restarting
@@ -375,18 +445,61 @@ class UpdateHandler(object):
self.agents = [agent for agent in self.agents if not agent.is_blacklisted]
return
+ def _get_pid_files(self):
+ pid_file = conf.get_agent_pid_file_path()
+
+ pid_dir = os.path.dirname(pid_file)
+ pid_name = os.path.basename(pid_file)
+
+ pid_re = re.compile("(\d+)_{0}".format(re.escape(pid_name)))
+ pid_files = [int(pid_re.match(f).group(1)) for f in os.listdir(pid_dir) if pid_re.match(f)]
+ pid_files.sort()
+
+ pid_index = -1 if len(pid_files) <= 0 else pid_files[-1]
+ previous_pid_file = None \
+ if pid_index < 0 \
+ else os.path.join(pid_dir, "{0}_{1}".format(pid_index, pid_name))
+ pid_file = os.path.join(pid_dir, "{0}_{1}".format(pid_index+1, pid_name))
+ return previous_pid_file, pid_file
+
+ @property
+ def _is_clean_start(self):
+ if not os.path.isfile(self._sentinal_file_path()):
+ return True
+
+ try:
+ if fileutil.read_file(self._sentinal_file_path()) != CURRENT_AGENT:
+ return True
+ except Exception as e:
+ logger.warn(
+ u"Exception reading sentinal file {0}: {1}",
+ self._sentinal_file_path(),
+ str(e))
+
+ return False
+
+ @property
+ def _is_orphaned(self):
+ parent_pid = os.getppid()
+ if parent_pid in (1, None):
+ return True
+
+ if not os.path.isfile(conf.get_agent_pid_file_path()):
+ return True
+
+ return fileutil.read_file(conf.get_agent_pid_file_path()) != ustr(parent_pid)
+
def _load_agents(self):
"""
Load all non-blacklisted agents currently on disk.
"""
- if len(self.agents) <= 0:
- try:
- path = os.path.join(conf.get_lib_dir(), "{0}-*".format(AGENT_NAME))
- self._set_agents([GuestAgent(path=agent_dir)
- for agent_dir in glob.iglob(path) if os.path.isdir(agent_dir)])
- self._filter_blacklisted_agents()
- except Exception as e:
- logger.warn(u"Exception occurred loading available agents: {0}", ustr(e))
+ try:
+ path = os.path.join(conf.get_lib_dir(), "{0}-*".format(AGENT_NAME))
+ self._set_agents([GuestAgent(path=agent_dir)
+ for agent_dir in glob.iglob(path) if os.path.isdir(agent_dir)])
+ self._filter_blacklisted_agents()
+ except Exception as e:
+ logger.warn(u"Exception occurred loading available agents: {0}", ustr(e))
return
def _purge_agents(self):
@@ -423,10 +536,51 @@ class UpdateHandler(object):
self.agents.sort(key=lambda agent: agent.version, reverse=True)
return
+ def _set_sentinal(self, agent=CURRENT_AGENT):
+ try:
+ fileutil.write_file(self._sentinal_file_path(), agent)
+ except Exception as e:
+ logger.warn(
+ u"Exception writing sentinal file {0}: {1}",
+ self._sentinal_file_path(),
+ str(e))
+ return
+
+ def _sentinal_file_path(self):
+ return os.path.join(conf.get_lib_dir(), AGENT_SENTINAL_FILE)
+
+ def _shutdown(self):
+ if not os.path.isfile(self._sentinal_file_path()):
+ return
+
+ try:
+ os.remove(self._sentinal_file_path())
+ except Exception as e:
+ logger.warn(
+ u"Exception removing sentinal file {0}: {1}",
+ self._sentinal_file_path(),
+ str(e))
+ return
+
+ def _write_pid_file(self):
+ previous_pid_file, pid_file = self._get_pid_files()
+ try:
+ fileutil.write_file(pid_file, ustr(os.getpid()))
+ logger.info(u"{0} running as process {1}", CURRENT_AGENT, ustr(os.getpid()))
+ except Exception as e:
+ pid_file = None
+ logger.warn(
+ u"Expection writing goal state agent {0} pid to {1}: {2}",
+ CURRENT_AGENT,
+ pid_file,
+ ustr(e))
+ return previous_pid_file, pid_file
+
class GuestAgent(object):
- def __init__(self, path=None, pkg=None):
+ def __init__(self, path=None, pkg=None, host=None):
self.pkg = pkg
+ self.host = host
version = None
if path is not None:
m = AGENT_DIR_PATTERN.match(path)
@@ -543,18 +697,15 @@ class GuestAgent(object):
return
def _download(self):
- package = None
-
for uri in self.pkg.uris:
- try:
- resp = restutil.http_get(uri.uri, chk_proxy=True)
- 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:
- logger.warn(u"Agent {0} download from {1} failed", self.name, uri.uri)
+ if self._fetch(uri.uri):
+ break
+ else:
+ if self.host is not None:
+ logger.info("Download unsuccessful, falling back to host plugin")
+ uri, headers = self.host.get_artifact_request(uri.uri, self.host.manifest_uri)
+ if self._fetch(uri, headers=headers):
+ break
if not os.path.isfile(self.get_agent_pkg_path()):
msg = u"Unable to download Agent {0} from any URI".format(self.name)
@@ -567,6 +718,23 @@ class GuestAgent(object):
raise UpdateError(msg)
return
+ def _fetch(self, uri, headers=None):
+ package = None
+ try:
+ resp = restutil.http_get(uri, chk_proxy=True, headers=headers)
+ 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)
+ except restutil.HttpError as http_error:
+ logger.verbose(u"Agent {0} download from {1} failed [{2}]",
+ self.name,
+ uri,
+ http_error)
+ return package is not None
+
def _load_error(self):
try:
if self.error is None: