summaryrefslogtreecommitdiff
path: root/azurelinuxagent/ga
diff options
context:
space:
mode:
authorƁukasz 'sil2100' Zemczak <lukasz.zemczak@ubuntu.com>2017-05-18 19:58:02 +0200
committerusd-importer <ubuntu-server@lists.ubuntu.com>2017-05-31 09:53:12 +0000
commit4fb0b5a09b26135ade285844da5d7dfe582a8d4c (patch)
tree09b1e5867d6e7501118cdd0af0012b51fc216530 /azurelinuxagent/ga
parent473ad6fbfe0b9c3b362b530492928303f2b4c7f3 (diff)
downloadvyos-walinuxagent-4fb0b5a09b26135ade285844da5d7dfe582a8d4c.tar.gz
vyos-walinuxagent-4fb0b5a09b26135ade285844da5d7dfe582a8d4c.zip
Import patches-unapplied version 2.2.12-0ubuntu1 to ubuntu/artful-proposed
Imported using git-ubuntu import. Changelog parent: 473ad6fbfe0b9c3b362b530492928303f2b4c7f3 New changelog entries: * New upstream release (LP: #1690854). - Refreshed debian/patches/disable_import_test.patch.
Diffstat (limited to 'azurelinuxagent/ga')
-rw-r--r--azurelinuxagent/ga/exthandlers.py84
-rw-r--r--azurelinuxagent/ga/monitor.py6
-rw-r--r--azurelinuxagent/ga/update.py179
3 files changed, 238 insertions, 31 deletions
diff --git a/azurelinuxagent/ga/exthandlers.py b/azurelinuxagent/ga/exthandlers.py
index e0125aa..b44ed6d 100644
--- a/azurelinuxagent/ga/exthandlers.py
+++ b/azurelinuxagent/ga/exthandlers.py
@@ -20,6 +20,8 @@
import glob
import json
import os
+import os.path
+import re
import shutil
import stat
import subprocess
@@ -31,6 +33,7 @@ import azurelinuxagent.common.logger as logger
import azurelinuxagent.common.utils.fileutil as fileutil
import azurelinuxagent.common.utils.restutil as restutil
import azurelinuxagent.common.utils.shellutil as shellutil
+import azurelinuxagent.common.version as version
from azurelinuxagent.common.event import add_event, WALAEventOperation
from azurelinuxagent.common.exception import ExtensionError, ProtocolError, HttpError
@@ -55,6 +58,13 @@ VALID_EXTENSION_STATUS = ['transitioning', 'error', 'success', 'warning']
VALID_HANDLER_STATUS = ['Ready', 'NotReady', "Installing", "Unresponsive"]
+HANDLER_PATTERN = "^([^-]+)-(\d+(?:\.\d+)*)"
+HANDLER_NAME_PATTERN = re.compile(HANDLER_PATTERN+"$", re.IGNORECASE)
+HANDLER_PKG_EXT = ".zip"
+HANDLER_PKG_PATTERN = re.compile(HANDLER_PATTERN+"\\"+HANDLER_PKG_EXT+"$",
+ re.IGNORECASE)
+
+
def validate_has_key(obj, key, fullname):
if key not in obj:
raise ExtensionError("Missing: {0}".format(fullname))
@@ -163,6 +173,7 @@ def get_exthandlers_handler():
class ExtHandlersHandler(object):
def __init__(self):
self.protocol_util = get_protocol_util()
+ self.protocol = None
self.ext_handlers = None
self.last_etag = None
self.log_report = False
@@ -188,10 +199,74 @@ class ExtHandlersHandler(object):
self.last_etag = etag
self.report_ext_handlers_status()
+ self.cleanup_outdated_handlers()
def run_status(self):
self.report_ext_handlers_status()
return
+
+ def cleanup_outdated_handlers(self):
+ handlers = []
+ pkgs = []
+
+ # Build a collection of uninstalled handlers and orphaned packages
+ # Note:
+ # -- An orphaned package is one without a corresponding handler
+ # directory
+ for item in os.listdir(conf.get_lib_dir()):
+ path = os.path.join(conf.get_lib_dir(), item)
+
+ if version.is_agent_package(path) or version.is_agent_path(path):
+ continue
+
+ if os.path.isdir(path):
+ if re.match(HANDLER_NAME_PATTERN, item) is None:
+ continue
+ try:
+ eh = ExtHandler()
+
+ separator = item.rfind('-')
+
+ eh.name = item[0:separator]
+ eh.properties.version = str(FlexibleVersion(item[separator+1:]))
+
+ handler = ExtHandlerInstance(eh, self.protocol)
+ except Exception as e:
+ continue
+ if handler.get_handler_state() != ExtHandlerState.NotInstalled:
+ continue
+ handlers.append(handler)
+
+ elif os.path.isfile(path) and \
+ not os.path.isdir(path[0:-len(HANDLER_PKG_EXT)]):
+ if not re.match(HANDLER_PKG_PATTERN, item):
+ continue
+ pkgs.append(path)
+
+ # Then, remove the orphaned packages
+ for pkg in pkgs:
+ try:
+ os.remove(pkg)
+ logger.verbose("Removed orphaned extension package "
+ "{0}".format(pkg))
+ except Exception as e:
+ logger.warn("Failed to remove orphaned package: {0}".format(
+ pkg))
+
+ # Finally, remove the directories and packages of the
+ # uninstalled handlers
+ for handler in handlers:
+ handler.rm_ext_handler_dir()
+ pkg = os.path.join(conf.get_lib_dir(),
+ handler.get_full_name() + HANDLER_PKG_EXT)
+ if os.path.isfile(pkg):
+ try:
+ os.remove(pkg)
+ logger.verbose("Removed extension package "
+ "{0}".format(pkg))
+ except Exception as e:
+ logger.warn("Failed to remove extension package: "
+ "{0}".format(pkg))
def handle_ext_handlers(self, etag=None):
if self.ext_handlers.extHandlers is None or \
@@ -478,6 +553,14 @@ class ExtHandlerInstance(object):
separator = path.rfind('-')
version = FlexibleVersion(path[separator+1:])
+ state_path = os.path.join(path, 'config', 'HandlerState')
+
+ if not os.path.exists(state_path) or \
+ fileutil.read_file(state_path) == \
+ ExtHandlerState.NotInstalled:
+ logger.verbose("Ignoring version of uninstalled extension: "
+ "{0}".format(path))
+ continue
if lastest_version is None or lastest_version < version:
lastest_version = version
@@ -615,6 +698,7 @@ class ExtHandlerInstance(object):
except IOError as e:
message = "Failed to remove extension handler directory: {0}".format(e)
self.report_event(message=message, is_success=False)
+ self.logger.warn(message)
def update(self):
self.set_operation(WALAEventOperation.Update)
diff --git a/azurelinuxagent/ga/monitor.py b/azurelinuxagent/ga/monitor.py
index 7ef7f04..dcfd6a4 100644
--- a/azurelinuxagent/ga/monitor.py
+++ b/azurelinuxagent/ga/monitor.py
@@ -178,11 +178,11 @@ class MonitorHandler(object):
logger.error("{0}", e)
def daemon(self):
- last_heartbeat = datetime.datetime.min
period = datetime.timedelta(minutes=30)
+ last_heartbeat = datetime.datetime.utcnow() - period
while True:
- if (datetime.datetime.now() - last_heartbeat) > period:
- last_heartbeat = datetime.datetime.now()
+ if datetime.datetime.utcnow() >= (last_heartbeat + period):
+ last_heartbeat = datetime.datetime.utcnow()
add_event(
op=WALAEventOperation.HeartBeat,
name=CURRENT_AGENT,
diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py
index 203bb41..67eb785 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -30,13 +30,17 @@ import time
import traceback
import zipfile
+from datetime import datetime, timedelta
+
import azurelinuxagent.common.conf as conf
import azurelinuxagent.common.logger as logger
import azurelinuxagent.common.utils.fileutil as fileutil
import azurelinuxagent.common.utils.restutil as restutil
import azurelinuxagent.common.utils.textutil as textutil
-from azurelinuxagent.common.event import add_event, WALAEventOperation
+from azurelinuxagent.common.event import add_event, \
+ elapsed_milliseconds, \
+ WALAEventOperation
from azurelinuxagent.common.exception import UpdateError, ProtocolError
from azurelinuxagent.common.future import ustr
from azurelinuxagent.common.osutil import get_osutil
@@ -53,6 +57,7 @@ from azurelinuxagent.ga.exthandlers import HandlerManifest
AGENT_ERROR_FILE = "error.json" # File name for agent error record
AGENT_MANIFEST_FILE = "HandlerManifest.json"
+AGENT_SUPPORTED_FILE = "supported.json"
CHILD_HEALTH_INTERVAL = 15 * 60
CHILD_LAUNCH_INTERVAL = 5 * 60
@@ -60,16 +65,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
+GOAL_STATE_INTERVAL = 3
REPORT_STATUS_INTERVAL = 15
ORPHAN_WAIT_INTERVAL = 15 * 60 * 60
AGENT_SENTINAL_FILE = "current_version"
-
def get_update_handler():
return UpdateHandler()
@@ -98,7 +101,7 @@ class UpdateHandler(object):
self.signal_handler = None
return
- def run_latest(self):
+ def run_latest(self, child_args=None):
"""
This method is called from the daemon to find and launch the most
current, downloaded agent.
@@ -127,6 +130,9 @@ class UpdateHandler(object):
agent_name = latest_agent.name
agent_version = latest_agent.version
+ if child_args is not None:
+ agent_cmd = "{0} {1}".format(agent_cmd, child_args)
+
try:
# Launch the correct Python version for python-based agents
@@ -198,7 +204,7 @@ class UpdateHandler(object):
ret)
logger.warn(msg)
if latest_agent is not None:
- latest_agent.mark_failure()
+ latest_agent.mark_failure(is_fatal=True)
except Exception as e:
msg = u"Agent {0} launched with command '{1}' failed with exception: {2}".format(
@@ -237,10 +243,11 @@ class UpdateHandler(object):
migrate_handler_state()
try:
+ send_event_time = datetime.utcnow()
+
self._ensure_no_orphans()
self._emit_restart_event()
- # TODO: Add means to stop running
while self.running:
if self._is_orphaned:
logger.info("Goal state agent {0} was orphaned -- exiting", CURRENT_AGENT)
@@ -254,8 +261,29 @@ class UpdateHandler(object):
self.agents[0].name)
break
+ utc_start = datetime.utcnow()
+
+ last_etag = exthandlers_handler.last_etag
exthandlers_handler.run()
-
+
+ log_event = last_etag != exthandlers_handler.last_etag or \
+ (datetime.utcnow() >= send_event_time)
+ add_event(
+ AGENT_NAME,
+ version=CURRENT_VERSION,
+ op=WALAEventOperation.ProcessGoalState,
+ is_success=True,
+ duration=elapsed_milliseconds(utc_start),
+ log_event=log_event)
+ if log_event:
+ send_event_time += timedelta(minutes=REPORT_STATUS_INTERVAL)
+
+ test_agent = self.get_test_agent()
+ if test_agent is not None and test_agent.in_slice:
+ test_agent.enable()
+ logger.info(u"Enabled Agent {0} as test agent", test_agent.name)
+ break
+
time.sleep(GOAL_STATE_INTERVAL)
except Exception as e:
@@ -305,13 +333,21 @@ class UpdateHandler(object):
if not conf.get_autoupdate_enabled():
return None
- self._load_agents()
+ self._find_agents()
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 get_test_agent(self):
+ agent = None
+ agents = [agent for agent in self._load_agents() if agent.is_test]
+ if len(agents) > 0:
+ agents.sort(key=lambda agent: agent.version, reverse=True)
+ agent = agents[0]
+ return agent
+
def _emit_restart_event(self):
if not self._is_clean_start:
msg = u"{0} did not terminate cleanly".format(CURRENT_AGENT)
@@ -390,6 +426,7 @@ class UpdateHandler(object):
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()
@@ -457,6 +494,17 @@ class UpdateHandler(object):
self.agents = [agent for agent in self.agents if not agent.is_blacklisted]
return
+ def _find_agents(self):
+ """
+ Load all non-blacklisted agents currently on disk.
+ """
+ try:
+ self._set_agents(self._load_agents())
+ self._filter_blacklisted_agents()
+ except Exception as e:
+ logger.warn(u"Exception occurred loading available agents: {0}", ustr(e))
+ return
+
def _get_pid_files(self):
pid_file = conf.get_agent_pid_file_path()
@@ -502,17 +550,9 @@ class UpdateHandler(object):
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.
- """
- 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
+ path = os.path.join(conf.get_lib_dir(), "{0}-*".format(AGENT_NAME))
+ return [GuestAgent(path=agent_dir)
+ for agent_dir in glob.iglob(path) if os.path.isdir(agent_dir)]
def _purge_agents(self):
"""
@@ -610,7 +650,11 @@ class GuestAgent(object):
logger.verbose(u"Instantiating Agent {0} from {1}", self.name, location)
self.error = None
+ self.supported = None
+
self._load_error()
+ self._load_supported()
+
self._ensure_downloaded()
return
@@ -633,10 +677,19 @@ class GuestAgent(object):
def get_agent_pkg_path(self):
return ".".join((os.path.join(conf.get_lib_dir(), self.name), "zip"))
+ def get_agent_supported_file(self):
+ return os.path.join(conf.get_lib_dir(), self.name, AGENT_SUPPORTED_FILE)
+
def clear_error(self):
self.error.clear()
return
+ def enable(self):
+ if self.error.is_sentinel:
+ self.error.clear()
+ self.error.save()
+ return
+
@property
def is_available(self):
return self.is_downloaded and not self.is_blacklisted
@@ -649,6 +702,14 @@ class GuestAgent(object):
def is_downloaded(self):
return self.is_blacklisted or os.path.isfile(self.get_agent_manifest_path())
+ @property
+ def is_test(self):
+ return self.error.is_sentinel and self.supported.is_supported
+
+ @property
+ def in_slice(self):
+ return self.is_test and self.supported.in_slice
+
def mark_failure(self, is_fatal=False):
try:
if not os.path.isdir(self.get_agent_dir()):
@@ -666,7 +727,7 @@ class GuestAgent(object):
logger.verbose(u"Ensuring Agent {0} is downloaded", self.name)
if self.is_blacklisted:
- logger.warn(u"Agent {0} is blacklisted - skipping download", self.name)
+ logger.info(u"Agent {0} is blacklisted - skipping download", self.name)
return
if self.is_downloaded:
@@ -682,6 +743,7 @@ class GuestAgent(object):
self._unpack()
self._load_manifest()
self._load_error()
+ self._load_supported()
msg = u"Agent {0} downloaded successfully".format(self.name)
logger.verbose(msg)
@@ -770,6 +832,12 @@ class GuestAgent(object):
logger.warn(u"Agent {0} failed loading error state: {1}", self.name, ustr(e))
return
+ def _load_supported(self):
+ try:
+ self.supported = Supported(self.get_agent_supported_file())
+ except Exception as e:
+ self.supported = Supported()
+
def _load_manifest(self):
path = self.get_agent_manifest_path()
if not os.path.isfile(path):
@@ -859,18 +927,15 @@ class GuestAgentError(object):
self.failure_count = 0
self.was_fatal = False
return
-
- def clear_old_failure(self):
- if self.last_failure <= 0.0:
- return
- if self.last_failure < (time.time() - RETAIN_INTERVAL):
- self.clear()
- return
@property
def is_blacklisted(self):
return self.was_fatal or self.failure_count >= MAX_FAILURE
+ @property
+ def is_sentinel(self):
+ return self.was_fatal and self.last_failure == 0.0
+
def load(self):
if self.path is not None and os.path.isfile(self.path):
with open(self.path, 'r') as f:
@@ -906,3 +971,61 @@ class GuestAgentError(object):
self.last_failure,
self.failure_count,
self.was_fatal)
+
+class Supported(object):
+ def __init__(self, path):
+ if path is None:
+ raise UpdateError(u"Supported requires a path")
+ self.path = path
+
+ self._load()
+ return
+
+ @property
+ def is_supported(self):
+ return self._supported_distribution is not None
+
+ @property
+ def in_slice(self):
+ d = self._supported_distribution
+ return d is not None and d.in_slice
+
+ @property
+ def _supported_distribution(self):
+ for d in self.distributions:
+ dd = self.distributions[d]
+ if dd.is_supported:
+ return dd
+ return None
+
+ def _load(self):
+ self.distributions = {}
+ try:
+ if self.path is not None and os.path.isfile(self.path):
+ j = json.loads(fileutil.read_file(self.path))
+ for d in j:
+ self.distributions[d] = SupportedDistribution(j[d])
+ except Exception as e:
+ logger.warn("Failed JSON parse of {0}: {1}".format(self.path, e))
+ return
+
+class SupportedDistribution(object):
+ def __init__(self, s):
+ if s is None or not isinstance(s, dict):
+ raise UpdateError(u"SupportedDisribution requires a dictionary")
+
+ self.slice = s['slice']
+ self.versions = s['versions']
+
+ @property
+ def is_supported(self):
+ d = ','.join(platform.linux_distribution())
+ for v in self.versions:
+ if re.match(v, d):
+ return True
+ return False
+
+ @property
+ def in_slice(self):
+ n = int((60 * self.slice) / 100)
+ return (n - datetime.utcnow().second) > 0