summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md15
-rw-r--r--azurelinuxagent/agent.py18
-rw-r--r--azurelinuxagent/common/conf.py9
-rw-r--r--azurelinuxagent/common/errorstate.py31
-rw-r--r--azurelinuxagent/common/event.py92
-rw-r--r--azurelinuxagent/common/exception.py42
-rw-r--r--azurelinuxagent/common/future.py7
-rw-r--r--azurelinuxagent/common/logger.py34
-rw-r--r--azurelinuxagent/common/osutil/default.py97
-rw-r--r--azurelinuxagent/common/osutil/factory.py9
-rw-r--r--azurelinuxagent/common/osutil/ubuntu.py37
-rw-r--r--azurelinuxagent/common/protocol/metadata.py7
-rw-r--r--azurelinuxagent/common/protocol/restapi.py12
-rw-r--r--azurelinuxagent/common/protocol/util.py21
-rw-r--r--azurelinuxagent/common/protocol/wire.py221
-rw-r--r--azurelinuxagent/common/utils/cryptutil.py11
-rw-r--r--azurelinuxagent/common/utils/fileutil.py35
-rw-r--r--azurelinuxagent/common/utils/flexible_version.py2
-rw-r--r--azurelinuxagent/common/utils/restutil.py105
-rw-r--r--azurelinuxagent/common/utils/shellutil.py26
-rw-r--r--azurelinuxagent/common/version.py5
-rw-r--r--azurelinuxagent/ga/env.py147
-rw-r--r--azurelinuxagent/ga/exthandlers.py236
-rw-r--r--azurelinuxagent/ga/monitor.py43
-rw-r--r--azurelinuxagent/ga/update.py280
-rw-r--r--azurelinuxagent/pa/deprovision/default.py1
-rw-r--r--azurelinuxagent/pa/provision/cloudinit.py11
-rw-r--r--azurelinuxagent/pa/provision/default.py58
-rw-r--r--azurelinuxagent/pa/provision/factory.py2
-rw-r--r--azurelinuxagent/pa/rdma/suse.py29
-rw-r--r--config/alpine/waagent.conf5
-rw-r--r--config/arch/waagent.conf5
-rw-r--r--config/bigip/waagent.conf5
-rw-r--r--config/clearlinux/waagent.conf5
-rw-r--r--config/coreos/waagent.conf5
-rw-r--r--config/freebsd/waagent.conf5
-rw-r--r--config/gaia/waagent.conf5
-rw-r--r--config/openbsd/waagent.conf3
-rw-r--r--config/suse/waagent.conf5
-rw-r--r--config/ubuntu/waagent.conf5
-rw-r--r--config/waagent.conf6
-rw-r--r--debian/changelog7
-rw-r--r--debian/patches/disable_import_test.patch10
-rw-r--r--tests/common/osutil/test_default.py127
-rw-r--r--tests/common/test_conf.py7
-rw-r--r--tests/common/test_errorstate.py69
-rw-r--r--tests/common/test_event.py15
-rw-r--r--tests/common/test_logger.py40
-rw-r--r--tests/data/ext/sample_ext-1.3.0.zip (renamed from tests/data/ext/sample_ext-1.2.0.zip)bin878 -> 878 bytes
-rw-r--r--tests/data/ext/sample_ext-1.3.0/HandlerManifest.json (renamed from tests/data/ext/sample_ext-1.2.0/HandlerManifest.json)0
-rwxr-xr-xtests/data/ext/sample_ext-1.3.0/sample.py (renamed from tests/data/ext/sample_ext-1.2.0/sample.py)0
-rw-r--r--tests/data/ga/WALinuxAgent-2.2.14.zipbin500633 -> 0 bytes
-rw-r--r--tests/data/ga/WALinuxAgent-2.2.19.zipbin0 -> 514126 bytes
-rw-r--r--tests/data/ga/supported.json8
-rw-r--r--tests/data/safe_deploy.json23
-rw-r--r--tests/data/test_waagent.conf17
-rw-r--r--tests/data/wire/ext_conf_autoupgrade_internalversion.xml4
-rw-r--r--tests/data/wire/ext_conf_internalversion.xml4
-rw-r--r--tests/data/wire/ext_conf_upgradeguid.xml26
-rw-r--r--tests/data/wire/ga_manifest.xml12
-rw-r--r--tests/data/wire/ga_manifest_1.xml18
-rw-r--r--tests/data/wire/ga_manifest_2.xml24
-rw-r--r--tests/data/wire/manifest.xml16
-rw-r--r--tests/ga/test_env.py86
-rw-r--r--tests/ga/test_extension.py138
-rw-r--r--tests/ga/test_update.py658
-rw-r--r--tests/pa/test_provision.py85
-rw-r--r--tests/protocol/mockwiredata.py5
-rw-r--r--tests/protocol/test_metadata.py2
-rw-r--r--tests/protocol/test_wire.py26
-rw-r--r--tests/test_agent.py42
-rw-r--r--tests/tools.py26
-rw-r--r--tests/utils/test_flexible_version.py54
-rw-r--r--tests/utils/test_rest_util.py123
74 files changed, 2431 insertions, 938 deletions
diff --git a/README.md b/README.md
index 88ad37a..d9850db 100644
--- a/README.md
+++ b/README.md
@@ -181,12 +181,12 @@ ResourceDisk.MountPoint=/mnt/resource
ResourceDisk.MountOptions=None
ResourceDisk.EnableSwap=n
ResourceDisk.SwapSizeMB=0
-LBProbeResponder=y
Logs.Verbose=n
OS.AllowHTTP=n
OS.RootDeviceScsiTimeout=300
OS.EnableFIPS=n
OS.OpensslPath=None
+OS.SshClientAliveInterval=180
OS.SshDir=/etc/ssh
HttpProxy.Host=None
HttpProxy.Port=None
@@ -329,13 +329,6 @@ _Default: 0_
The size of the swap file in megabytes.
-* __LBProbeResponder__
-_Type: Boolean_
-_Default: y_
-
-If set, waagent will respond to load balancer probes from the platform (if
-present).
-
* Logs.Verbose
_Type: Boolean_
_Default: n_
@@ -383,6 +376,12 @@ _Default: None_
This can be used to specify an alternate path for the openssl binary to use for
cryptographic operations.
+* __OS.SshClientAliveInterval__
+_Type: Integer_
+_Default: 180_
+
+This values sets the number of seconds the agent uses for the SSH ClientAliveInterval configuration option.
+
* __OS.SshDir__
_Type: String_
_Default: `/etc/ssh`_
diff --git a/azurelinuxagent/agent.py b/azurelinuxagent/agent.py
index e99f7be..4ab7aa0 100644
--- a/azurelinuxagent/agent.py
+++ b/azurelinuxagent/agent.py
@@ -63,6 +63,9 @@ class Agent(object):
path="/var/log/waagent.log")
logger.add_logger_appender(logger.AppenderType.CONSOLE, level,
path="/dev/console")
+ logger.add_logger_appender(logger.AppenderType.TELEMETRY,
+ logger.LogLevel.WARNING,
+ path=event.add_log_event)
ext_log_dir = conf.get_ext_log_dir()
try:
@@ -144,7 +147,7 @@ def main(args=[]):
if command == "version":
version()
elif command == "help":
- usage()
+ print(usage())
elif command == "start":
start(conf_file_path=conf_file_path)
else:
@@ -228,15 +231,16 @@ def version():
def usage():
"""
- Show agent usage
+ Return agent usage message
"""
- print("")
- print((("usage: {0} [-verbose] [-force] [-help] "
+ s = "\n"
+ s += ("usage: {0} [-verbose] [-force] [-help] "
"-configuration-path:<path to configuration file>"
"-deprovision[+user]|-register-service|-version|-daemon|-start|"
- "-run-exthandlers]"
- "").format(sys.argv[0])))
- print("")
+ "-run-exthandlers|-show-configuration]"
+ "").format(sys.argv[0])
+ s += "\n"
+ return s
def start(conf_file_path=None):
"""
diff --git a/azurelinuxagent/common/conf.py b/azurelinuxagent/common/conf.py
index 75a0248..33f953a 100644
--- a/azurelinuxagent/common/conf.py
+++ b/azurelinuxagent/common/conf.py
@@ -40,11 +40,11 @@ class ConfigurationProvider(object):
raise AgentConfigError("Can't not parse empty configuration")
for line in content.split('\n'):
if not line.startswith("#") and "=" in line:
- parts = line.split('=')
+ parts = line.split('=', 1)
if len(parts) < 2:
continue
key = parts[0].strip()
- value = parts[1].strip("\" ")
+ value = parts[1].split('#')[0].strip("\" ").strip()
self.values[key] = value if value != "None" else None
def get(self, key, default_val):
@@ -131,12 +131,14 @@ __STRING_OPTIONS__ = {
}
__INTEGER_OPTIONS__ = {
+ "OS.SshClientAliveInterval" : 180,
"Provisioning.PasswordCryptSaltLength" : 10,
"HttpProxy.Port" : None,
"ResourceDisk.SwapSizeMB" : 0,
"Autoupdate.Frequency" : 3600
}
+
def get_configuration(conf=__conf__):
options = {}
for option in __SWITCH_OPTIONS__:
@@ -190,6 +192,9 @@ def get_fips_enabled(conf=__conf__):
def get_openssl_cmd(conf=__conf__):
return conf.get("OS.OpensslPath", "/usr/bin/openssl")
+def get_ssh_client_alive_interval(conf=__conf__):
+ return conf.get("OS.SshClientAliveInterval", 180)
+
def get_ssh_dir(conf=__conf__):
return conf.get("OS.SshDir", "/etc/ssh")
diff --git a/azurelinuxagent/common/errorstate.py b/azurelinuxagent/common/errorstate.py
new file mode 100644
index 0000000..750aa77
--- /dev/null
+++ b/azurelinuxagent/common/errorstate.py
@@ -0,0 +1,31 @@
+from datetime import datetime, timedelta
+
+ERROR_STATE_DELTA = timedelta(minutes=15)
+
+
+class ErrorState(object):
+ def __init__(self, min_timedelta = ERROR_STATE_DELTA):
+ self.min_timedelta = min_timedelta
+
+ self.count = 0
+ self.timestamp = None
+
+ def incr(self):
+ if self.count == 0:
+ self.timestamp = datetime.utcnow()
+
+ self.count += 1
+
+ def reset(self):
+ self.count = 0
+ self.timestamp = None
+
+ def is_triggered(self):
+ if self.timestamp is None:
+ return False
+
+ delta = datetime.utcnow() - self.timestamp
+ if delta >= self.min_timedelta:
+ return True
+
+ return False
diff --git a/azurelinuxagent/common/event.py b/azurelinuxagent/common/event.py
index e62a925..84a439f 100644
--- a/azurelinuxagent/common/event.py
+++ b/azurelinuxagent/common/event.py
@@ -15,51 +15,58 @@
# Requires Python 2.4+ and Openssl 1.0+
#
-import os
-import sys
-import traceback
import atexit
+import datetime
import json
+import os
+import sys
import time
-import datetime
-import threading
-import platform
+import traceback
-from datetime import datetime, timedelta
+from datetime import datetime
import azurelinuxagent.common.conf as conf
import azurelinuxagent.common.logger as logger
-from azurelinuxagent.common.exception import EventError, ProtocolError
+from azurelinuxagent.common.exception import EventError
from azurelinuxagent.common.future import ustr
from azurelinuxagent.common.protocol.restapi import TelemetryEventParam, \
- TelemetryEventList, \
TelemetryEvent, \
- set_properties, get_properties
-from azurelinuxagent.common.version import DISTRO_NAME, DISTRO_VERSION, \
- DISTRO_CODE_NAME, AGENT_VERSION, \
- CURRENT_AGENT, CURRENT_VERSION
+ get_properties
+from azurelinuxagent.common.version import CURRENT_VERSION
_EVENT_MSG = "Event: name={0}, op={1}, message={2}, duration={3}"
+
class WALAEventOperation:
ActivateResourceDisk = "ActivateResourceDisk"
+ AgentBlacklisted = "AgentBlacklisted"
+ AgentEnabled = "AgentEnabled"
AutoUpdate = "AutoUpdate"
+ CustomData = "CustomData"
+ Deploy = "Deploy"
Disable = "Disable"
Download = "Download"
Enable = "Enable"
+ ExtensionProcessing = "ExtensionProcessing"
Firewall = "Firewall"
HealthCheck = "HealthCheck"
HeartBeat = "HeartBeat"
HostPlugin = "HostPlugin"
+ HttpErrors = "HttpErrors"
Install = "Install"
InitializeHostPlugin = "InitializeHostPlugin"
+ Log = "Log"
+ Partition = "Partition"
ProcessGoalState = "ProcessGoalState"
Provision = "Provision"
+ GuestState = "GuestState"
ReportStatus = "ReportStatus"
Restart = "Restart"
+ SkipUpdate = "SkipUpdate"
UnhandledError = "UnhandledError"
UnInstall = "UnInstall"
+ Unknown = "Unknown"
Upgrade = "Upgrade"
Update = "Update"
@@ -67,7 +74,7 @@ class WALAEventOperation:
class EventStatus(object):
EVENT_STATUS_FILE = "event_status.json"
- def __init__(self, status_dir=conf.get_lib_dir()):
+ def __init__(self):
self._path = None
self._status = {}
@@ -82,7 +89,7 @@ class EventStatus(object):
event = self._event_name(name, version, op)
if event not in self._status:
return True
- return self._status[event] == True
+ return self._status[event] is True
def initialize(self, status_dir=conf.get_lib_dir()):
self._path = os.path.join(status_dir, EventStatus.EVENT_STATUS_FILE)
@@ -90,7 +97,7 @@ class EventStatus(object):
def mark_event_status(self, name, version, op, status):
event = self._event_name(name, version, op)
- self._status[event] = (status == True)
+ self._status[event] = (status is True)
self._save()
def _event_name(self, name, version, op):
@@ -113,6 +120,7 @@ class EventStatus(object):
except Exception as e:
logger.warn("Exception occurred saving event status: {0}".format(e))
+
__event_status__ = EventStatus()
__event_status_operations__ = [
WALAEventOperation.AutoUpdate,
@@ -171,7 +179,7 @@ class EventLogger(object):
(self.periodic_events[h] + delta) <= datetime.now()
def add_periodic(self,
- delta, name, op="", is_success=True, duration=0,
+ delta, name, op=WALAEventOperation.Unknown, is_success=True, duration=0,
version=CURRENT_VERSION, message="", evt_type="",
is_internal=False, log_event=True, force=False):
@@ -186,7 +194,7 @@ class EventLogger(object):
def add_event(self,
name,
- op="",
+ op=WALAEventOperation.Unknown,
is_success=True,
duration=0,
version=CURRENT_VERSION,
@@ -215,6 +223,32 @@ class EventLogger(object):
except EventError as e:
logger.error("{0}", e)
+ def add_log_event(self, level, message):
+ # By the time the message has gotten to this point it is formatted as
+ #
+ # YYYY/MM/DD HH:mm:ss.fffffff LEVEL <text>.
+ #
+ # The timestamp and the level are redundant, and should be stripped.
+ # The logging library does not schematize this data, so I am forced
+ # to parse the message. The format is regular, so the burden is low.
+
+ parts = message.split(' ', 3)
+ msg = parts[3] if len(parts) == 4 \
+ else message
+
+ event = TelemetryEvent(7, "FFF0196F-EE4C-4EAF-9AA5-776F622DEB4F")
+ event.parameters.append(TelemetryEventParam('EventName', WALAEventOperation.Log))
+ event.parameters.append(TelemetryEventParam('CapabilityUsed', logger.LogLevel.STRINGS[level]))
+ event.parameters.append(TelemetryEventParam('Context1', msg))
+ event.parameters.append(TelemetryEventParam('Context2', ''))
+ event.parameters.append(TelemetryEventParam('Context3', ''))
+
+ data = get_properties(event)
+ try:
+ self.save_event(json.dumps(data))
+ except EventError:
+ pass
+
__event_logger__ = EventLogger()
@@ -224,6 +258,7 @@ def elapsed_milliseconds(utc_start):
return int(((d.days * 24 * 60 * 60 + d.seconds) * 1000) + \
(d.microseconds / 1000.0))
+
def report_event(op, is_success=True, message=''):
from azurelinuxagent.common.version import AGENT_NAME, CURRENT_VERSION
add_event(AGENT_NAME,
@@ -232,6 +267,7 @@ def report_event(op, is_success=True, message=''):
message=message,
op=op)
+
def report_periodic(delta, op, is_success=True, message=''):
from azurelinuxagent.common.version import AGENT_NAME, CURRENT_VERSION
add_periodic(delta, AGENT_NAME,
@@ -240,7 +276,9 @@ def report_periodic(delta, op, is_success=True, message=''):
message=message,
op=op)
-def add_event(name, op="", is_success=True, duration=0, version=CURRENT_VERSION,
+
+def add_event(name, op=WALAEventOperation.Unknown, is_success=True, duration=0,
+ version=CURRENT_VERSION,
message="", evt_type="", is_internal=False, log_event=True,
reporter=__event_logger__):
if reporter.event_dir is None:
@@ -255,8 +293,17 @@ def add_event(name, op="", is_success=True, duration=0, version=CURRENT_VERSION,
version=str(version), message=message, evt_type=evt_type,
is_internal=is_internal, log_event=log_event)
+
+def add_log_event(level, message, reporter=__event_logger__):
+ if reporter.event_dir is None:
+ return
+
+ reporter.add_log_event(level, message)
+
+
def add_periodic(
- delta, name, op="", is_success=True, duration=0, version=CURRENT_VERSION,
+ delta, name, op=WALAEventOperation.Unknown, is_success=True, duration=0,
+ version=CURRENT_VERSION,
message="", evt_type="", is_internal=False, log_event=True, force=False,
reporter=__event_logger__):
if reporter.event_dir is None:
@@ -269,10 +316,12 @@ def add_periodic(
version=str(version), message=message, evt_type=evt_type,
is_internal=is_internal, log_event=log_event, force=force)
+
def mark_event_status(name, version, op, status):
if op in __event_status_operations__:
__event_status__.mark_event_status(name, version, op, status)
+
def should_emit_event(name, version, op, status):
return \
op not in __event_status_operations__ or \
@@ -280,12 +329,15 @@ def should_emit_event(name, version, op, status):
not __event_status__.event_marked(name, version, op) or \
__event_status__.event_succeeded(name, version, op) != status
+
def init_event_logger(event_dir):
__event_logger__.event_dir = event_dir
+
def init_event_status(status_dir):
__event_status__.initialize(status_dir)
+
def dump_unhandled_err(name):
if hasattr(sys, 'last_type') and hasattr(sys, 'last_value') and \
hasattr(sys, 'last_traceback'):
diff --git a/azurelinuxagent/common/exception.py b/azurelinuxagent/common/exception.py
index 17c6ce0..bb8ab10 100644
--- a/azurelinuxagent/common/exception.py
+++ b/azurelinuxagent/common/exception.py
@@ -26,8 +26,8 @@ class AgentError(Exception):
Base class of agent error.
"""
- def __init__(self, errno, msg, inner=None):
- msg = u"[{0}] {1}".format(errno, msg)
+ def __init__(self, msg, inner=None):
+ msg = u"[{0}] {1}".format(type(self).__name__, msg)
if inner is not None:
msg = u"{0}\nInner error: {1}".format(msg, inner)
super(AgentError, self).__init__(msg)
@@ -39,16 +39,16 @@ class AgentConfigError(AgentError):
"""
def __init__(self, msg=None, inner=None):
- super(AgentConfigError, self).__init__('000001', msg, inner)
+ super(AgentConfigError, self).__init__(msg, inner)
class AgentNetworkError(AgentError):
"""
- When network is not avaiable.
+ When network is not available\.
"""
def __init__(self, msg=None, inner=None):
- super(AgentNetworkError, self).__init__('000002', msg, inner)
+ super(AgentNetworkError, self).__init__(msg, inner)
class ExtensionError(AgentError):
@@ -57,7 +57,7 @@ class ExtensionError(AgentError):
"""
def __init__(self, msg=None, inner=None):
- super(ExtensionError, self).__init__('000003', msg, inner)
+ super(ExtensionError, self).__init__(msg, inner)
class ProvisionError(AgentError):
@@ -66,7 +66,7 @@ class ProvisionError(AgentError):
"""
def __init__(self, msg=None, inner=None):
- super(ProvisionError, self).__init__('000004', msg, inner)
+ super(ProvisionError, self).__init__(msg, inner)
class ResourceDiskError(AgentError):
@@ -75,7 +75,7 @@ class ResourceDiskError(AgentError):
"""
def __init__(self, msg=None, inner=None):
- super(ResourceDiskError, self).__init__('000005', msg, inner)
+ super(ResourceDiskError, self).__init__(msg, inner)
class DhcpError(AgentError):
@@ -84,7 +84,8 @@ class DhcpError(AgentError):
"""
def __init__(self, msg=None, inner=None):
- super(DhcpError, self).__init__('000006', msg, inner)
+ super(DhcpError, self).__init__(msg, inner)
+
class OSUtilError(AgentError):
"""
@@ -92,7 +93,7 @@ class OSUtilError(AgentError):
"""
def __init__(self, msg=None, inner=None):
- super(OSUtilError, self).__init__('000007', msg, inner)
+ super(OSUtilError, self).__init__(msg, inner)
class ProtocolError(AgentError):
@@ -101,7 +102,7 @@ class ProtocolError(AgentError):
"""
def __init__(self, msg=None, inner=None):
- super(ProtocolError, self).__init__('000008', msg, inner)
+ super(ProtocolError, self).__init__(msg, inner)
class ProtocolNotFoundError(ProtocolError):
@@ -113,13 +114,22 @@ class ProtocolNotFoundError(ProtocolError):
super(ProtocolNotFoundError, self).__init__(msg, inner)
+class RestartError(ProtocolError):
+ """
+ Variant of ProtocolError used to restart processing if the GoalState
+ becomes stale.
+ """
+
+ pass
+
+
class HttpError(AgentError):
"""
Http request failure
"""
def __init__(self, msg=None, inner=None):
- super(HttpError, self).__init__('000009', msg, inner)
+ super(HttpError, self).__init__(msg, inner)
class EventError(AgentError):
@@ -128,7 +138,7 @@ class EventError(AgentError):
"""
def __init__(self, msg=None, inner=None):
- super(EventError, self).__init__('000010', msg, inner)
+ super(EventError, self).__init__(msg, inner)
class CryptError(AgentError):
@@ -137,7 +147,7 @@ class CryptError(AgentError):
"""
def __init__(self, msg=None, inner=None):
- super(CryptError, self).__init__('000011', msg, inner)
+ super(CryptError, self).__init__(msg, inner)
class UpdateError(AgentError):
@@ -146,7 +156,7 @@ class UpdateError(AgentError):
"""
def __init__(self, msg=None, inner=None):
- super(UpdateError, self).__init__('000012', msg, inner)
+ super(UpdateError, self).__init__(msg, inner)
class ResourceGoneError(HttpError):
@@ -155,4 +165,6 @@ class ResourceGoneError(HttpError):
"""
def __init__(self, msg=None, inner=None):
+ if msg is None:
+ msg = "Resource is gone"
super(ResourceGoneError, self).__init__(msg, inner)
diff --git a/azurelinuxagent/common/future.py b/azurelinuxagent/common/future.py
index 8d5b70b..8389cd3 100644
--- a/azurelinuxagent/common/future.py
+++ b/azurelinuxagent/common/future.py
@@ -1,10 +1,10 @@
import sys
"""
-Add alies for python2 and python3 libs and fucntions.
+Add alias for python2 and python3 libs and functions.
"""
-if sys.version_info[0]== 3:
+if sys.version_info[0] == 3:
import http.client as httpclient
from urllib.parse import urlparse
@@ -23,5 +23,4 @@ elif sys.version_info[0] == 2:
bytebuffer = buffer
else:
- raise ImportError("Unknown python version:{0}".format(sys.version_info))
-
+ raise ImportError("Unknown python version: {0}".format(sys.version_info))
diff --git a/azurelinuxagent/common/logger.py b/azurelinuxagent/common/logger.py
index bfdc73a..0a90718 100644
--- a/azurelinuxagent/common/logger.py
+++ b/azurelinuxagent/common/logger.py
@@ -17,8 +17,8 @@
"""
Log utils
"""
-import os
import sys
+
from azurelinuxagent.common.future import ustr
from datetime import datetime, timedelta
@@ -28,6 +28,7 @@ EVERY_HOUR = timedelta(hours=1)
EVERY_HALF_HOUR = timedelta(minutes=30)
EVERY_FIFTEEN_MINUTES = timedelta(minutes=15)
+
class Logger(object):
"""
Logger class
@@ -92,6 +93,7 @@ class Logger(object):
appender = _create_logger_appender(appender_type, level, path)
self.appenders.append(appender)
+
class ConsoleAppender(object):
def __init__(self, level, path):
self.level = level
@@ -105,6 +107,7 @@ class ConsoleAppender(object):
except IOError:
pass
+
class FileAppender(object):
def __init__(self, level, path):
self.level = level
@@ -118,6 +121,7 @@ class FileAppender(object):
except IOError:
pass
+
class StdoutAppender(object):
def __init__(self, level):
self.level = level
@@ -129,9 +133,24 @@ class StdoutAppender(object):
except IOError:
pass
+
+class TelemetryAppender(object):
+ def __init__(self, level, event_func):
+ self.level = level
+ self.event_func = event_func
+
+ def write(self, level, msg):
+ if self.level <= level:
+ try:
+ self.event_func(level, msg)
+ except IOError:
+ pass
+
+
#Initialize logger instance
DEFAULT_LOGGER = Logger()
+
class LogLevel(object):
VERBOSE = 0
INFO = 1
@@ -144,35 +163,46 @@ class LogLevel(object):
"ERROR"
]
+
class AppenderType(object):
FILE = 0
CONSOLE = 1
STDOUT = 2
+ TELEMETRY = 3
+
def add_logger_appender(appender_type, level=LogLevel.INFO, path=None):
DEFAULT_LOGGER.add_appender(appender_type, level, path)
+
def reset_periodic():
DEFAULT_LOGGER.reset_periodic()
+
def periodic(delta, msg_format, *args):
DEFAULT_LOGGER.periodic(delta, msg_format, *args)
+
def verbose(msg_format, *args):
DEFAULT_LOGGER.verbose(msg_format, *args)
+
def info(msg_format, *args):
DEFAULT_LOGGER.info(msg_format, *args)
+
def warn(msg_format, *args):
DEFAULT_LOGGER.warn(msg_format, *args)
+
def error(msg_format, *args):
DEFAULT_LOGGER.error(msg_format, *args)
+
def log(level, msg_format, *args):
DEFAULT_LOGGER.log(level, msg_format, args)
+
def _create_logger_appender(appender_type, level=LogLevel.INFO, path=None):
if appender_type == AppenderType.CONSOLE:
return ConsoleAppender(level, path)
@@ -180,6 +210,8 @@ def _create_logger_appender(appender_type, level=LogLevel.INFO, path=None):
return FileAppender(level, path)
elif appender_type == AppenderType.STDOUT:
return StdoutAppender(level)
+ elif appender_type == AppenderType.TELEMETRY:
+ return TelemetryAppender(level, path)
else:
raise ValueError("Unknown appender type")
diff --git a/azurelinuxagent/common/osutil/default.py b/azurelinuxagent/common/osutil/default.py
index f0b1050..16b7444 100644
--- a/azurelinuxagent/common/osutil/default.py
+++ b/azurelinuxagent/common/osutil/default.py
@@ -57,10 +57,21 @@ IPTABLES_VERSION = "iptables --version"
IPTABLES_LOCKING_VERSION = FlexibleVersion('1.4.21')
FIREWALL_ACCEPT = "iptables {0} -t security -{1} OUTPUT -d {2} -p tcp -m owner --uid-owner {3} -j ACCEPT"
-FIREWALL_DROP = "iptables {0} -t security -{1} OUTPUT -d {2} -p tcp -j DROP"
-FIREWALL_LIST = "iptables {0} -t security -L"
+# Note:
+# -- Initially "flight" the change to ACCEPT packets and develop a metric baseline
+# A subsequent release will convert the ACCEPT to DROP
+FIREWALL_DROP = "iptables {0} -t security -{1} OUTPUT -d {2} -p tcp -m conntrack --ctstate INVALID,NEW -j ACCEPT"
+# FIREWALL_DROP = "iptables {0} -t security -{1} OUTPUT -d {2} -p tcp -m conntrack --ctstate INVALID,NEW -j DROP"
+FIREWALL_LIST = "iptables {0} -t security -L -nxv"
+FIREWALL_PACKETS = "iptables {0} -t security -L OUTPUT --zero OUTPUT -nxv"
FIREWALL_FLUSH = "iptables {0} -t security --flush"
+# Precisely delete the rules created by the agent.
+FIREWALL_DELETE_CONNTRACK = "iptables {0} -t security -D OUTPUT -d {1} -p tcp -m conntrack --ctstate INVALID,NEW -j ACCEPT"
+FIREWALL_DELETE_OWNER = "iptables {0} -t security -D OUTPUT -d {1} -p tcp -m owner --uid-owner {2} -j ACCEPT"
+
+PACKET_PATTERN = "^\s*(\d+)\s+(\d+)\s+DROP\s+.*{0}[^\d]*$"
+
_enable_firewall = True
DMIDECODE_CMD = 'dmidecode --string system-uuid'
@@ -69,13 +80,45 @@ UUID_PATTERN = re.compile(
r'^\s*[A-F0-9]{8}(?:\-[A-F0-9]{4}){3}\-[A-F0-9]{12}\s*$',
re.IGNORECASE)
-class DefaultOSUtil(object):
+class DefaultOSUtil(object):
def __init__(self):
self.agent_conf_file_path = '/etc/waagent.conf'
self.selinux = None
self.disable_route_warning = False
+ def get_firewall_dropped_packets(self, dst_ip=None):
+ # If a previous attempt failed, do not retry
+ global _enable_firewall
+ if not _enable_firewall:
+ return 0
+
+ try:
+ wait = self.get_firewall_will_wait()
+
+ rc, output = shellutil.run_get_output(FIREWALL_PACKETS.format(wait))
+ if rc == 3:
+ # Transient error that we ignore. This code fires every loop
+ # of the daemon (60m), so we will get the value eventually.
+ return 0
+
+ if rc != 0:
+ return -1
+
+ pattern = re.compile(PACKET_PATTERN.format(dst_ip))
+ for line in output.split('\n'):
+ m = pattern.match(line)
+ if m is not None:
+ return int(m.group(1))
+
+ return 0
+
+ except Exception as e:
+ _enable_firewall = False
+ logger.warn("Unable to retrieve firewall packets dropped"
+ "{0}".format(ustr(e)))
+ return -1
+
def get_firewall_will_wait(self):
# Determine if iptables will serialize access
rc, output = shellutil.run_get_output(IPTABLES_VERSION)
@@ -95,31 +138,46 @@ class DefaultOSUtil(object):
else ""
return wait
- def remove_firewall(self):
- # If a previous attempt threw an exception, do not retry
+ def _delete_rule(self, rule):
+ """
+ Continually execute the delete operation until the return
+ code is non-zero or the limit has been reached.
+ """
+ for i in range(1, 100):
+ rc = shellutil.run(rule, chk_err=False)
+ if rc == 1:
+ return
+ elif rc == 2:
+ raise Exception("invalid firewall deletion rule '{0}'".format(rule))
+
+ def remove_firewall(self, dst_ip=None, uid=None):
+ # If a previous attempt failed, do not retry
global _enable_firewall
if not _enable_firewall:
return False
try:
+ if dst_ip is None or uid is None:
+ msg = "Missing arguments to enable_firewall"
+ logger.warn(msg)
+ raise Exception(msg)
+
wait = self.get_firewall_will_wait()
- flush_rule = FIREWALL_FLUSH.format(wait)
- if shellutil.run(flush_rule, chk_err=False) != 0:
- logger.warn("Failed to flush firewall")
+ self._delete_rule(FIREWALL_DELETE_CONNTRACK.format(wait, dst_ip))
+ self._delete_rule(FIREWALL_DELETE_OWNER.format(wait, dst_ip, uid))
return True
except Exception as e:
_enable_firewall = False
- logger.info("Unable to flush firewall -- "
+ logger.info("Unable to remove firewall -- "
"no further attempts will be made: "
"{0}".format(ustr(e)))
return False
def enable_firewall(self, dst_ip=None, uid=None):
-
- # If a previous attempt threw an exception, do not retry
+ # If a previous attempt failed, do not retry
global _enable_firewall
if not _enable_firewall:
return False
@@ -134,10 +192,15 @@ class DefaultOSUtil(object):
# If the DROP rule exists, make no changes
drop_rule = FIREWALL_DROP.format(wait, "C", dst_ip)
-
- if shellutil.run(drop_rule, chk_err=False) == 0:
+ rc = shellutil.run(drop_rule, chk_err=False)
+ if rc == 0:
logger.verbose("Firewall appears established")
return True
+ elif rc == 2:
+ self.remove_firewall(dst_ip, uid)
+ msg = "please upgrade iptables to a version that supports the -C option"
+ logger.warn(msg)
+ raise Exception(msg)
# Otherwise, append both rules
accept_rule = FIREWALL_ACCEPT.format(wait, "A", dst_ip, uid)
@@ -309,11 +372,11 @@ class DefaultOSUtil(object):
else:
sudoer = "{0} ALL=(ALL) ALL".format(username)
if not os.path.isfile(sudoers_wagent) or \
- fileutil.findstr_in_file(sudoers_wagent, sudoer) is None:
+ fileutil.findstr_in_file(sudoers_wagent, sudoer) is False:
fileutil.append_file(sudoers_wagent, "{0}\n".format(sudoer))
fileutil.chmod(sudoers_wagent, 0o440)
else:
- #Remove user from sudoers
+ # remove user from sudoers
if os.path.isfile(sudoers_wagent):
try:
content = fileutil.read_file(sudoers_wagent)
@@ -440,7 +503,7 @@ class DefaultOSUtil(object):
conf_file = fileutil.read_file(conf_file_path).split("\n")
textutil.set_ssh_config(conf_file, "PasswordAuthentication", option)
textutil.set_ssh_config(conf_file, "ChallengeResponseAuthentication", option)
- textutil.set_ssh_config(conf_file, "ClientAliveInterval", "180")
+ textutil.set_ssh_config(conf_file, "ClientAliveInterval", str(conf.get_ssh_client_alive_interval()))
fileutil.write_file(conf_file_path, "\n".join(conf_file))
logger.info("{0} SSH password-based authentication methods."
.format("Disabled" if disable_password else "Enabled"))
@@ -965,7 +1028,7 @@ class DefaultOSUtil(object):
if not os.path.exists(hostname_record):
# this file is created at provisioning time with agents >= 2.2.3
hostname = socket.gethostname()
- logger.warn('Hostname record does not exist, '
+ logger.info('Hostname record does not exist, '
'creating [{0}] with hostname [{1}]',
hostname_record,
hostname)
diff --git a/azurelinuxagent/common/osutil/factory.py b/azurelinuxagent/common/osutil/factory.py
index 43aa6a7..1b4e2cb 100644
--- a/azurelinuxagent/common/osutil/factory.py
+++ b/azurelinuxagent/common/osutil/factory.py
@@ -27,7 +27,8 @@ from .freebsd import FreeBSDOSUtil
from .openbsd import OpenBSDOSUtil
from .redhat import RedhatOSUtil, Redhat6xOSUtil
from .suse import SUSEOSUtil, SUSE11OSUtil
-from .ubuntu import UbuntuOSUtil, Ubuntu12OSUtil, Ubuntu14OSUtil, UbuntuSnappyOSUtil
+from .ubuntu import UbuntuOSUtil, Ubuntu12OSUtil, Ubuntu14OSUtil, \
+ UbuntuSnappyOSUtil, Ubuntu16OSUtil
from .alpine import AlpineOSUtil
from .bigip import BigIpOSUtil
from .gaia import GaiaOSUtil
@@ -46,10 +47,12 @@ def get_osutil(distro_name=DISTRO_NAME,
return ClearLinuxUtil()
if distro_name == "ubuntu":
- if Version(distro_version) == Version("12.04") or Version(distro_version) == Version("12.10"):
+ if Version(distro_version) in [Version("12.04"), Version("12.10")]:
return Ubuntu12OSUtil()
- elif Version(distro_version) == Version("14.04") or Version(distro_version) == Version("14.10"):
+ elif Version(distro_version) in [Version("14.04"), Version("14.10")]:
return Ubuntu14OSUtil()
+ elif Version(distro_version) in [Version('16.04'), Version('16.10'), Version('17.04')]:
+ return Ubuntu16OSUtil()
elif distro_full_name == "Snappy Ubuntu Core":
return UbuntuSnappyOSUtil()
else:
diff --git a/azurelinuxagent/common/osutil/ubuntu.py b/azurelinuxagent/common/osutil/ubuntu.py
index 3c353cf..8dacc75 100644
--- a/azurelinuxagent/common/osutil/ubuntu.py
+++ b/azurelinuxagent/common/osutil/ubuntu.py
@@ -16,9 +16,14 @@
# Requires Python 2.4+ and Openssl 1.0+
#
+import time
+
+import azurelinuxagent.common.logger as logger
import azurelinuxagent.common.utils.shellutil as shellutil
+
from azurelinuxagent.common.osutil.default import DefaultOSUtil
+
class Ubuntu14OSUtil(DefaultOSUtil):
def __init__(self):
super(Ubuntu14OSUtil, self).__init__()
@@ -41,6 +46,7 @@ class Ubuntu14OSUtil(DefaultOSUtil):
def get_dhcp_lease_endpoint(self):
return self.get_endpoint_from_leases_path('/var/lib/dhcp/dhclient.*.leases')
+
class Ubuntu12OSUtil(Ubuntu14OSUtil):
def __init__(self):
super(Ubuntu12OSUtil, self).__init__()
@@ -50,9 +56,13 @@ class Ubuntu12OSUtil(Ubuntu14OSUtil):
ret = shellutil.run_get_output("pidof dhclient3", chk_err=False)
return ret[1] if ret[0] == 0 else None
-class UbuntuOSUtil(Ubuntu14OSUtil):
+
+class Ubuntu16OSUtil(Ubuntu14OSUtil):
+ """
+ Ubuntu 16.04, 16.10, and 17.04.
+ """
def __init__(self):
- super(UbuntuOSUtil, self).__init__()
+ super(Ubuntu16OSUtil, self).__init__()
def register_agent_service(self):
return shellutil.run("systemctl unmask walinuxagent", chk_err=False)
@@ -60,6 +70,29 @@ class UbuntuOSUtil(Ubuntu14OSUtil):
def unregister_agent_service(self):
return shellutil.run("systemctl mask walinuxagent", chk_err=False)
+
+class UbuntuOSUtil(Ubuntu16OSUtil):
+ def __init__(self):
+ super(UbuntuOSUtil, self).__init__()
+
+ def restart_if(self, ifname, retries=3, wait=5):
+ """
+ Restart an interface by bouncing the link. systemd-networkd observes
+ this event, and forces a renew of DHCP.
+ """
+ retry_limit=retries+1
+ for attempt in range(1, retry_limit):
+ return_code=shellutil.run("ip link set {0} down && ip link set {0} up".format(ifname))
+ if return_code == 0:
+ return
+ logger.warn("failed to restart {0}: return code {1}".format(ifname, return_code))
+ if attempt < retry_limit:
+ logger.info("retrying in {0} seconds".format(wait))
+ time.sleep(wait)
+ else:
+ logger.warn("exceeded restart retries")
+
+
class UbuntuSnappyOSUtil(Ubuntu14OSUtil):
def __init__(self):
super(UbuntuSnappyOSUtil, self).__init__()
diff --git a/azurelinuxagent/common/protocol/metadata.py b/azurelinuxagent/common/protocol/metadata.py
index 4de7ecf..0cfa5ed 100644
--- a/azurelinuxagent/common/protocol/metadata.py
+++ b/azurelinuxagent/common/protocol/metadata.py
@@ -173,6 +173,11 @@ class MetadataProtocol(Protocol):
return None
return self.certs
+ def get_incarnation(self):
+ # Always return 0 since Azure Stack does not maintain goal state
+ # incarnation identifiers
+ return 0
+
def get_vmagent_manifests(self):
self.update_goal_state()
@@ -235,7 +240,7 @@ class MetadataProtocol(Protocol):
set_properties("extensionHandlers", ext_list.extHandlers, data)
return ext_list, etag
- def get_ext_handler_pkgs(self, ext_handler):
+ def get_ext_handler_pkgs(self, ext_handler, etag):
logger.verbose("Get extension handler packages")
pkg_list = ExtHandlerPackageList()
diff --git a/azurelinuxagent/common/protocol/restapi.py b/azurelinuxagent/common/protocol/restapi.py
index 1ec3e21..5a678d7 100644
--- a/azurelinuxagent/common/protocol/restapi.py
+++ b/azurelinuxagent/common/protocol/restapi.py
@@ -162,6 +162,7 @@ class ExtHandlerProperties(DataContract):
def __init__(self):
self.version = None
self.upgradePolicy = None
+ self.upgradeGuid = None
self.state = None
self.extensions = DataContractList(Extension)
@@ -245,11 +246,13 @@ class ExtHandlerStatus(DataContract):
def __init__(self,
name=None,
version=None,
+ upgradeGuid=None,
status=None,
code=0,
message=None):
self.name = name
self.version = version
+ self.upgradeGuid = upgradeGuid
self.status = status
self.code = code
self.message = message
@@ -300,6 +303,9 @@ class Protocol(DataContract):
def get_certs(self):
raise NotImplementedError()
+ def get_incarnation(self):
+ raise NotImplementedError()
+
def get_vmagent_manifests(self):
raise NotImplementedError()
@@ -309,15 +315,15 @@ class Protocol(DataContract):
def get_ext_handlers(self):
raise NotImplementedError()
- def get_ext_handler_pkgs(self, extension):
+ def get_ext_handler_pkgs(self, extension, etag):
raise NotImplementedError()
def get_artifacts_profile(self):
raise NotImplementedError()
- def download_ext_handler_pkg(self, uri, headers=None):
+ def download_ext_handler_pkg(self, uri, headers=None, use_proxy=True):
try:
- resp = restutil.http_get(uri, use_proxy=True, headers=headers)
+ resp = restutil.http_get(uri, headers=headers, use_proxy=use_proxy)
if restutil.request_succeeded(resp):
return resp.read()
except Exception as e:
diff --git a/azurelinuxagent/common/protocol/util.py b/azurelinuxagent/common/protocol/util.py
index 3071d7a..b3ec373 100644
--- a/azurelinuxagent/common/protocol/util.py
+++ b/azurelinuxagent/common/protocol/util.py
@@ -26,16 +26,18 @@ import threading
import azurelinuxagent.common.conf as conf
import azurelinuxagent.common.logger as logger
+import azurelinuxagent.common.utils.fileutil as fileutil
+
from azurelinuxagent.common.exception import ProtocolError, OSUtilError, \
ProtocolNotFoundError, DhcpError
from azurelinuxagent.common.future import ustr
-import azurelinuxagent.common.utils.fileutil as fileutil
from azurelinuxagent.common.osutil import get_osutil
from azurelinuxagent.common.dhcp import get_dhcp_handler
from azurelinuxagent.common.protocol.ovfenv import OvfEnv
from azurelinuxagent.common.protocol.wire import WireProtocol
from azurelinuxagent.common.protocol.metadata import MetadataProtocol, \
METADATA_ENDPOINT
+from azurelinuxagent.common.utils.restutil import IOErrorCounter
OVF_FILE_NAME = "ovf-env.xml"
TAG_FILE_NAME = "useMetadataEndpoint.tag"
@@ -175,17 +177,20 @@ class ProtocolUtil(object):
self.clear_protocol()
for retry in range(0, MAX_RETRY):
- for protocol in protocols:
+ for protocol_name in protocols:
try:
- if protocol == "WireProtocol":
- return self._detect_wire_protocol()
-
- if protocol == "MetadataProtocol":
- return self._detect_metadata_protocol()
+ protocol = self._detect_wire_protocol() \
+ if protocol_name == "WireProtocol" \
+ else self._detect_metadata_protocol()
+
+ IOErrorCounter.set_protocol_endpoint(
+ endpoint=protocol.endpoint)
+
+ return protocol
except ProtocolError as e:
logger.info("Protocol endpoint not found: {0}, {1}",
- protocol, e)
+ protocol_name, e)
if retry < MAX_RETRY -1:
logger.info("Retry detect protocols: retry={0}", retry)
diff --git a/azurelinuxagent/common/protocol/wire.py b/azurelinuxagent/common/protocol/wire.py
index 4f3b7e0..963d33c 100644
--- a/azurelinuxagent/common/protocol/wire.py
+++ b/azurelinuxagent/common/protocol/wire.py
@@ -18,6 +18,7 @@
import json
import os
+import random
import re
import time
import xml.sax.saxutils as saxutils
@@ -27,7 +28,7 @@ import azurelinuxagent.common.utils.fileutil as fileutil
import azurelinuxagent.common.utils.textutil as textutil
from azurelinuxagent.common.exception import ProtocolNotFoundError, \
- ResourceGoneError
+ ResourceGoneError, RestartError
from azurelinuxagent.common.future import httpclient, bytebuffer
from azurelinuxagent.common.protocol.hostplugin import HostPluginProtocol
from azurelinuxagent.common.protocol.restapi import *
@@ -51,6 +52,7 @@ P7M_FILE_NAME = "Certificates.p7m"
PEM_FILE_NAME = "Certificates.pem"
EXT_CONF_FILE_NAME = "ExtensionsConfig.{0}.xml"
MANIFEST_FILE_NAME = "{0}.{1}.manifest.xml"
+AGENTS_MANIFEST_FILE_NAME = "{0}.{1}.agentsManifest"
TRANSPORT_CERT_FILE_NAME = "TransportCert.pem"
TRANSPORT_PRV_FILE_NAME = "TransportPrivate.pem"
@@ -58,17 +60,12 @@ PROTOCOL_VERSION = "2012-11-30"
ENDPOINT_FINE_NAME = "WireServer"
SHORT_WAITING_INTERVAL = 1 # 1 second
-LONG_WAITING_INTERVAL = 15 # 15 seconds
class UploadError(HttpError):
pass
-class WireProtocolResourceGone(ProtocolError):
- pass
-
-
class WireProtocol(Protocol):
"""Slim layer to adapt wire protocol data to metadata protocol interface"""
@@ -119,6 +116,13 @@ class WireProtocol(Protocol):
certificates = self.client.get_certs()
return certificates.cert_list
+ def get_incarnation(self):
+ path = os.path.join(conf.get_lib_dir(), INCARNATION_FILE_NAME)
+ if os.path.exists(path):
+ return fileutil.read_file(path)
+ else:
+ return 0
+
def get_vmagent_manifests(self):
# Update goal state to get latest extensions config
self.update_goal_state()
@@ -128,8 +132,9 @@ class WireProtocol(Protocol):
def get_vmagent_pkgs(self, vmagent_manifest):
goal_state = self.client.get_goal_state()
- man = self.client.get_gafamily_manifest(vmagent_manifest, goal_state)
- return man.pkg_list
+ ga_manifest = self.client.get_gafamily_manifest(vmagent_manifest, goal_state)
+ valid_pkg_list = self.client.filter_package_list(vmagent_manifest.family, ga_manifest, goal_state)
+ return valid_pkg_list
def get_ext_handlers(self):
logger.verbose("Get extension handler config")
@@ -140,10 +145,9 @@ class WireProtocol(Protocol):
# In wire protocol, incarnation is equivalent to ETag
return ext_conf.ext_handlers, goal_state.incarnation
- def get_ext_handler_pkgs(self, ext_handler):
+ def get_ext_handler_pkgs(self, ext_handler, etag):
logger.verbose("Get extension handler package")
- goal_state = self.client.get_goal_state()
- man = self.client.get_ext_manifest(ext_handler, goal_state)
+ man = self.client.get_ext_manifest(ext_handler, etag)
return man.pkg_list
def get_artifacts_profile(self):
@@ -156,10 +160,10 @@ class WireProtocol(Protocol):
if package is not None:
return package
else:
- logger.warn("Download did not succeed, falling back to host plugin")
+ logger.verbose("Download did not succeed, falling back to host plugin")
host = self.client.get_host_plugin()
uri, headers = host.get_artifact_request(uri, host.manifest_uri)
- package = super(WireProtocol, self).download_ext_handler_pkg(uri, headers=headers)
+ package = super(WireProtocol, self).download_ext_handler_pkg(uri, headers=headers, use_proxy=False)
return package
def report_provision_status(self, provision_status):
@@ -248,22 +252,28 @@ Convert VMStatus object to status blob format
"""
+def ga_status_to_guest_info(ga_status):
+ v1_ga_guest_info = {
+ "computerName" : ga_status.hostname,
+ "osName" : ga_status.osname,
+ "osVersion" : ga_status.osversion,
+ "version" : ga_status.version,
+ }
+ return v1_ga_guest_info
+
+
def ga_status_to_v1(ga_status):
formatted_msg = {
'lang': 'en-US',
'message': ga_status.message
}
v1_ga_status = {
- 'version': ga_status.version,
- 'status': ga_status.status,
- 'osversion': ga_status.osversion,
- 'osname': ga_status.osname,
- 'hostname': ga_status.hostname,
- 'formattedMessage': formatted_msg
+ "version" : ga_status.version,
+ "status" : ga_status.status,
+ "formattedMessage" : formatted_msg
}
return v1_ga_status
-
def ext_substatus_to_v1(sub_status_list):
status_list = []
for substatus in sub_status_list:
@@ -318,6 +328,9 @@ def ext_handler_status_to_v1(handler_status, ext_statuses, timestamp):
"message": handler_status.message
}
+ if handler_status.upgradeGuid is not None:
+ v1_handler_status["upgradeGuid"] = handler_status.upgradeGuid
+
if len(handler_status.extensions) > 0:
# Currently, no more than one extension per handler
ext_name = handler_status.extensions[0]
@@ -334,6 +347,7 @@ def ext_handler_status_to_v1(handler_status, ext_statuses, timestamp):
def vm_status_to_v1(vm_status, ext_statuses):
timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
+ v1_ga_guest_info = ga_status_to_guest_info(vm_status.vmAgent)
v1_ga_status = ga_status_to_v1(vm_status.vmAgent)
v1_handler_status_list = []
for handler_status in vm_status.vmAgent.extensionHandlers:
@@ -349,7 +363,8 @@ def vm_status_to_v1(vm_status, ext_statuses):
v1_vm_status = {
'version': '1.1',
'timestampUTC': timestamp,
- 'aggregateStatus': v1_agg_status
+ 'aggregateStatus': v1_agg_status,
+ 'guestOSInfo' : v1_ga_guest_info
}
return v1_vm_status
@@ -512,51 +527,29 @@ class WireClient(object):
self.shared_conf = None
self.certs = None
self.ext_conf = None
- self.last_request = 0
- self.req_count = 0
self.host_plugin = None
self.status_blob = StatusBlob(self)
- def prevent_throttling(self):
- """
- Try to avoid throttling of wire server
- """
- now = time.time()
- if now - self.last_request < 1:
- logger.verbose("Last request issued less than 1 second ago")
- logger.verbose("Sleep {0} second to avoid throttling.",
- SHORT_WAITING_INTERVAL)
- time.sleep(SHORT_WAITING_INTERVAL)
- self.last_request = now
-
- self.req_count += 1
- if self.req_count % 3 == 0:
- logger.verbose("Sleep {0} second to avoid throttling.",
- SHORT_WAITING_INTERVAL)
- time.sleep(SHORT_WAITING_INTERVAL)
- self.req_count = 0
-
def call_wireserver(self, http_req, *args, **kwargs):
- self.prevent_throttling()
-
try:
# Never use the HTTP proxy for wireserver
kwargs['use_proxy'] = False
resp = http_req(*args, **kwargs)
+
+ if restutil.request_failed(resp):
+ msg = "[Wireserver Failed] URI {0} ".format(args[0])
+ if resp is not None:
+ msg += " [HTTP Failed] Status Code {0}".format(resp.status)
+ raise ProtocolError(msg)
+
+ # If the GoalState is stale, pass along the exception to the caller
+ except ResourceGoneError:
+ raise
+
except Exception as e:
raise ProtocolError("[Wireserver Exception] {0}".format(
ustr(e)))
- if resp is not None and resp.status == httpclient.GONE:
- msg = args[0] if len(args) > 0 else ""
- raise WireProtocolResourceGone(msg)
-
- elif restutil.request_failed(resp):
- msg = "[Wireserver Failed] URI {0} ".format(args[0])
- if resp is not None:
- msg += " [HTTP Failed] Status Code {0}".format(resp.status)
- raise ProtocolError(msg)
-
return resp
def decode_config(self, data):
@@ -598,7 +591,10 @@ class WireClient(object):
def fetch_manifest(self, version_uris):
logger.verbose("Fetch manifest")
- for version in version_uris:
+ version_uris_shuffled = version_uris
+ random.shuffle(version_uris_shuffled)
+
+ for version in version_uris_shuffled:
response = None
if not HostPluginProtocol.is_default_channel():
response = self.fetch(version.uri)
@@ -698,26 +694,28 @@ class WireClient(object):
self.ext_conf = ExtensionsConfig(xml_text)
def update_goal_state(self, forced=False, max_retry=3):
- uri = GOAL_STATE_URI.format(self.endpoint)
- xml_text = self.fetch_config(uri, self.get_header())
- goal_state = GoalState(xml_text)
-
incarnation_file = os.path.join(conf.get_lib_dir(),
INCARNATION_FILE_NAME)
+ uri = GOAL_STATE_URI.format(self.endpoint)
- if not forced:
- last_incarnation = None
- if os.path.isfile(incarnation_file):
- last_incarnation = fileutil.read_file(incarnation_file)
- new_incarnation = goal_state.incarnation
- if last_incarnation is not None and \
- last_incarnation == new_incarnation:
- # Goalstate is not updated.
- return
-
- # Start updating goalstate, retry on 410
+ goal_state = None
for retry in range(0, max_retry):
try:
+ if goal_state is None:
+ xml_text = self.fetch_config(uri, self.get_header())
+ goal_state = GoalState(xml_text)
+
+ if not forced:
+ last_incarnation = None
+ if os.path.isfile(incarnation_file):
+ last_incarnation = fileutil.read_file(
+ incarnation_file)
+ new_incarnation = goal_state.incarnation
+ if last_incarnation is not None and \
+ last_incarnation == new_incarnation:
+ # Goalstate is not updated.
+ return
+
self.goal_state = goal_state
file_name = GOAL_STATE_FILE_NAME.format(goal_state.incarnation)
goal_state_file = os.path.join(conf.get_lib_dir(), file_name)
@@ -727,21 +725,29 @@ class WireClient(object):
self.update_certs(goal_state)
self.update_ext_conf(goal_state)
self.save_cache(incarnation_file, goal_state.incarnation)
+
if self.host_plugin is not None:
self.host_plugin.container_id = goal_state.container_id
self.host_plugin.role_config_name = goal_state.role_config_name
+
return
- except ProtocolError:
+ except ResourceGoneError:
+ logger.info("GoalState is stale -- re-fetching")
+ goal_state = None
+
+ except Exception as e:
+ log_method = logger.info \
+ if type(e) is ProtocolError \
+ else logger.warn
+ log_method(
+ "Exception processing GoalState-related files: {0}".format(
+ ustr(e)))
+
if retry < max_retry-1:
continue
raise
- except WireProtocolResourceGone:
- logger.info("Incarnation is out of date. Update goalstate.")
- xml_text = self.fetch_config(uri, self.get_header())
- goal_state = GoalState(xml_text)
-
raise ProtocolError("Exceeded max retry updating goal state")
def get_goal_state(self):
@@ -793,25 +799,70 @@ class WireClient(object):
self.ext_conf = ExtensionsConfig(xml_text)
return self.ext_conf
- def get_ext_manifest(self, ext_handler, goal_state):
+ def get_ext_manifest(self, ext_handler, incarnation):
+
for update_goal_state in [False, True]:
try:
if update_goal_state:
self.update_goal_state(forced=True)
- goal_state = self.get_goal_state()
+ incarnation = self.get_goal_state().incarnation
local_file = MANIFEST_FILE_NAME.format(
ext_handler.name,
- goal_state.incarnation)
+ incarnation)
local_file = os.path.join(conf.get_lib_dir(), local_file)
- xml_text = self.fetch_manifest(ext_handler.versionUris)
- self.save_cache(local_file, xml_text)
+
+ xml_text = None
+ if not update_goal_state:
+ try:
+ xml_text = self.fetch_cache(local_file)
+ except ProtocolError:
+ pass
+
+ if xml_text is None:
+ xml_text = self.fetch_manifest(ext_handler.versionUris)
+ self.save_cache(local_file, xml_text)
return ExtensionManifest(xml_text)
except ResourceGoneError:
continue
- raise ProtocolError("Failed to retrieve extension manifest")
+ raise RestartError("Failed to retrieve extension manifest")
+
+ def filter_package_list(self, family, ga_manifest, goal_state):
+ complete_list = ga_manifest.pkg_list
+ agent_manifest = os.path.join(conf.get_lib_dir(),
+ AGENTS_MANIFEST_FILE_NAME.format(
+ family,
+ goal_state.incarnation))
+
+ if not os.path.exists(agent_manifest):
+ # clear memory cache
+ ga_manifest.allowed_versions = None
+
+ # create disk cache
+ with open(agent_manifest, mode='w') as manifest_fh:
+ for version in complete_list.versions:
+ manifest_fh.write('{0}\n'.format(version.version))
+ fileutil.chmod(agent_manifest, 0o644)
+
+ return complete_list
+
+ else:
+ # use allowed versions from cache, otherwise from disk
+ if ga_manifest.allowed_versions is None:
+ with open(agent_manifest, mode='r') as manifest_fh:
+ ga_manifest.allowed_versions = [v.strip('\n') for v
+ in manifest_fh.readlines()]
+
+ # use the updated manifest urls for allowed versions
+ allowed_list = ExtHandlerPackageList()
+ allowed_list.versions = [version for version
+ in complete_list.versions
+ if version.version
+ in ga_manifest.allowed_versions]
+
+ return allowed_list
def get_gafamily_manifest(self, vmagent_manifest, goal_state):
for update_goal_state in [False, True]:
@@ -844,7 +895,7 @@ class WireClient(object):
logger.info("Wire protocol version:{0}", PROTOCOL_VERSION)
elif PROTOCOL_VERSION in version_info.get_supported():
logger.info("Wire protocol version:{0}", PROTOCOL_VERSION)
- logger.warn("Server preferred version:{0}", preferred)
+ logger.info("Server preferred version:{0}", preferred)
else:
error = ("Agent supported wire protocol version: {0} was not "
"advised by Fabric.").format(PROTOCOL_VERSION)
@@ -1360,6 +1411,9 @@ class ExtensionsConfig(object):
ext_handler.properties.version = getattrib(plugin, "version")
ext_handler.properties.state = getattrib(plugin, "state")
+ ext_handler.properties.upgradeGuid = getattrib(plugin, "upgradeGuid")
+ if not ext_handler.properties.upgradeGuid:
+ ext_handler.properties.upgradeGuid = None
auto_upgrade = getattrib(plugin, "autoUpgrade")
if auto_upgrade is not None and auto_upgrade.lower() == "true":
ext_handler.properties.upgradePolicy = "auto"
@@ -1418,6 +1472,7 @@ class ExtensionManifest(object):
raise ValueError("ExtensionManifest is None")
logger.verbose("Load ExtensionManifest.xml")
self.pkg_list = ExtHandlerPackageList()
+ self.allowed_versions = None
self.parse(xml_text)
def parse(self, xml_text):
diff --git a/azurelinuxagent/common/utils/cryptutil.py b/azurelinuxagent/common/utils/cryptutil.py
index 6339eb3..b34c1a5 100644
--- a/azurelinuxagent/common/utils/cryptutil.py
+++ b/azurelinuxagent/common/utils/cryptutil.py
@@ -19,8 +19,11 @@
import base64
import struct
+
from azurelinuxagent.common.future import ustr, bytebuffer
from azurelinuxagent.common.exception import CryptError
+
+import azurelinuxagent.common.logger as logger
import azurelinuxagent.common.utils.shellutil as shellutil
class CryptUtil(object):
@@ -34,7 +37,10 @@ class CryptUtil(object):
cmd = ("{0} req -x509 -nodes -subj /CN=LinuxTransport -days 730 "
"-newkey rsa:2048 -keyout {1} "
"-out {2}").format(self.openssl_cmd, prv_file, crt_file)
- shellutil.run(cmd)
+ rc = shellutil.run(cmd)
+ if rc != 0:
+ logger.error("Failed to create {0} and {1} certificates".format(
+ prv_file, crt_file))
def get_pubkey_from_prv(self, file_name):
cmd = "{0} rsa -in {1} -pubout 2>/dev/null".format(self.openssl_cmd,
@@ -61,6 +67,9 @@ class CryptUtil(object):
"").format(self.openssl_cmd, p7m_file, trans_prv_file,
trans_cert_file, self.openssl_cmd, pem_file)
shellutil.run(cmd)
+ rc = shellutil.run(cmd)
+ if rc != 0:
+ logger.error("Failed to decrypt {0}".format(p7m_file))
def crt_to_ssh(self, input_file, output_file):
shellutil.run("ssh-keygen -i -m PKCS8 -f {0} >> {1}".format(input_file,
diff --git a/azurelinuxagent/common/utils/fileutil.py b/azurelinuxagent/common/utils/fileutil.py
index 96b5b82..1f0c7ac 100644
--- a/azurelinuxagent/common/utils/fileutil.py
+++ b/azurelinuxagent/common/utils/fileutil.py
@@ -27,7 +27,6 @@ import os
import pwd
import re
import shutil
-import string
import azurelinuxagent.common.logger as logger
import azurelinuxagent.common.utils.textutil as textutil
@@ -42,9 +41,10 @@ KNOWN_IOERRORS = [
errno.ENOSPC, # Out of space
errno.ENAMETOOLONG, # Name too long
errno.ELOOP, # Too many symbolic links encountered
- errno.EREMOTEIO # Remote I/O error
+ 121 # Remote I/O error (errno.EREMOTEIO -- not present in all Python 2.7+)
]
+
def copy_file(from_path, to_path=None, to_dir=None):
if to_path is None:
to_path = os.path.join(to_dir, os.path.basename(from_path))
@@ -66,11 +66,12 @@ def read_file(filepath, asbin=False, remove_bom=False, encoding='utf-8'):
return data
if remove_bom:
- #Remove bom on bytes data before it is converted into string.
+ # remove bom on bytes data before it is converted into string.
data = textutil.remove_bom(data)
data = ustr(data, encoding=encoding)
return data
+
def write_file(filepath, contents, asbin=False, encoding='utf-8', append=False):
"""
Write 'contents' to 'filepath'.
@@ -82,6 +83,7 @@ def write_file(filepath, contents, asbin=False, encoding='utf-8', append=False):
with open(filepath, mode) as out_file:
out_file.write(data)
+
def append_file(filepath, contents, asbin=False, encoding='utf-8'):
"""
Append 'contents' to 'filepath'.
@@ -93,6 +95,7 @@ def base_name(path):
head, tail = os.path.split(path)
return tail
+
def get_line_startingwith(prefix, filepath):
"""
Return line from 'filepath' if the line startswith 'prefix'
@@ -102,7 +105,6 @@ def get_line_startingwith(prefix, filepath):
return line
return None
-#End File operation util functions
def mkdir(dirpath, mode=None, owner=None):
if not os.path.isdir(dirpath):
@@ -112,6 +114,7 @@ def mkdir(dirpath, mode=None, owner=None):
if owner is not None:
chowner(dirpath, owner)
+
def chowner(path, owner):
if not os.path.exists(path):
logger.error("Path does not exist: {0}".format(path))
@@ -119,19 +122,22 @@ def chowner(path, owner):
owner_info = pwd.getpwnam(owner)
os.chown(path, owner_info[2], owner_info[3])
+
def chmod(path, mode):
if not os.path.exists(path):
logger.error("Path does not exist: {0}".format(path))
else:
os.chmod(path, mode)
+
def rm_files(*args):
for paths in args:
- #Find all possible file paths
+ # find all possible file paths
for path in glob.glob(paths):
if os.path.isfile(path):
os.remove(path)
+
def rm_dirs(*args):
"""
Remove the contents of each directry
@@ -149,20 +155,24 @@ def rm_dirs(*args):
elif os.path.isdir(path):
shutil.rmtree(path)
+
def trim_ext(path, ext):
if not ext.startswith("."):
ext = "." + ext
return path.split(ext)[0] if path.endswith(ext) else path
+
def update_conf_file(path, line_start, val, chk_err=False):
conf = []
if not os.path.isfile(path) and chk_err:
raise IOError("Can't find config file:{0}".format(path))
conf = read_file(path).split('\n')
- conf = [x for x in conf if x is not None and len(x) > 0 and not x.startswith(line_start)]
+ conf = [x for x in conf
+ if x is not None and len(x) > 0 and not x.startswith(line_start)]
conf.append(val)
write_file(path, '\n'.join(conf) + '\n')
+
def search_file(target_dir_name, target_file_name):
for root, dirs, files in os.walk(target_dir_name):
for file_name in files:
@@ -170,24 +180,28 @@ def search_file(target_dir_name, target_file_name):
return os.path.join(root, file_name)
return None
+
def chmod_tree(path, mode):
for root, dirs, files in os.walk(path):
for file_name in files:
os.chmod(os.path.join(root, file_name), mode)
+
def findstr_in_file(file_path, line_str):
"""
Return True if the line is in the file; False otherwise.
- (Trailing whitespace is ignore.)
+ (Trailing whitespace is ignored.)
"""
try:
for line in (open(file_path, 'r')).readlines():
if line_str == line.rstrip():
return True
- except Exception as e:
+ except Exception:
+ # swallow exception
pass
return False
+
def findre_in_file(file_path, line_re):
"""
Return match object if found in file.
@@ -203,6 +217,7 @@ def findre_in_file(file_path, line_re):
return None
+
def get_all_files(root_path):
"""
Find all files under the given root path
@@ -213,6 +228,7 @@ def get_all_files(root_path):
return result
+
def clean_ioerror(e, paths=[]):
"""
Clean-up possibly bad files and directories after an IO error.
@@ -228,5 +244,6 @@ def clean_ioerror(e, paths=[]):
shutil.rmtree(path, ignore_errors=True)
else:
os.remove(path)
- except Exception as e:
+ except Exception:
+ # swallow exception
pass
diff --git a/azurelinuxagent/common/utils/flexible_version.py b/azurelinuxagent/common/utils/flexible_version.py
index 2fce88d..14c2a73 100644
--- a/azurelinuxagent/common/utils/flexible_version.py
+++ b/azurelinuxagent/common/utils/flexible_version.py
@@ -37,7 +37,7 @@ class FlexibleVersion(version.Version):
self.prerelease = None
self.version = ()
if vstring:
- self._parse(vstring)
+ self._parse(str(vstring))
return
_nn_version = 'version'
diff --git a/azurelinuxagent/common/utils/restutil.py b/azurelinuxagent/common/utils/restutil.py
index ddd930b..807be29 100644
--- a/azurelinuxagent/common/utils/restutil.py
+++ b/azurelinuxagent/common/utils/restutil.py
@@ -18,6 +18,7 @@
#
import os
+import threading
import time
import traceback
@@ -32,10 +33,11 @@ from azurelinuxagent.common.version import PY_VERSION_MAJOR
SECURE_WARNING_EMITTED = False
-DEFAULT_RETRIES = 3
+DEFAULT_RETRIES = 6
+DELAY_IN_SECONDS = 1
-SHORT_DELAY_IN_SECONDS = 5
-LONG_DELAY_IN_SECONDS = 15
+THROTTLE_RETRIES = 25
+THROTTLE_DELAY_IN_SECONDS = 1
RETRY_CODES = [
httpclient.RESET_CONTENT,
@@ -63,7 +65,8 @@ OK_CODES = [
THROTTLE_CODES = [
httpclient.FORBIDDEN,
- httpclient.SERVICE_UNAVAILABLE
+ httpclient.SERVICE_UNAVAILABLE,
+ 429, # Request Rate Limit Exceeded
]
RETRY_EXCEPTIONS = [
@@ -76,6 +79,48 @@ RETRY_EXCEPTIONS = [
HTTP_PROXY_ENV = "http_proxy"
HTTPS_PROXY_ENV = "https_proxy"
+DEFAULT_PROTOCOL_ENDPOINT='168.63.129.16'
+HOST_PLUGIN_PORT = 32526
+
+
+class IOErrorCounter(object):
+ _lock = threading.RLock()
+ _protocol_endpoint = DEFAULT_PROTOCOL_ENDPOINT
+ _counts = {"hostplugin":0, "protocol":0, "other":0}
+
+ @staticmethod
+ def increment(host=None, port=None):
+ with IOErrorCounter._lock:
+ if host == IOErrorCounter._protocol_endpoint:
+ if port == HOST_PLUGIN_PORT:
+ IOErrorCounter._counts["hostplugin"] += 1
+ else:
+ IOErrorCounter._counts["protocol"] += 1
+ else:
+ IOErrorCounter._counts["other"] += 1
+
+ @staticmethod
+ def get_and_reset():
+ with IOErrorCounter._lock:
+ counts = IOErrorCounter._counts.copy()
+ IOErrorCounter.reset()
+ return counts
+
+ @staticmethod
+ def reset():
+ with IOErrorCounter._lock:
+ IOErrorCounter._counts = {"hostplugin":0, "protocol":0, "other":0}
+
+ @staticmethod
+ def set_protocol_endpoint(endpoint=DEFAULT_PROTOCOL_ENDPOINT):
+ IOErrorCounter._protocol_endpoint = endpoint
+
+
+def _compute_delay(retry_attempt=1, delay=DELAY_IN_SECONDS):
+ fib = (1, 1)
+ for n in range(retry_attempt):
+ fib = (fib[1], fib[0]+fib[1])
+ return delay*fib[1]
def _is_retry_status(status, retry_codes=RETRY_CODES):
return status in retry_codes
@@ -166,7 +211,7 @@ def http_request(method,
use_proxy=False,
max_retry=DEFAULT_RETRIES,
retry_codes=RETRY_CODES,
- retry_delay=SHORT_DELAY_IN_SECONDS):
+ retry_delay=DELAY_IN_SECONDS):
global SECURE_WARNING_EMITTED
@@ -208,18 +253,31 @@ def http_request(method,
msg = ''
attempt = 0
- delay = retry_delay
+ delay = 0
+ was_throttled = False
while attempt < max_retry:
if attempt > 0:
- logger.info("[HTTP Retry] Attempt {0} of {1}: {2}",
+ # Compute the request delay
+ # -- Use a fixed delay if the server ever rate-throttles the request
+ # (with a safe, minimum number of retry attempts)
+ # -- Otherwise, compute a delay that is the product of the next
+ # item in the Fibonacci series and the initial delay value
+ delay = THROTTLE_DELAY_IN_SECONDS \
+ if was_throttled \
+ else _compute_delay(retry_attempt=attempt,
+ delay=retry_delay)
+
+ logger.verbose("[HTTP Retry] "
+ "Attempt {0} of {1} will delay {2} seconds: {3}",
attempt+1,
max_retry,
+ delay,
msg)
+
time.sleep(delay)
attempt += 1
- delay = retry_delay
try:
resp = _http_request(method,
@@ -235,13 +293,13 @@ def http_request(method,
if request_failed(resp):
if _is_retry_status(resp.status, retry_codes=retry_codes):
- msg = '[HTTP Retry] HTTP {0} Status Code {1}'.format(
- method, resp.status)
+ msg = '[HTTP Retry] {0} {1} -- Status Code {2}'.format(
+ method, url, resp.status)
+ # Note if throttled and ensure a safe, minimum number of
+ # retry attempts
if _is_throttle_status(resp.status):
- delay = LONG_DELAY_IN_SECONDS
- logger.info("[HTTP Delay] Delay {0} seconds for " \
- "Status Code {1}".format(
- delay, resp.status))
+ was_throttled = True
+ max_retry = max(max_retry, THROTTLE_RETRIES)
continue
if resp.status in RESOURCE_GONE_CODES:
@@ -250,22 +308,25 @@ def http_request(method,
return resp
except httpclient.HTTPException as e:
- msg = '[HTTP Failed] HTTP {0} HttpException {1}'.format(method, e)
+ msg = '[HTTP Failed] {0} {1} -- HttpException {2}'.format(
+ method, url, e)
if _is_retry_exception(e):
continue
break
except IOError as e:
- msg = '[HTTP Failed] HTTP {0} IOError {1}'.format(method, e)
+ IOErrorCounter.increment(host=host, port=port)
+ msg = '[HTTP Failed] {0} {1} -- IOError {2}'.format(
+ method, url, e)
continue
- raise HttpError(msg)
+ raise HttpError("{0} -- {1} attempts made".format(msg,attempt))
def http_get(url, headers=None, use_proxy=False,
max_retry=DEFAULT_RETRIES,
retry_codes=RETRY_CODES,
- retry_delay=SHORT_DELAY_IN_SECONDS):
+ retry_delay=DELAY_IN_SECONDS):
return http_request("GET",
url, None, headers=headers,
use_proxy=use_proxy,
@@ -277,7 +338,7 @@ def http_get(url, headers=None, use_proxy=False,
def http_head(url, headers=None, use_proxy=False,
max_retry=DEFAULT_RETRIES,
retry_codes=RETRY_CODES,
- retry_delay=SHORT_DELAY_IN_SECONDS):
+ retry_delay=DELAY_IN_SECONDS):
return http_request("HEAD",
url, None, headers=headers,
use_proxy=use_proxy,
@@ -289,7 +350,7 @@ def http_head(url, headers=None, use_proxy=False,
def http_post(url, data, headers=None, use_proxy=False,
max_retry=DEFAULT_RETRIES,
retry_codes=RETRY_CODES,
- retry_delay=SHORT_DELAY_IN_SECONDS):
+ retry_delay=DELAY_IN_SECONDS):
return http_request("POST",
url, data, headers=headers,
use_proxy=use_proxy,
@@ -301,7 +362,7 @@ def http_post(url, data, headers=None, use_proxy=False,
def http_put(url, data, headers=None, use_proxy=False,
max_retry=DEFAULT_RETRIES,
retry_codes=RETRY_CODES,
- retry_delay=SHORT_DELAY_IN_SECONDS):
+ retry_delay=DELAY_IN_SECONDS):
return http_request("PUT",
url, data, headers=headers,
use_proxy=use_proxy,
@@ -313,7 +374,7 @@ def http_put(url, data, headers=None, use_proxy=False,
def http_delete(url, headers=None, use_proxy=False,
max_retry=DEFAULT_RETRIES,
retry_codes=RETRY_CODES,
- retry_delay=SHORT_DELAY_IN_SECONDS):
+ retry_delay=DELAY_IN_SECONDS):
return http_request("DELETE",
url, None, headers=headers,
use_proxy=use_proxy,
diff --git a/azurelinuxagent/common/utils/shellutil.py b/azurelinuxagent/common/utils/shellutil.py
index fff6aa8..7b8e0c9 100644
--- a/azurelinuxagent/common/utils/shellutil.py
+++ b/azurelinuxagent/common/utils/shellutil.py
@@ -84,16 +84,22 @@ def run_get_output(cmd, chk_err=True, log_cmd=True):
output = ustr(output,
encoding='utf-8',
errors="backslashreplace")
- except subprocess.CalledProcessError as e:
- output = ustr(e.output,
- encoding='utf-8',
- errors="backslashreplace")
- if chk_err:
- if log_cmd:
- logger.error(u"Command: '{0}'", e.cmd)
- logger.error(u"Return code: {0}", e.returncode)
- logger.error(u"Result: {0}", output)
- return e.returncode, output
+ except Exception as e:
+ if type(e) is subprocess.CalledProcessError:
+ output = ustr(e.output,
+ encoding='utf-8',
+ errors="backslashreplace")
+ if chk_err:
+ if log_cmd:
+ logger.error(u"Command: '{0}'", e.cmd)
+ logger.error(u"Return code: {0}", e.returncode)
+ logger.error(u"Result: {0}", output)
+ return e.returncode, output
+ else:
+ logger.error(
+ u"'{0}' raised unexpected exception: '{1}'".format(
+ cmd, ustr(e)))
+ return -1, ustr(e)
return 0, output
diff --git a/azurelinuxagent/common/version.py b/azurelinuxagent/common/version.py
index e1bb828..f743f11 100644
--- a/azurelinuxagent/common/version.py
+++ b/azurelinuxagent/common/version.py
@@ -113,7 +113,7 @@ def get_distro():
AGENT_NAME = "WALinuxAgent"
AGENT_LONG_NAME = "Azure Linux Agent"
-AGENT_VERSION = '2.2.17'
+AGENT_VERSION = '2.2.21'
AGENT_LONG_VERSION = "{0}-{1}".format(AGENT_NAME, AGENT_VERSION)
AGENT_DESCRIPTION = """
The Azure Linux Agent supports the provisioning and running of Linux
@@ -161,14 +161,17 @@ def set_current_agent():
version = AGENT_VERSION
return agent, FlexibleVersion(version)
+
def is_agent_package(path):
path = os.path.basename(path)
return not re.match(AGENT_PKG_PATTERN, path) is None
+
def is_agent_path(path):
path = os.path.basename(path)
return not re.match(AGENT_NAME_PATTERN, path) is None
+
CURRENT_AGENT, CURRENT_VERSION = set_current_agent()
diff --git a/azurelinuxagent/ga/env.py b/azurelinuxagent/ga/env.py
index 45b10bb..d9b7d82 100644
--- a/azurelinuxagent/ga/env.py
+++ b/azurelinuxagent/ga/env.py
@@ -17,11 +17,16 @@
# Requires Python 2.4+ and Openssl 1.0+
#
+import re
import os
import socket
import time
import threading
+import operator
+
+import datetime
+
import azurelinuxagent.common.conf as conf
import azurelinuxagent.common.logger as logger
@@ -29,15 +34,29 @@ from azurelinuxagent.common.dhcp import get_dhcp_handler
from azurelinuxagent.common.event import add_periodic, WALAEventOperation
from azurelinuxagent.common.osutil import get_osutil
from azurelinuxagent.common.protocol import get_protocol_util
+from azurelinuxagent.common.protocol.wire import INCARNATION_FILE_NAME
+from azurelinuxagent.common.utils import fileutil
from azurelinuxagent.common.version import AGENT_NAME, CURRENT_VERSION
+CACHE_PATTERNS = [
+ re.compile("^(.*)\.(\d+)\.(agentsManifest)$", re.IGNORECASE),
+ re.compile("^(.*)\.(\d+)\.(manifest\.xml)$", re.IGNORECASE),
+ re.compile("^(.*)\.(\d+)\.(xml)$", re.IGNORECASE)
+]
+
+MAXIMUM_CACHED_FILES = 50
+
+CACHE_PURGE_INTERVAL = datetime.timedelta(hours=24)
+
+
def get_env_handler():
return EnvHandler()
+
class EnvHandler(object):
"""
Monitor changes to dhcp and hostname.
- If dhcp clinet process re-start has occurred, reset routes, dhcp with fabric.
+ If dhcp client process re-start has occurred, reset routes, dhcp with fabric.
Monitor scsi disk.
If new scsi disk found, set timeout
@@ -48,9 +67,10 @@ class EnvHandler(object):
self.protocol_util = get_protocol_util()
self.stopped = True
self.hostname = None
- self.dhcpid = None
+ self.dhcp_id = None
self.server_thread = None
self.dhcp_warning_enabled = True
+ self.last_purge = None
def run(self):
if not self.stopped:
@@ -61,7 +81,7 @@ class EnvHandler(object):
logger.info("Start env monitor service.")
self.dhcp_handler.conf_routes()
self.hostname = self.osutil.get_hostname_record()
- self.dhcpid = self.osutil.get_dhcp_pid()
+ self.dhcp_id = self.osutil.get_dhcp_pid()
self.server_thread = threading.Thread(target=self.monitor)
self.server_thread.setDaemon(True)
self.server_thread.start()
@@ -70,26 +90,24 @@ class EnvHandler(object):
"""
Monitor firewall rules
Monitor dhcp client pid and hostname.
- If dhcp clinet process re-start has occurred, reset routes.
+ If dhcp client process re-start has occurred, reset routes.
+ Purge unnecessary files from disk cache.
"""
protocol = self.protocol_util.get_protocol()
while not self.stopped:
self.osutil.remove_rules_files()
- # Disable setting firewall for now, regardless of configuration switch
- # if conf.enable_firewall():
- # success = self.osutil.enable_firewall(
- # dst_ip=protocol.endpoint,
- # uid=os.getuid())
- # add_periodic(
- # logger.EVERY_HOUR,
- # AGENT_NAME,
- # version=CURRENT_VERSION,
- # op=WALAEventOperation.Firewall,
- # is_success=success,
- # log_event=True)
-
- self.osutil.remove_firewall()
+ if conf.enable_firewall():
+ success = self.osutil.enable_firewall(
+ dst_ip=protocol.endpoint,
+ uid=os.getuid())
+ add_periodic(
+ logger.EVERY_HOUR,
+ AGENT_NAME,
+ version=CURRENT_VERSION,
+ op=WALAEventOperation.Firewall,
+ is_success=success,
+ log_event=False)
timeout = conf.get_root_device_scsi_timeout()
if timeout is not None:
@@ -100,6 +118,8 @@ class EnvHandler(object):
self.handle_dhclient_restart()
+ self.purge_disk_cache()
+
time.sleep(5)
def handle_hostname_update(self):
@@ -113,30 +133,95 @@ class EnvHandler(object):
self.hostname = curr_hostname
def handle_dhclient_restart(self):
- if self.dhcpid is None:
+ if self.dhcp_id is None:
if self.dhcp_warning_enabled:
logger.warn("Dhcp client is not running. ")
- self.dhcpid = self.osutil.get_dhcp_pid()
+ self.dhcp_id = self.osutil.get_dhcp_pid()
# disable subsequent error logging
- self.dhcp_warning_enabled = self.dhcpid is not None
+ self.dhcp_warning_enabled = self.dhcp_id is not None
+ return
+
+ # the dhcp process has not changed since the last check
+ if self.osutil.check_pid_alive(self.dhcp_id.strip()):
return
- #The dhcp process hasn't changed since last check
- if self.osutil.check_pid_alive(self.dhcpid.strip()):
+ new_pid = self.osutil.get_dhcp_pid()
+ if new_pid is not None and new_pid != self.dhcp_id:
+ logger.info("EnvMonitor: Detected dhcp client restart. "
+ "Restoring routing table.")
+ self.dhcp_handler.conf_routes()
+ self.dhcp_id = new_pid
+
+ def purge_disk_cache(self):
+ """
+ Ensure the number of cached files does not exceed a maximum count.
+ Purge only once per interval, and never delete files related to the
+ current incarnation.
+ """
+ if self.last_purge is not None \
+ and datetime.datetime.utcnow() < \
+ self.last_purge + CACHE_PURGE_INTERVAL:
+ return
+
+ current_incarnation = -1
+ self.last_purge = datetime.datetime.utcnow()
+ incarnation_file = os.path.join(conf.get_lib_dir(),
+ INCARNATION_FILE_NAME)
+ if os.path.exists(incarnation_file):
+ last_incarnation = fileutil.read_file(incarnation_file)
+ if last_incarnation is not None:
+ current_incarnation = int(last_incarnation)
+
+ logger.info("Purging disk cache, current incarnation is {0}"
+ .format('not found'
+ if current_incarnation == -1
+ else current_incarnation))
+
+ # Create tuples: (prefix, suffix, incarnation, name, file_modified)
+ files = []
+ for f in os.listdir(conf.get_lib_dir()):
+ full_path = os.path.join(conf.get_lib_dir(), f)
+ for pattern in CACHE_PATTERNS:
+ m = pattern.match(f)
+ if m is not None:
+ prefix = m.group(1)
+ suffix = m.group(3)
+ incarnation = int(m.group(2))
+ file_modified = os.path.getmtime(full_path)
+ t = (prefix, suffix, incarnation, f, file_modified)
+ files.append(t)
+ break
+
+ if len(files) <= 0:
return
- newpid = self.osutil.get_dhcp_pid()
- if newpid is not None and newpid != self.dhcpid:
- logger.info("EnvMonitor: Detected dhcp client restart. "
- "Restoring routing table.")
- self.dhcp_handler.conf_routes()
- self.dhcpid = newpid
+ # Sort by (prefix, suffix, file_modified) in reverse order
+ files = sorted(files, key=operator.itemgetter(0, 1, 4), reverse=True)
+
+ # Remove any files in excess of the maximum allowed
+ # -- Restart then whenever the (prefix, suffix) change
+ count = 0
+ last_match = [None, None]
+ for f in files:
+ if last_match != f[0:2]:
+ last_match = f[0:2]
+ count = 0
+
+ if current_incarnation == f[2]:
+ logger.verbose("Skipping {0}".format(f[3]))
+ continue
+
+ count += 1
+
+ if count > MAXIMUM_CACHED_FILES:
+ full_name = os.path.join(conf.get_lib_dir(), f[3])
+ logger.verbose("Deleting {0}".format(full_name))
+ os.remove(full_name)
def stop(self):
"""
- Stop server comminucation and join the thread to main thread.
+ Stop server communication and join the thread to main thread.
"""
self.stopped = True
if self.server_thread is not None:
self.server_thread.join()
-
diff --git a/azurelinuxagent/ga/exthandlers.py b/azurelinuxagent/ga/exthandlers.py
index f0a3b09..cc1796b 100644
--- a/azurelinuxagent/ga/exthandlers.py
+++ b/azurelinuxagent/ga/exthandlers.py
@@ -17,10 +17,12 @@
# Requires Python 2.4+ and Openssl 1.0+
#
+import datetime
import glob
import json
import os
import os.path
+import random
import re
import shutil
import stat
@@ -31,25 +33,22 @@ import zipfile
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.shellutil as shellutil
import azurelinuxagent.common.version as version
+from azurelinuxagent.common.errorstate import ErrorState, ERROR_STATE_DELTA
-from azurelinuxagent.common.event import add_event, WALAEventOperation
-from azurelinuxagent.common.exception import ExtensionError, ProtocolError, HttpError
+from azurelinuxagent.common.event import add_event, WALAEventOperation, elapsed_milliseconds
+from azurelinuxagent.common.exception import ExtensionError, ProtocolError, RestartError
from azurelinuxagent.common.future import ustr
-from azurelinuxagent.common.version import AGENT_VERSION
from azurelinuxagent.common.protocol.restapi import ExtHandlerStatus, \
ExtensionStatus, \
ExtensionSubStatus, \
- Extension, \
VMStatus, ExtHandler, \
get_properties, \
set_properties
from azurelinuxagent.common.utils.flexible_version import FlexibleVersion
-from azurelinuxagent.common.utils.textutil import Version
from azurelinuxagent.common.protocol import get_protocol_util
-from azurelinuxagent.common.version import AGENT_NAME, CURRENT_AGENT, CURRENT_VERSION
+from azurelinuxagent.common.version import AGENT_NAME, CURRENT_VERSION
+
#HandlerEnvironment.json schema version
HANDLER_ENVIRONMENT_VERSION = 1.0
@@ -64,7 +63,6 @@ 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))
@@ -176,35 +174,73 @@ class ExtHandlersHandler(object):
self.protocol = None
self.ext_handlers = None
self.last_etag = None
+ self.last_upgrade_guids = {}
self.log_report = False
self.log_etag = True
+ self.log_process = False
+
+ self.report_status_error_state = ErrorState()
def run(self):
self.ext_handlers, etag = None, None
try:
self.protocol = self.protocol_util.get_protocol()
self.ext_handlers, etag = self.protocol.get_ext_handlers()
- except ProtocolError as e:
+ except Exception as e:
msg = u"Exception retrieving extension handlers: {0}".format(
ustr(e))
logger.warn(msg)
- add_event(AGENT_NAME, version=CURRENT_VERSION, is_success=False, message=msg)
+ add_event(AGENT_NAME,
+ version=CURRENT_VERSION,
+ op=WALAEventOperation.ExtensionProcessing,
+ is_success=False,
+ message=msg)
return
- 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()
- self.cleanup_outdated_handlers()
+ try:
+ 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()
+ self.cleanup_outdated_handlers()
+ except RestartError:
+ raise
+ except Exception as e:
+ msg = u"Exception processing extension handlers: {0}".format(
+ ustr(e))
+ logger.warn(msg)
+ add_event(AGENT_NAME,
+ version=CURRENT_VERSION,
+ op=WALAEventOperation.ExtensionProcessing,
+ is_success=False,
+ message=msg)
+ return
def run_status(self):
self.report_ext_handlers_status()
return
+ def get_upgrade_guid(self, name):
+ return self.last_upgrade_guids.get(name, (None, False))[0]
+
+ def get_log_upgrade_guid(self, ext_handler):
+ return self.last_upgrade_guids.get(ext_handler.name, (None, False))[1]
+
+ def set_log_upgrade_guid(self, ext_handler, log_val):
+ guid = self.get_upgrade_guid(ext_handler.name)
+ if guid is not None:
+ self.last_upgrade_guids[ext_handler.name] = (guid, log_val)
+
+ def is_new_guid(self, ext_handler):
+ last_guid = self.get_upgrade_guid(ext_handler.name)
+ if last_guid is None:
+ return True
+ return last_guid != ext_handler.properties.upgradeGuid
+
def cleanup_outdated_handlers(self):
handlers = []
pkgs = []
@@ -288,7 +324,29 @@ class ExtHandlersHandler(object):
ext_handler_i = ExtHandlerInstance(ext_handler, self.protocol)
try:
- ext_handler_i.decide_version()
+ state = ext_handler.properties.state
+ # The extension is to be enabled, there is an upgrade GUID
+ # and the GUID is NOT new
+ if state == u"enabled" and \
+ ext_handler.properties.upgradeGuid is not None and \
+ not self.is_new_guid(ext_handler):
+ ext_handler_i.ext_handler.properties.version = ext_handler_i.get_installed_version()
+ ext_handler_i.set_logger()
+ if self.last_etag != etag:
+ self.set_log_upgrade_guid(ext_handler, True)
+
+ msg = "New GUID is the same as the old GUID. Exiting without upgrading."
+ if self.get_log_upgrade_guid(ext_handler):
+ ext_handler_i.logger.info(msg)
+ self.set_log_upgrade_guid(ext_handler, False)
+ ext_handler_i.set_handler_state(ExtHandlerState.Enabled)
+ ext_handler_i.set_handler_status(status="Ready", message="No change")
+ ext_handler_i.set_operation(WALAEventOperation.SkipUpdate)
+ ext_handler_i.report_event(message=ustr(msg), is_success=True)
+ return
+
+ self.set_log_upgrade_guid(ext_handler, True)
+ ext_handler_i.decide_version(etag=etag, target_state=state)
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}",
@@ -299,22 +357,34 @@ class ExtHandlersHandler(object):
self.log_etag = True
- state = ext_handler.properties.state
ext_handler_i.logger.info("Target handler state: {0}", state)
if state == u"enabled":
self.handle_enable(ext_handler_i)
+ if ext_handler.properties.upgradeGuid is not None:
+ ext_handler_i.logger.info("New Upgrade GUID: {0}", ext_handler.properties.upgradeGuid)
+ self.last_upgrade_guids[ext_handler.name] = (ext_handler.properties.upgradeGuid, True)
elif state == u"disabled":
self.handle_disable(ext_handler_i)
+ # Remove the GUID from the dictionary so that it is upgraded upon re-enable
+ self.last_upgrade_guids.pop(ext_handler.name, None)
elif state == u"uninstall":
self.handle_uninstall(ext_handler_i)
+ # Remove the GUID from the dictionary so that it is upgraded upon re-install
+ self.last_upgrade_guids.pop(ext_handler.name, None)
else:
message = u"Unknown ext handler state:{0}".format(state)
raise ExtensionError(message)
- except ExtensionError as e:
+ except RestartError:
+ ext_handler_i.logger.info("GoalState became stale during "
+ "processing. Restarting with new "
+ "GoalState")
+ raise
+ except Exception as e:
ext_handler_i.set_handler_status(message=ustr(e), code=-1)
ext_handler_i.report_event(message=ustr(e), is_success=False)
def handle_enable(self, ext_handler_i):
+ self.log_process = True
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):
@@ -341,6 +411,7 @@ class ExtHandlersHandler(object):
ext_handler_i.enable()
def handle_disable(self, ext_handler_i):
+ self.log_process = True
handler_state = ext_handler_i.get_handler_state()
ext_handler_i.logger.info("[Disable] current handler state is: {0}",
handler_state.lower())
@@ -348,6 +419,7 @@ class ExtHandlersHandler(object):
ext_handler_i.disable()
def handle_uninstall(self, ext_handler_i):
+ self.log_process = True
handler_state = ext_handler_i.get_handler_state()
ext_handler_i.logger.info("[Uninstall] current handler state is: {0}",
handler_state.lower())
@@ -356,7 +428,7 @@ class ExtHandlersHandler(object):
ext_handler_i.disable()
ext_handler_i.uninstall()
ext_handler_i.rm_ext_handler_dir()
-
+
def report_ext_handlers_status(self):
"""Go through handler_state dir, collect and report status"""
vm_status = VMStatus(status="Ready", message="Guest Agent is running")
@@ -368,6 +440,7 @@ class ExtHandlersHandler(object):
add_event(
AGENT_NAME,
version=CURRENT_VERSION,
+ op=WALAEventOperation.ExtensionProcessing,
is_success=False,
message=ustr(e))
@@ -376,9 +449,27 @@ class ExtHandlersHandler(object):
self.protocol.report_vm_status(vm_status)
if self.log_report:
logger.verbose("Completed vm agent status report")
+ self.report_status_error_state.reset()
except ProtocolError as e:
+ self.report_status_error_state.incr()
message = "Failed to report vm agent status: {0}".format(e)
- add_event(AGENT_NAME, version=CURRENT_VERSION, is_success=False, message=message)
+ add_event(AGENT_NAME,
+ version=CURRENT_VERSION,
+ op=WALAEventOperation.ExtensionProcessing,
+ is_success=False,
+ message=message)
+
+ if self.report_status_error_state.is_triggered():
+ message = "Failed to report vm agent status for more than {0}"\
+ .format(ERROR_STATE_DELTA)
+
+ add_event(AGENT_NAME,
+ version=CURRENT_VERSION,
+ op=WALAEventOperation.ExtensionProcessing,
+ is_success=False,
+ message=message)
+
+ self.report_status_error_state.reset()
def report_ext_handler_status(self, vm_status, ext_handler):
ext_handler_i = ExtHandlerInstance(ext_handler, self.protocol)
@@ -386,6 +477,9 @@ class ExtHandlersHandler(object):
handler_status = ext_handler_i.get_handler_status()
if handler_status is None:
return
+ guid = self.get_upgrade_guid(ext_handler.name)
+ if guid is not None:
+ handler_status.upgradeGuid = guid
handler_state = ext_handler_i.get_handler_state()
if handler_state != ExtHandlerState.NotInstalled:
@@ -413,9 +507,7 @@ class ExtHandlerInstance(object):
self.pkg = None
self.pkg_file = None
self.is_upgrade = False
-
- prefix = "[{0}]".format(self.get_full_name())
- self.logger = logger.Logger(logger.DEFAULT_LOGGER, prefix)
+ self.set_logger()
try:
fileutil.mkdir(self.get_log_dir(), mode=0o755)
@@ -426,15 +518,13 @@ class ExtHandlerInstance(object):
self.logger.add_appender(logger.AppenderType.FILE,
logger.LogLevel.INFO, log_file)
- def decide_version(self):
+ def decide_version(self, etag, target_state=None):
self.logger.verbose("Decide which version to use")
- try:
- pkg_list = self.protocol.get_ext_handler_pkgs(self.ext_handler)
- except ProtocolError as e:
- raise ExtensionError("Failed to get ext handler pkgs", e)
+ pkg_list = self.protocol.get_ext_handler_pkgs(self.ext_handler, etag)
# Determine the desired and installed versions
- requested_version = FlexibleVersion(self.ext_handler.properties.version)
+ requested_version = FlexibleVersion(
+ str(self.ext_handler.properties.version))
installed_version_string = self.get_installed_version()
installed_version = requested_version \
if installed_version_string is None \
@@ -511,13 +601,22 @@ class ExtHandlerInstance(object):
# Note:
# - A downgrade, which will be bound to the same major version,
# is allowed if the installed version is no longer available
- if selected_pkg is None \
+ if target_state == u"uninstall":
+ if installed_pkg is None:
+ msg = "Failed to find installed version of {0} " \
+ "to uninstall".format(self.ext_handler.name)
+ self.logger.warn(msg)
+ self.pkg = installed_pkg
+ self.ext_handler.properties.version = str(installed_version) \
+ if installed_version is not None else None
+ elif selected_pkg is None \
or (installed_pkg is not None and selected_version < installed_version):
self.pkg = installed_pkg
- self.ext_handler.properties.version = installed_version
+ self.ext_handler.properties.version = str(installed_version) \
+ if installed_version is not None else None
else:
self.pkg = selected_pkg
- self.ext_handler.properties.version = selected_pkg.version
+ self.ext_handler.properties.version = str(selected_pkg.version)
# Note if the selected package is greater than that installed
if installed_pkg is None \
@@ -528,12 +627,17 @@ class ExtHandlerInstance(object):
raise ExtensionError("Failed to find any valid extension package")
self.logger.verbose("Use version: {0}", self.pkg.version)
+ self.set_logger()
return
+ def set_logger(self):
+ prefix = "[{0}]".format(self.get_full_name())
+ self.logger = logger.Logger(logger.DEFAULT_LOGGER, prefix)
+
def version_gt(self, other):
self_version = self.ext_handler.properties.version
other_version = other.ext_handler.properties.version
- return Version(self_version) > Version(other_version)
+ return FlexibleVersion(self_version) > FlexibleVersion(other_version)
def get_installed_ext_handler(self):
lastest_version = self.get_installed_version()
@@ -585,23 +689,26 @@ class ExtHandlerInstance(object):
status_file = os.path.join(old_ext_status_dir, status_file)
if os.path.isfile(status_file):
shutil.copy2(status_file, new_ext_status_dir)
-
+
def set_operation(self, op):
self.operation = op
- def report_event(self, message="", is_success=True):
+ def report_event(self, message="", is_success=True, duration=0):
version = self.ext_handler.properties.version
add_event(name=self.ext_handler.name, version=version, message=message,
- op=self.operation, is_success=is_success)
+ op=self.operation, is_success=is_success, duration=duration)
def download(self):
+ begin_utc = datetime.datetime.utcnow()
self.logger.verbose("Download extension package")
self.set_operation(WALAEventOperation.Download)
if self.pkg is None:
raise ExtensionError("No package uri found")
package = None
- for uri in self.pkg.uris:
+ uris_shuffled = self.pkg.uris
+ random.shuffle(uris_shuffled)
+ for uri in uris_shuffled:
try:
package = self.protocol.download_ext_handler_pkg(uri.uri)
if package is not None:
@@ -627,7 +734,8 @@ class ExtHandlerInstance(object):
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")
+ duration = elapsed_milliseconds(begin_utc)
+ self.report_event(message="Download succeeded", duration=duration)
self.logger.info("Initialize extension directory")
#Save HandlerManifest.json
@@ -649,8 +757,25 @@ class ExtHandlerInstance(object):
try:
status_dir = self.get_status_dir()
fileutil.mkdir(status_dir, mode=0o700)
+
+ seq_no, status_path = self.get_status_file_path()
+ if seq_no > -1:
+ now = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
+ status = {
+ "version": 1.0,
+ "timestampUTC" : now,
+ "status" : {
+ "name" : self.ext_handler.name,
+ "operation" : "Enabling Handler",
+ "status" : "transitioning",
+ "code" : 0
+ }
+ }
+ fileutil.write_file(json.dumps(status), status_path)
+
conf_dir = self.get_conf_dir()
fileutil.mkdir(conf_dir, mode=0o700)
+
except IOError as e:
fileutil.clean_ioerror(e,
paths=[self.get_base_dir(), self.pkg_file])
@@ -740,17 +865,24 @@ class ExtHandlerInstance(object):
continue
return seq_no
+ def get_status_file_path(self):
+ seq_no = self.get_largest_seq_no()
+ path = None
+
+ if seq_no > -1:
+ path = os.path.join(
+ self.get_status_dir(),
+ "{0}.status".format(seq_no))
+
+ return seq_no, path
+
def collect_ext_status(self, ext):
self.logger.verbose("Collect extension status")
- seq_no = self.get_largest_seq_no()
+ seq_no, ext_status_file = self.get_status_file_path()
if seq_no == -1:
return None
- status_dir = self.get_status_dir()
- ext_status_file = "{0}.status".format(seq_no)
- ext_status_file = os.path.join(status_dir, ext_status_file)
-
ext_status = ExtensionStatus(seq_no=seq_no)
try:
data_str = fileutil.read_file(ext_status_file)
@@ -810,6 +942,7 @@ class ExtHandlerInstance(object):
return last_update <= 600 # updated within the last 10 min
def launch_command(self, cmd, timeout=300):
+ begin_utc = datetime.datetime.utcnow()
self.logger.verbose("Launch command: [{0}]", cmd)
base_dir = self.get_base_dir()
try:
@@ -835,7 +968,8 @@ class ExtHandlerInstance(object):
if ret == None or ret != 0:
raise ExtensionError("Non-zero exit code: {0}, {1}".format(ret, cmd))
- self.report_event(message="Launch command succeeded: {0}".format(cmd))
+ duration = elapsed_milliseconds(begin_utc)
+ self.report_event(message="Launch command succeeded: {0}".format(cmd), duration=duration)
def load_manifest(self):
man_file = self.get_manifest_file()
@@ -929,7 +1063,7 @@ class ExtHandlerInstance(object):
handler_status = ExtHandlerStatus()
handler_status.name = self.ext_handler.name
- handler_status.version = self.ext_handler.properties.version
+ handler_status.version = str(self.ext_handler.properties.version)
handler_status.message = message
handler_status.code = code
handler_status.status = status
@@ -980,7 +1114,7 @@ class ExtHandlerInstance(object):
def get_log_dir(self):
return os.path.join(conf.get_ext_log_dir(), self.ext_handler.name,
- self.ext_handler.properties.version)
+ str(self.ext_handler.properties.version))
class HandlerEnvironment(object):
def __init__(self, data):
diff --git a/azurelinuxagent/ga/monitor.py b/azurelinuxagent/ga/monitor.py
index 307a514..71ac9b0 100644
--- a/azurelinuxagent/ga/monitor.py
+++ b/azurelinuxagent/ga/monitor.py
@@ -21,8 +21,10 @@ import os
import platform
import time
import threading
+import uuid
import azurelinuxagent.common.conf as conf
+import azurelinuxagent.common.utils.fileutil as fileutil
import azurelinuxagent.common.logger as logger
from azurelinuxagent.common.event import add_event, WALAEventOperation
@@ -34,6 +36,7 @@ from azurelinuxagent.common.protocol.restapi import TelemetryEventParam, \
TelemetryEventList, \
TelemetryEvent, \
set_properties
+from azurelinuxagent.common.utils.restutil import IOErrorCounter
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, \
@@ -179,15 +182,53 @@ class MonitorHandler(object):
def daemon(self):
period = datetime.timedelta(minutes=30)
+ protocol = self.protocol_util.get_protocol()
last_heartbeat = datetime.datetime.utcnow() - period
+
+ # Create a new identifier on each restart and reset the counter
+ heartbeat_id = str(uuid.uuid4()).upper()
+ counter = 0
while True:
if datetime.datetime.utcnow() >= (last_heartbeat + period):
last_heartbeat = datetime.datetime.utcnow()
+ incarnation = protocol.get_incarnation()
+ dropped_packets = self.osutil.get_firewall_dropped_packets(
+ protocol.endpoint)
+
+ msg = "{0};{1};{2};{3}".format(
+ incarnation, counter, heartbeat_id, dropped_packets)
+
add_event(
name=AGENT_NAME,
version=CURRENT_VERSION,
op=WALAEventOperation.HeartBeat,
- is_success=True)
+ is_success=True,
+ message=msg,
+ log_event=False)
+
+ counter += 1
+
+ io_errors = IOErrorCounter.get_and_reset()
+ hostplugin_errors = io_errors.get("hostplugin")
+ protocol_errors = io_errors.get("protocol")
+ other_errors = io_errors.get("other")
+
+ if hostplugin_errors > 0 \
+ or protocol_errors > 0 \
+ or other_errors > 0:
+
+ msg = "hostplugin:{0};protocol:{1};other:{2}"\
+ .format(hostplugin_errors,
+ protocol_errors,
+ other_errors)
+ add_event(
+ name=AGENT_NAME,
+ version=CURRENT_VERSION,
+ op=WALAEventOperation.HttpErrors,
+ is_success=True,
+ message=msg,
+ log_event=False)
+
try:
self.collect_and_send_events()
except Exception as e:
diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py
index b7ee96a..2e43031 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -21,9 +21,11 @@ import glob
import json
import os
import platform
+import random
import re
import shutil
import signal
+import stat
import subprocess
import sys
import time
@@ -43,6 +45,7 @@ from azurelinuxagent.common.event import add_event, add_periodic, \
WALAEventOperation
from azurelinuxagent.common.exception import ProtocolError, \
ResourceGoneError, \
+ RestartError, \
UpdateError
from azurelinuxagent.common.future import ustr
from azurelinuxagent.common.osutil import get_osutil
@@ -60,7 +63,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"
+AGENT_PARTITION_FILE = "partition"
CHILD_HEALTH_INTERVAL = 15 * 60
CHILD_LAUNCH_INTERVAL = 5 * 60
@@ -71,10 +74,18 @@ MAX_FAILURE = 3 # Max failure allowed for agent before blacklisted
GOAL_STATE_INTERVAL = 3
-ORPHAN_WAIT_INTERVAL = 15 * 60 * 60
+ORPHAN_WAIT_INTERVAL = 15 * 60
AGENT_SENTINAL_FILE = "current_version"
+READONLY_FILE_GLOBS = [
+ "*.crt",
+ "*.p7m",
+ "*.pem",
+ "*.prv",
+ "ovf-env.xml"
+]
+
def get_update_handler():
return UpdateHandler()
@@ -101,7 +112,6 @@ class UpdateHandler(object):
self.child_process = None
self.signal_handler = None
- return
def run_latest(self, child_args=None):
"""
@@ -233,36 +243,43 @@ class UpdateHandler(object):
This is the main loop which watches for agent and extension updates.
"""
- logger.info(u"Agent {0} is running as the goal state agent",
- CURRENT_AGENT)
+ try:
+ 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
- get_monitor_handler().run()
+ # Launch monitoring threads
+ from azurelinuxagent.ga.monitor import get_monitor_handler
+ get_monitor_handler().run()
- from azurelinuxagent.ga.env import get_env_handler
- get_env_handler().run()
+ from azurelinuxagent.ga.env import get_env_handler
+ get_env_handler().run()
- from azurelinuxagent.ga.exthandlers import get_exthandlers_handler, migrate_handler_state
- exthandlers_handler = get_exthandlers_handler()
- migrate_handler_state()
+ from azurelinuxagent.ga.exthandlers import get_exthandlers_handler, migrate_handler_state
+ exthandlers_handler = get_exthandlers_handler()
+ migrate_handler_state()
- try:
self._ensure_no_orphans()
self._emit_restart_event()
+ self._ensure_partition_assigned()
+ self._ensure_readonly_files()
while self.running:
if self._is_orphaned:
- logger.info("Goal state agent {0} was orphaned -- exiting",
+ logger.info("Agent {0} is an orphan -- exiting",
CURRENT_AGENT)
break
if self._upgrade_available():
- if len(self.agents) > 0:
+ available_agent = self.get_latest_agent()
+ if available_agent is None:
+ logger.info(
+ "Agent {0} is reverting to the installed agent -- exiting",
+ CURRENT_AGENT)
+ else:
logger.info(
- u"Agent {0} discovered {1} as an update and will exit",
+ u"Agent {0} discovered update {1} -- exiting",
CURRENT_AGENT,
- self.agents[0].name)
+ available_agent.name)
break
utc_start = datetime.utcnow()
@@ -271,20 +288,24 @@ class UpdateHandler(object):
exthandlers_handler.run()
if last_etag != exthandlers_handler.last_etag:
+ self._ensure_readonly_files()
add_event(
AGENT_NAME,
version=CURRENT_VERSION,
op=WALAEventOperation.ProcessGoalState,
is_success=True,
duration=elapsed_milliseconds(utc_start),
+ message="Incarnation {0}".format(
+ exthandlers_handler.last_etag),
log_event=True)
time.sleep(GOAL_STATE_INTERVAL)
except Exception as e:
- logger.warn(u"Agent {0} failed with exception: {1}",
- CURRENT_AGENT,
- ustr(e))
+ msg = u"Agent {0} failed with exception: {1}".format(
+ CURRENT_AGENT, ustr(e))
+ self._set_sentinal(msg=msg)
+ logger.warn(msg)
logger.warn(traceback.format_exc())
sys.exit(1)
# additional return here because sys.exit is mocked in unit tests
@@ -338,17 +359,21 @@ class UpdateHandler(object):
return available_agents[0] if len(available_agents) >= 1 else None
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)
+ try:
+ if not self._is_clean_start:
+ msg = u"Agent did not terminate cleanly: {0}".format(
+ fileutil.read_file(self._sentinal_file_path()))
+ logger.info(msg)
+ add_event(
+ AGENT_NAME,
+ version=CURRENT_VERSION,
+ op=WALAEventOperation.Restart,
+ is_success=False,
+ message=msg)
+ except Exception:
+ pass
- self._set_sentinal()
+ self._set_sentinal(msg="Starting")
return
def _ensure_no_orphans(self, orphan_wait_interval=ORPHAN_WAIT_INTERVAL):
@@ -382,6 +407,26 @@ class UpdateHandler(object):
ustr(e))
return
+ def _ensure_partition_assigned(self):
+ """
+ Assign the VM to a partition (0 - 99). Downloaded updates may be configured
+ to run on only some VMs; the assigned partition determines eligibility.
+ """
+ if not os.path.exists(self._partition_file):
+ partition = ustr(int(datetime.utcnow().microsecond / 10000))
+ fileutil.write_file(self._partition_file, partition)
+ add_event(
+ AGENT_NAME,
+ version=CURRENT_VERSION,
+ op=WALAEventOperation.Partition,
+ is_success=True,
+ message=partition)
+
+ def _ensure_readonly_files(self):
+ for g in READONLY_FILE_GLOBS:
+ for path in glob.iglob(os.path.join(conf.get_lib_dir(), g)):
+ os.chmod(path, stat.S_IRUSR)
+
def _evaluate_agent_health(self, latest_agent):
"""
Evaluate the health of the selected agent: If it is restarting
@@ -412,7 +457,6 @@ class UpdateHandler(object):
def _filter_blacklisted_agents(self):
self.agents = [agent for agent in self.agents if not agent.is_blacklisted]
- return
def _find_agents(self):
"""
@@ -447,19 +491,7 @@ class UpdateHandler(object):
@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
+ return not os.path.isfile(self._sentinal_file_path())
@property
def _is_orphaned(self):
@@ -472,11 +504,29 @@ class UpdateHandler(object):
return fileutil.read_file(conf.get_agent_pid_file_path()) != ustr(parent_pid)
+ def _is_version_eligible(self, version):
+ # Ensure the installed version is always eligible
+ if version == CURRENT_VERSION and is_current_agent_installed():
+ return True
+
+ for agent in self.agents:
+ if agent.version == version:
+ return agent.is_available
+
+ return False
+
def _load_agents(self):
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 _partition(self):
+ return int(fileutil.read_file(self._partition_file))
+
+ @property
+ def _partition_file(self):
+ return os.path.join(conf.get_lib_dir(), AGENT_PARTITION_FILE)
+
def _purge_agents(self):
"""
Remove from disk all directories and .zip files of unknown agents
@@ -485,8 +535,8 @@ class UpdateHandler(object):
path = os.path.join(conf.get_lib_dir(), "{0}-*".format(AGENT_NAME))
known_versions = [agent.version for agent in self.agents]
- if not is_current_agent_installed() and CURRENT_VERSION not in known_versions:
- logger.warn(
+ if CURRENT_VERSION not in known_versions:
+ logger.info(
u"Running Agent {0} was not found in the agent manifest - adding to list",
CURRENT_VERSION)
known_versions.append(CURRENT_VERSION)
@@ -511,9 +561,11 @@ class UpdateHandler(object):
self.agents.sort(key=lambda agent: agent.version, reverse=True)
return
- def _set_sentinal(self, agent=CURRENT_AGENT):
+ def _set_sentinal(self, agent=CURRENT_AGENT, msg="Unknown cause"):
try:
- fileutil.write_file(self._sentinal_file_path(), agent)
+ fileutil.write_file(
+ self._sentinal_file_path(),
+ "[{0}] [{1}]".format(agent, msg))
except Exception as e:
logger.warn(
u"Exception writing sentinal file {0}: {1}",
@@ -599,16 +651,18 @@ class UpdateHandler(object):
self._purge_agents()
self._filter_blacklisted_agents()
- # Return True if more recent agents are available
- return len(self.agents) > 0 and \
- self.agents[0].version > base_version
+ # Return True if current agent is no longer available or an
+ # agent with a higher version number is available
+ return not self._is_version_eligible(base_version) \
+ or (len(self.agents) > 0 \
+ and self.agents[0].version > base_version)
except Exception as e:
if isinstance(e, ResourceGoneError):
continue
msg = u"Exception retrieving agent manifests: {0}".format(
- ustr(e))
+ ustr(traceback.format_exc()))
logger.warn(msg)
add_event(
AGENT_NAME,
@@ -667,8 +721,6 @@ class GuestAgent(object):
self.error = GuestAgentError(self.get_agent_error_file())
self.error.load()
- self.supported = Supported(self.get_agent_supported_file())
- self.supported.load()
try:
self._ensure_downloaded()
@@ -677,6 +729,13 @@ class GuestAgent(object):
if isinstance(e, ResourceGoneError):
raise
+ # The agent was improperly blacklisting versions due to a timeout
+ # encountered while downloading a later version. Errors of type
+ # socket.error are IOError, so this should provide sufficient
+ # protection against a large class of I/O operation failures.
+ if isinstance(e, IOError):
+ raise
+
# Note the failure, blacklist the agent if the package downloaded
# - An exception with a downloaded package indicates the package
# is corrupt (e.g., missing the HandlerManifest.json file)
@@ -691,7 +750,6 @@ class GuestAgent(object):
op=WALAEventOperation.Install,
is_success=False,
message=msg)
- return
@property
def name(self):
@@ -712,13 +770,9 @@ 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()
self.error.save()
- return
@property
def is_available(self):
@@ -730,15 +784,8 @@ class GuestAgent(object):
@property
def is_downloaded(self):
- return self.is_blacklisted or os.path.isfile(self.get_agent_manifest_path())
-
- @property
- def _is_optional(self):
- return self.error is not None and self.error.is_sentinel and self.supported.is_supported
-
- @property
- def _in_slice(self):
- return self.supported.is_supported and self.supported.in_slice
+ return self.is_blacklisted or \
+ os.path.isfile(self.get_agent_manifest_path())
def mark_failure(self, is_fatal=False):
try:
@@ -750,21 +797,6 @@ class GuestAgent(object):
logger.warn(u"Agent {0} is permanently blacklisted", self.name)
except Exception as e:
logger.warn(u"Agent {0} failed recording error state: {1}", self.name, ustr(e))
- return
-
- def _enable(self):
- # Enable optional agents if within the "slice"
- # - The "slice" is a percentage of the agent to execute
- # - Blacklist out-of-slice agents to prevent reconsideration
- if self._is_optional:
- if self._in_slice:
- self.error.clear()
- self.error.save()
- logger.info(u"Enabled optional Agent {0}", self.name)
- else:
- self.mark_failure(is_fatal=True)
- logger.info(u"Optional Agent {0} not in slice", self.name)
- return
def _ensure_downloaded(self):
logger.verbose(u"Ensuring Agent {0} is downloaded", self.name)
@@ -788,18 +820,15 @@ class GuestAgent(object):
op=WALAEventOperation.Install,
is_success=True,
message=msg)
- return
def _ensure_loaded(self):
self._load_manifest()
self._load_error()
- self._load_supported()
-
- self._enable()
- return
def _download(self):
- for uri in self.pkg.uris:
+ uris_shuffled = self.pkg.uris
+ random.shuffle(uris_shuffled)
+ for uri in uris_shuffled:
if not HostPluginProtocol.is_default_channel() and self._fetch(uri.uri):
break
@@ -838,8 +867,6 @@ class GuestAgent(object):
message=msg)
raise UpdateError(msg)
- return
-
def _fetch(self, uri, headers=None, use_proxy=True):
package = None
try:
@@ -871,14 +898,6 @@ class GuestAgent(object):
logger.verbose(u"Agent {0} error state: {1}", self.name, ustr(self.error))
except Exception as e:
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())
- self.supported.load()
- except Exception as e:
- self.supported = Supported()
def _load_manifest(self):
path = self.get_agent_manifest_path()
@@ -976,10 +995,6 @@ class GuestAgentError(object):
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:
@@ -1015,60 +1030,3 @@ 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.distributions = {}
- 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
-
- 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
-
- @property
- def _supported_distribution(self):
- for d in self.distributions:
- dd = self.distributions[d]
- if dd.is_supported:
- return dd
- return None
-
-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
diff --git a/azurelinuxagent/pa/deprovision/default.py b/azurelinuxagent/pa/deprovision/default.py
index e2c5613..895264a 100644
--- a/azurelinuxagent/pa/deprovision/default.py
+++ b/azurelinuxagent/pa/deprovision/default.py
@@ -123,6 +123,7 @@ class DeprovisionHandler(object):
known_files = [
'HostingEnvironmentConfig.xml',
'Incarnation',
+ 'partition',
'Protocol',
'SharedConfig.xml',
'WireServerEndpoint'
diff --git a/azurelinuxagent/pa/provision/cloudinit.py b/azurelinuxagent/pa/provision/cloudinit.py
index 5789e9a..fa47799 100644
--- a/azurelinuxagent/pa/provision/cloudinit.py
+++ b/azurelinuxagent/pa/provision/cloudinit.py
@@ -26,9 +26,9 @@ from datetime import datetime
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.event import elapsed_milliseconds
+from azurelinuxagent.common.event import elapsed_milliseconds, \
+ WALAEventOperation
from azurelinuxagent.common.exception import ProvisionError, ProtocolError
from azurelinuxagent.common.future import ustr
from azurelinuxagent.common.protocol import OVF_FILE_NAME
@@ -64,9 +64,12 @@ class CloudInitProvisionHandler(ProvisionHandler):
logger.info("Finished provisioning")
self.report_ready(thumbprint)
- self.report_event("Provision succeed",
+ self.report_event("Provisioning with cloud-init succeeded",
is_success=True,
duration=elapsed_milliseconds(utc_start))
+ self.report_event(self.create_guest_state_telemetry_messsage(),
+ is_success=True,
+ operation=WALAEventOperation.GuestState)
except ProvisionError as e:
logger.error("Provisioning failed: {0}", ustr(e))
@@ -103,7 +106,7 @@ class CloudInitProvisionHandler(ProvisionHandler):
"after {1}s".format(ovf_file_path,
max_retry * sleep_time))
- def wait_for_ssh_host_key(self, max_retry=360, sleep_time=5):
+ def wait_for_ssh_host_key(self, max_retry=1800, sleep_time=1):
"""
Wait for cloud-init to generate ssh host key
"""
diff --git a/azurelinuxagent/pa/provision/default.py b/azurelinuxagent/pa/provision/default.py
index 2f7ec18..44e171b 100644
--- a/azurelinuxagent/pa/provision/default.py
+++ b/azurelinuxagent/pa/provision/default.py
@@ -22,6 +22,8 @@ Provision handler
import os
import os.path
import re
+import socket
+import time
from datetime import datetime
@@ -87,10 +89,14 @@ class ProvisionHandler(object):
self.write_provisioned()
- self.report_event("Provision succeed",
+ self.report_event("Provisioning succeeded",
is_success=True,
duration=elapsed_milliseconds(utc_start))
+ self.report_event(self.create_guest_state_telemetry_messsage(),
+ is_success=True,
+ operation=WALAEventOperation.GuestState)
+
self.report_ready(thumbprint)
logger.info("Provisioning complete")
@@ -244,9 +250,14 @@ class ProvisionHandler(object):
fileutil.write_file(customdata_file, customdata)
if conf.get_execute_customdata():
+ start = time.time()
logger.info("Execute custom data")
os.chmod(customdata_file, 0o700)
shellutil.run(customdata_file)
+ add_event(name=AGENT_NAME,
+ duration=int(time.time() - start),
+ is_success=True,
+ op=WALAEventOperation.CustomData)
def deploy_ssh_pubkeys(self, ovfenv):
for pubkey in ovfenv.ssh_pubkeys:
@@ -258,12 +269,53 @@ class ProvisionHandler(object):
logger.info("Deploy ssh key pairs.")
self.osutil.deploy_ssh_keypair(ovfenv.username, keypair)
- def report_event(self, message, is_success=False, duration=0):
+ def report_event(self, message, is_success=False, duration=0,
+ operation=WALAEventOperation.Provision):
add_event(name=AGENT_NAME,
message=message,
duration=duration,
is_success=is_success,
- op=WALAEventOperation.Provision)
+ op=operation)
+
+ def get_cpu_count(self):
+ try:
+ count = len([x for x in open('/proc/cpuinfo').readlines()
+ if x.startswith("processor")])
+ return count
+ except Exception as e:
+ logger.verbose(u"Failed to determine the CPU count: {0}.", ustr(e))
+ pass
+ return -1
+
+ def get_mem_size_mb(self):
+ try:
+ for line in open('/proc/meminfo').readlines():
+ m = re.match('^MemTotal:\s*(\d+) kB$', line)
+ if m is not None:
+ return int(int(m.group(1)) / 1024)
+ except Exception as e:
+ logger.verbose(u"Failed to determine the memory size: {0}..", ustr(e))
+ pass
+ return -1
+
+ def create_guest_state_telemetry_messsage(self):
+ """
+ Create a GuestState JSON message that contains the current CPU, Memory
+ (MB), and hostname of the guest.
+
+ e.g.
+
+ {
+ "cpu": 1,
+ "mem": 1024,
+ "hostname": "server1234"
+ }
+ """
+ cpu = self.get_cpu_count()
+ mem = self.get_mem_size_mb()
+
+ return """{{"cpu": {0}, "mem": {1}, "hostname": "{2}"}}"""\
+ .format(cpu, mem, socket.gethostname())
def report_not_ready(self, sub_status, description):
status = ProvisionStatus(status="NotReady", subStatus=sub_status,
diff --git a/azurelinuxagent/pa/provision/factory.py b/azurelinuxagent/pa/provision/factory.py
index d87765f..9e88618 100644
--- a/azurelinuxagent/pa/provision/factory.py
+++ b/azurelinuxagent/pa/provision/factory.py
@@ -16,9 +16,7 @@
#
import azurelinuxagent.common.conf as conf
-import azurelinuxagent.common.logger as logger
-from azurelinuxagent.common.utils.textutil import Version
from azurelinuxagent.common.version import DISTRO_NAME, DISTRO_VERSION, \
DISTRO_FULL_NAME
diff --git a/azurelinuxagent/pa/rdma/suse.py b/azurelinuxagent/pa/rdma/suse.py
index 20f06cd..2b6ae29 100644
--- a/azurelinuxagent/pa/rdma/suse.py
+++ b/azurelinuxagent/pa/rdma/suse.py
@@ -1,6 +1,6 @@
# Microsoft Azure Linux Agent
#
-# Copyright 2014 Microsoft Corporation
+# Copyright 2017 Microsoft Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
#
import glob
-import os
import azurelinuxagent.common.logger as logger
import azurelinuxagent.common.utils.shellutil as shellutil
from azurelinuxagent.common.rdma import RDMAHandler
@@ -37,8 +36,10 @@ class SUSERDMAHandler(RDMAHandler):
return
zypper_install = 'zypper -n in %s'
zypper_install_noref = 'zypper -n --no-refresh in %s'
+ zypper_lock = 'zypper addlock %s'
zypper_remove = 'zypper -n rm %s'
zypper_search = 'zypper -n se -s %s'
+ zypper_unlock = 'zypper removelock %s'
package_name = 'msft-rdma-kmp-default'
cmd = zypper_search % package_name
status, repo_package_info = shellutil.run_get_output(cmd)
@@ -54,13 +55,19 @@ class SUSERDMAHandler(RDMAHandler):
installed = sections[0].strip()
version = sections[3].strip()
driver_package_versions.append(version)
- if fw_version in version and installed == 'i':
+ if fw_version in version and installed.startswith('i'):
info_msg = 'RDMA: Matching driver package "%s-%s" '
info_msg += 'is already installed, nothing to do.'
logger.info(info_msg % (package_name, version))
return True
- if installed == 'i':
+ if installed.startswith('i'):
+ # A driver with a different version is installed
driver_package_installed = True
+ cmd = zypper_unlock % package_name
+ result = shellutil.run(cmd)
+ info_msg = 'Driver with different version installed '
+ info_msg += 'unlocked package "%s".'
+ logger.info(info_msg % (package_name))
# If we get here the driver package is installed but the
# version doesn't match or no package is installed
@@ -80,11 +87,11 @@ class SUSERDMAHandler(RDMAHandler):
logger.info("RDMA: looking for fw version %s in packages" % fw_version)
for entry in driver_package_versions:
- if not fw_version in version:
+ if fw_version not in entry:
logger.info("Package '%s' is not a match." % entry)
else:
logger.info("Package '%s' is a match. Installing." % entry)
- complete_name = '%s-%s' % (package_name, version)
+ complete_name = '%s-%s' % (package_name, entry)
cmd = zypper_install % complete_name
result = shellutil.run(cmd)
if result:
@@ -94,6 +101,11 @@ class SUSERDMAHandler(RDMAHandler):
msg = 'RDMA: Successfully installed "%s" from '
msg += 'configured repositories'
logger.info(msg % complete_name)
+ # Lock the package so it does not accidentally get updated
+ cmd = zypper_lock % package_name
+ result = shellutil.run(cmd)
+ info_msg = 'Applied lock to "%s"' % package_name
+ logger.info(info_msg)
if not self.load_driver_module() or requires_reboot:
self.reboot_system()
return True
@@ -119,6 +131,11 @@ class SUSERDMAHandler(RDMAHandler):
msg = 'RDMA: Successfully installed "%s" from '
msg += 'local package cache'
logger.info(msg % (local_package))
+ # Lock the package so it does not accidentally get updated
+ cmd = zypper_lock % package_name
+ result = shellutil.run(cmd)
+ info_msg = 'Applied lock to "%s"' % package_name
+ logger.info(info_msg)
if not self.load_driver_module() or requires_reboot:
self.reboot_system()
return True
diff --git a/config/alpine/waagent.conf b/config/alpine/waagent.conf
index 99495d5..883c81b 100644
--- a/config/alpine/waagent.conf
+++ b/config/alpine/waagent.conf
@@ -14,7 +14,8 @@ Provisioning.DeleteRootPassword=y
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=y
-# Supported values are "rsa", "dsa" and "ecdsa".
+# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
+# The "auto" option is supported on OpenSSH 5.9 (2011) and later.
Provisioning.SshHostKeyPairType=rsa
# Monitor host name changes and publish changes via DHCP requests.
@@ -88,5 +89,5 @@ OS.SshDir=/etc/ssh
# Add firewall rules to protect access to Azure host node services
# Note:
-# - The default is false to protect the state of exising VMs
+# - The default is false to protect the state of existing VMs
OS.EnableFirewall=y
diff --git a/config/arch/waagent.conf b/config/arch/waagent.conf
index 200a458..cf14e0a 100644
--- a/config/arch/waagent.conf
+++ b/config/arch/waagent.conf
@@ -14,7 +14,8 @@ Provisioning.DeleteRootPassword=n
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=y
-# Supported values are "rsa", "dsa" and "ecdsa".
+# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
+# The "auto" option is supported on OpenSSH 5.9 (2011) and later.
Provisioning.SshHostKeyPairType=rsa
# Monitor host name changes and publish changes via DHCP requests.
@@ -114,5 +115,5 @@ OS.SshDir=/etc/ssh
# Add firewall rules to protect access to Azure host node services
# Note:
-# - The default is false to protect the state of exising VMs
+# - The default is false to protect the state of existing VMs
OS.EnableFirewall=y
diff --git a/config/bigip/waagent.conf b/config/bigip/waagent.conf
index 9ff6ee1..1e59a6a 100644
--- a/config/bigip/waagent.conf
+++ b/config/bigip/waagent.conf
@@ -25,7 +25,8 @@ Provisioning.DeleteRootPassword=y
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=y
-# Supported values are "rsa", "dsa" and "ecdsa".
+# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
+# The "auto" option is supported on OpenSSH 5.9 (2011) and later.
Provisioning.SshHostKeyPairType=rsa
# Monitor host name changes and publish changes via DHCP requests.
@@ -92,5 +93,5 @@ AutoUpdate.Enabled=y
# Add firewall rules to protect access to Azure host node services
# Note:
-# - The default is false to protect the state of exising VMs
+# - The default is false to protect the state of existing VMs
OS.EnableFirewall=y
diff --git a/config/clearlinux/waagent.conf b/config/clearlinux/waagent.conf
index 8109425..c63f92b 100644
--- a/config/clearlinux/waagent.conf
+++ b/config/clearlinux/waagent.conf
@@ -25,7 +25,8 @@ Provisioning.DeleteRootPassword=y
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=y
-# Supported values are "rsa", "dsa" and "ecdsa".
+# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
+# The "auto" option is supported on OpenSSH 5.9 (2011) and later.
Provisioning.SshHostKeyPairType=rsa
# Monitor host name changes and publish changes via DHCP requests.
@@ -86,5 +87,5 @@ AutoUpdate.GAFamily=Prod
# Add firewall rules to protect access to Azure host node services
# Note:
-# - The default is false to protect the state of exising VMs
+# - The default is false to protect the state of existing VMs
OS.EnableFirewall=y
diff --git a/config/coreos/waagent.conf b/config/coreos/waagent.conf
index cbb327f..ee4ebf2 100644
--- a/config/coreos/waagent.conf
+++ b/config/coreos/waagent.conf
@@ -14,7 +14,8 @@ Provisioning.DeleteRootPassword=n
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=n
-# Supported values are "rsa", "dsa" and "ecdsa".
+# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
+# The "auto" option is supported on OpenSSH 5.9 (2011) and later.
Provisioning.SshHostKeyPairType=ed25519
# Monitor host name changes and publish changes via DHCP requests.
@@ -114,5 +115,5 @@ OS.AllowHTTP=y
# Add firewall rules to protect access to Azure host node services
# Note:
-# - The default is false to protect the state of exising VMs
+# - The default is false to protect the state of existing VMs
OS.EnableFirewall=y
diff --git a/config/freebsd/waagent.conf b/config/freebsd/waagent.conf
index 6406c75..74ad843 100644
--- a/config/freebsd/waagent.conf
+++ b/config/freebsd/waagent.conf
@@ -14,7 +14,8 @@ Provisioning.DeleteRootPassword=y
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=y
-# Supported values are "rsa", "dsa" and "ecdsa".
+# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
+# The "auto" option is supported on OpenSSH 5.9 (2011) and later.
Provisioning.SshHostKeyPairType=rsa
# Monitor host name changes and publish changes via DHCP requests.
@@ -112,5 +113,5 @@ OS.SudoersDir=/usr/local/etc/sudoers.d
# Add firewall rules to protect access to Azure host node services
# Note:
-# - The default is false to protect the state of exising VMs
+# - The default is false to protect the state of existing VMs
OS.EnableFirewall=y
diff --git a/config/gaia/waagent.conf b/config/gaia/waagent.conf
index 9c28ba3..0fd78a8 100644
--- a/config/gaia/waagent.conf
+++ b/config/gaia/waagent.conf
@@ -14,7 +14,8 @@ Provisioning.DeleteRootPassword=n
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=n
-# Supported values are "rsa", "dsa" and "ecdsa".
+# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
+# The "auto" option is supported on OpenSSH 5.9 (2011) and later.
Provisioning.SshHostKeyPairType=rsa
# Monitor host name changes and publish changes via DHCP requests.
@@ -111,5 +112,5 @@ AutoUpdate.Enabled=n
# Add firewall rules to protect access to Azure host node services
# Note:
-# - The default is false to protect the state of exising VMs
+# - The default is false to protect the state of existing VMs
OS.EnableFirewall=y
diff --git a/config/openbsd/waagent.conf b/config/openbsd/waagent.conf
index a39a9a5..580b4a3 100644
--- a/config/openbsd/waagent.conf
+++ b/config/openbsd/waagent.conf
@@ -15,6 +15,7 @@ Provisioning.DeleteRootPassword=y
Provisioning.RegenerateSshHostKeyPair=y
# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
+# The "auto" option is supported on OpenSSH 5.9 (2011) and later.
Provisioning.SshHostKeyPairType=auto
# Monitor host name changes and publish changes via DHCP requests.
@@ -110,5 +111,5 @@ OS.PasswordPath=/etc/master.passwd
# Add firewall rules to protect access to Azure host node services
# Note:
-# - The default is false to protect the state of exising VMs
+# - The default is false to protect the state of existing VMs
OS.EnableFirewall=y
diff --git a/config/suse/waagent.conf b/config/suse/waagent.conf
index ba50be6..e83caf3 100644
--- a/config/suse/waagent.conf
+++ b/config/suse/waagent.conf
@@ -14,7 +14,8 @@ Provisioning.DeleteRootPassword=y
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=y
-# Supported values are "rsa", "dsa" and "ecdsa".
+# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
+# The "auto" option is supported on OpenSSH 5.9 (2011) and later.
Provisioning.SshHostKeyPairType=rsa
# Monitor host name changes and publish changes via DHCP requests.
@@ -114,5 +115,5 @@ OS.SshDir=/etc/ssh
# Add firewall rules to protect access to Azure host node services
# Note:
-# - The default is false to protect the state of exising VMs
+# - The default is false to protect the state of existing VMs
OS.EnableFirewall=y
diff --git a/config/ubuntu/waagent.conf b/config/ubuntu/waagent.conf
index 71f2c04..4fa2d12 100644
--- a/config/ubuntu/waagent.conf
+++ b/config/ubuntu/waagent.conf
@@ -14,7 +14,8 @@ Provisioning.DeleteRootPassword=y
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=n
-# Supported values are "rsa", "dsa" and "ecdsa".
+# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
+# The "auto" option is supported on OpenSSH 5.9 (2011) and later.
Provisioning.SshHostKeyPairType=rsa
# Monitor host name changes and publish changes via DHCP requests.
@@ -102,5 +103,5 @@ OS.SshDir=/etc/ssh
# Add firewall rules to protect access to Azure host node services
# Note:
-# - The default is false to protect the state of exising VMs
+# - The default is false to protect the state of existing VMs
OS.EnableFirewall=y
diff --git a/config/waagent.conf b/config/waagent.conf
index f465e8f..5772645 100644
--- a/config/waagent.conf
+++ b/config/waagent.conf
@@ -15,6 +15,7 @@ Provisioning.DeleteRootPassword=n
Provisioning.RegenerateSshHostKeyPair=n
# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
+# The "auto" option is supported on OpenSSH 5.9 (2011) and later.
Provisioning.SshHostKeyPairType=rsa
# Monitor host name changes and publish changes via DHCP requests.
@@ -66,6 +67,9 @@ OS.RootDeviceScsiTimeout=300
# If "None", the system default version is used.
OS.OpensslPath=None
+# Set the SSH ClientAliveInterval
+# OS.SshClientAliveInterval=180
+
# Set the path to SSH keys and configuration files
OS.SshDir=/etc/ssh
@@ -111,5 +115,5 @@ OS.SshDir=/etc/ssh
# Add firewall rules to protect access to Azure host node services
# Note:
-# - The default is false to protect the state of exising VMs
+# - The default is false to protect the state of existing VMs
OS.EnableFirewall=y
diff --git a/debian/changelog b/debian/changelog
index 2d70dc5..c9c0460 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+walinuxagent (2.2.21-0ubuntu1) bionic; urgency=medium
+
+ * New upstream release (LP: #1746628).
+ * debian/patches/disable_import_test.patch: refreshed patch.
+
+ -- Łukasz 'sil2100' Zemczak <lukasz.zemczak@ubuntu.com> Mon, 05 Feb 2018 17:25:14 +0100
+
walinuxagent (2.2.17-0ubuntu1) artful; urgency=medium
* New upstream release (LP: #1717306).
diff --git a/debian/patches/disable_import_test.patch b/debian/patches/disable_import_test.patch
index f51d77e..e6b4bdb 100644
--- a/debian/patches/disable_import_test.patch
+++ b/debian/patches/disable_import_test.patch
@@ -1,5 +1,7 @@
---- a/config/waagent.conf
-+++ b/config/waagent.conf
+Index: walinuxagent-2.2.21/config/waagent.conf
+===================================================================
+--- walinuxagent-2.2.21.orig/config/waagent.conf
++++ walinuxagent-2.2.21/config/waagent.conf
@@ -3,16 +3,16 @@
#
@@ -19,8 +21,8 @@
+Provisioning.RegenerateSshHostKeyPair=n
# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
- Provisioning.SshHostKeyPairType=rsa
-@@ -36,14 +36,14 @@
+ # The "auto" option is supported on OpenSSH 5.9 (2011) and later.
+@@ -37,14 +37,14 @@ Provisioning.ExecuteCustomData=n
Provisioning.AllowResetSysUser=n
# Format if unformatted. If 'n', resource disk will not be mounted.
diff --git a/tests/common/osutil/test_default.py b/tests/common/osutil/test_default.py
index 08125ae..c9fa1de 100644
--- a/tests/common/osutil/test_default.py
+++ b/tests/common/osutil/test_default.py
@@ -489,6 +489,62 @@ Match host 192.168.1.2\n\
print("WRITING TO {0}".format(waagent_sudoers))
self.assertEqual(1, count)
+ def test_get_firewall_dropped_packets_returns_zero_if_firewall_disabled(self):
+ osutil._enable_firewall = False
+ util = osutil.DefaultOSUtil()
+
+ self.assertEqual(0, util.get_firewall_dropped_packets("not used"))
+
+ @patch('azurelinuxagent.common.utils.shellutil.run_get_output')
+ def test_get_firewall_dropped_packets_returns_negative_if_error(self, mock_output):
+ osutil._enable_firewall = True
+ util = osutil.DefaultOSUtil()
+
+ mock_output.side_effect = [
+ (0, "iptables v{0}".format(osutil.IPTABLES_LOCKING_VERSION)),
+ (1, "not used")]
+ self.assertEqual(-1, util.get_firewall_dropped_packets("not used"))
+
+ @patch('azurelinuxagent.common.utils.shellutil.run_get_output')
+ def test_get_firewall_dropped_packets_returns_negative_if_exception(self, mock_output):
+ osutil._enable_firewall = True
+ util = osutil.DefaultOSUtil()
+
+ mock_output.side_effect = [
+ (0, "iptables v{0}".format(osutil.IPTABLES_LOCKING_VERSION)),
+ (1, Exception)]
+ self.assertEqual(-1, util.get_firewall_dropped_packets("not used"))
+
+ @patch('azurelinuxagent.common.utils.shellutil.run_get_output')
+ def test_get_firewall_dropped_packets_transient_error_ignored(self, mock_output):
+ osutil._enable_firewall = True
+ util = osutil.DefaultOSUtil()
+
+ mock_output.side_effect = [
+ (0, "iptables v{0}".format(osutil.IPTABLES_LOCKING_VERSION)),
+ (3, "can't initialize iptables table `security': iptables who? (do you need to insmod?)")]
+ self.assertEqual(0, util.get_firewall_dropped_packets("not used"))
+
+ @patch('azurelinuxagent.common.utils.shellutil.run_get_output')
+ def test_get_firewall_dropped_packets(self, mock_output):
+ osutil._enable_firewall = True
+ util = osutil.DefaultOSUtil()
+
+ mock_output.side_effect = [
+ (0, "iptables v{0}".format(osutil.IPTABLES_LOCKING_VERSION)),
+ (0,
+'''
+
+Chain OUTPUT (policy ACCEPT 104 packets, 43628 bytes)
+ pkts bytes target prot opt in out source destination
+ 0 0 ACCEPT tcp -- any any anywhere 168.63.129.16 owner UID match daemon
+ 32 1920 DROP tcp -- any any anywhere 168.63.129.16
+
+''')]
+ dst = '168.63.129.16'
+
+ self.assertEqual(32, util.get_firewall_dropped_packets(dst))
+
@patch('os.getuid', return_value=42)
@patch('azurelinuxagent.common.utils.shellutil.run_get_output')
@patch('azurelinuxagent.common.utils.shellutil.run')
@@ -592,6 +648,34 @@ Match host 192.168.1.2\n\
])
self.assertFalse(osutil._enable_firewall)
+ @patch('azurelinuxagent.common.utils.shellutil.run_get_output')
+ @patch('azurelinuxagent.common.utils.shellutil.run')
+ def test_enable_firewall_checks_for_invalid_iptables_options(self, mock_run, mock_output):
+ osutil._enable_firewall = True
+ util = osutil.DefaultOSUtil()
+
+ dst = '1.2.3.4'
+ version = "iptables v{0}".format(osutil.IPTABLES_LOCKING_VERSION)
+ wait = "-w"
+
+ # iptables uses the following exit codes
+ # 0 - correct function
+ # 1 - other errors
+ # 2 - errors which appear to be caused by invalid or abused command
+ # line parameters
+ mock_run.side_effect = [2]
+ mock_output.return_value = (0, version)
+
+ self.assertFalse(util.enable_firewall(dst_ip='1.2.3.4', uid=42))
+ self.assertFalse(osutil._enable_firewall)
+
+ mock_run.assert_has_calls([
+ call(osutil.FIREWALL_DROP.format(wait, "C", dst), chk_err=False),
+ ])
+ mock_output.assert_has_calls([
+ call(osutil.IPTABLES_VERSION)
+ ])
+
@patch('os.getuid', return_value=42)
@patch('azurelinuxagent.common.utils.shellutil.run_get_output')
@patch('azurelinuxagent.common.utils.shellutil.run')
@@ -624,17 +708,54 @@ Match host 192.168.1.2\n\
version = "iptables v{0}".format(osutil.IPTABLES_LOCKING_VERSION)
wait = "-w"
- mock_run.side_effect = [0, 0]
+ mock_run.side_effect = [0, 1, 0, 1]
mock_output.side_effect = [(0, version), (0, "Output")]
- self.assertTrue(util.remove_firewall())
+ self.assertTrue(util.remove_firewall(dst, uid))
mock_run.assert_has_calls([
- call(osutil.FIREWALL_FLUSH.format(wait), chk_err=False)
+ call(osutil.FIREWALL_DELETE_CONNTRACK.format(wait, dst), chk_err=False),
+ call(osutil.FIREWALL_DELETE_CONNTRACK.format(wait, dst), chk_err=False),
+ call(osutil.FIREWALL_DELETE_OWNER.format(wait, dst, uid), chk_err=False),
+ call(osutil.FIREWALL_DELETE_OWNER.format(wait, dst, uid), chk_err=False),
])
mock_output.assert_has_calls([
call(osutil.IPTABLES_VERSION)
])
self.assertTrue(osutil._enable_firewall)
+ @patch('os.getuid', return_value=42)
+ @patch('azurelinuxagent.common.utils.shellutil.run_get_output')
+ @patch('azurelinuxagent.common.utils.shellutil.run')
+ def test_remove_firewall_does_not_repeat(self, mock_run, mock_output, _):
+ osutil._enable_firewall = True
+ util = osutil.DefaultOSUtil()
+
+ dst_ip='1.2.3.4'
+ uid=42
+ version = "iptables v{0}".format(osutil.IPTABLES_LOCKING_VERSION)
+ wait = "-w"
+
+ mock_run.side_effect = [2]
+ mock_output.side_effect = [(0, version), (1, "Output")]
+ self.assertFalse(util.remove_firewall(dst_ip, uid))
+
+ mock_run.assert_has_calls([
+ call(osutil.FIREWALL_DELETE_CONNTRACK.format(wait, dst_ip), chk_err=False),
+ ])
+ mock_output.assert_has_calls([
+ call(osutil.IPTABLES_VERSION)
+ ])
+ self.assertFalse(osutil._enable_firewall)
+
+ self.assertTrue(mock_run.call_count == 1)
+ self.assertTrue(mock_output.call_count == 1)
+
+ self.assertFalse(util.remove_firewall())
+ self.assertFalse(util.remove_firewall())
+
+ self.assertTrue(mock_run.call_count == 1)
+ self.assertTrue(mock_output.call_count == 1)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/common/test_conf.py b/tests/common/test_conf.py
index 93759de..057c83b 100644
--- a/tests/common/test_conf.py
+++ b/tests/common/test_conf.py
@@ -48,6 +48,7 @@ class TestConf(AgentTestCase):
"OS.EnableFIPS" : True,
"OS.RootDeviceScsiTimeout" : '300',
"OS.OpensslPath" : '/usr/bin/openssl',
+ "OS.SshClientAliveInterval" : 42,
"OS.SshDir" : "/notareal/path",
"HttpProxy.Host" : None,
"HttpProxy.Port" : None,
@@ -64,7 +65,7 @@ class TestConf(AgentTestCase):
"AutoUpdate.GAFamily" : "Prod",
"EnableOverProvisioning" : False,
"OS.AllowHTTP" : False,
- "OS.EnableFirewall" : True
+ "OS.EnableFirewall" : False
}
def setUp(self):
@@ -77,6 +78,7 @@ class TestConf(AgentTestCase):
def test_key_value_handling(self):
self.assertEqual("Value1", self.conf.get("FauxKey1", "Bad"))
self.assertEqual("Value2 Value2", self.conf.get("FauxKey2", "Bad"))
+ self.assertEqual("delalloc,rw,noatime,nobarrier,users,mode=777", self.conf.get("FauxKey3", "Bad"))
def test_get_ssh_dir(self):
self.assertTrue(get_ssh_dir(self.conf).startswith("/notareal/path"))
@@ -109,4 +111,5 @@ class TestConf(AgentTestCase):
for k in TestConf.EXPECTED_CONFIGURATION.keys():
self.assertEqual(
TestConf.EXPECTED_CONFIGURATION[k],
- configuration[k])
+ configuration[k],
+ k)
diff --git a/tests/common/test_errorstate.py b/tests/common/test_errorstate.py
new file mode 100644
index 0000000..7513fe5
--- /dev/null
+++ b/tests/common/test_errorstate.py
@@ -0,0 +1,69 @@
+from datetime import timedelta
+
+from azurelinuxagent.common.errorstate import *
+from tests.tools import *
+
+
+class TestErrorState(unittest.TestCase):
+ def test_errorstate00(self):
+ """
+ If ErrorState is never incremented, it will never trigger.
+ """
+ test_subject = ErrorState(timedelta(seconds=10000))
+ self.assertFalse(test_subject.is_triggered())
+ self.assertEqual(0, test_subject.count)
+
+ def test_errorstate01(self):
+ """
+ If ErrorState is never incremented, and the timedelta is zero it will
+ not trigger.
+ """
+ test_subject = ErrorState(timedelta(seconds=0))
+ self.assertFalse(test_subject.is_triggered())
+ self.assertEqual(0, test_subject.count)
+
+ def test_errorstate02(self):
+ """
+ If ErrorState is triggered, and the current time is within timedelta
+ of now it will trigger.
+ """
+ test_subject = ErrorState(timedelta(seconds=0))
+ test_subject.incr()
+
+
+ self.assertTrue(test_subject.is_triggered())
+ self.assertEqual(1, test_subject.count)
+
+ @patch('azurelinuxagent.common.errorstate.datetime')
+ def test_errorstate03(self, mock_time):
+ """
+ ErrorState will not trigger until
+ 1. ErrorState has been incr() at least once.
+ 2. The timedelta from the first incr() has elapsed.
+ """
+ test_subject = ErrorState(timedelta(minutes=15))
+
+ for x in range(1, 10):
+ mock_time.utcnow = Mock(return_value=datetime.utcnow() + timedelta(minutes=x))
+
+ test_subject.incr()
+ self.assertFalse(test_subject.is_triggered())
+
+ mock_time.utcnow = Mock(return_value=datetime.utcnow() + timedelta(minutes=30))
+ test_subject.incr()
+ self.assertTrue(test_subject.is_triggered())
+
+ def test_errorstate04(self):
+ """
+ If ErrorState is reset the timestamp of the last incr() is reset to
+ None.
+ """
+
+ test_subject = ErrorState(timedelta(minutes=15))
+ self.assertTrue(test_subject.timestamp is None)
+
+ test_subject.incr()
+ self.assertTrue(test_subject.timestamp is not None)
+
+ test_subject.reset()
+ self.assertTrue(test_subject.timestamp is None)
diff --git a/tests/common/test_event.py b/tests/common/test_event.py
index 55a99c4..01bcd7b 100644
--- a/tests/common/test_event.py
+++ b/tests/common/test_event.py
@@ -23,7 +23,8 @@ import azurelinuxagent.common.event as event
import azurelinuxagent.common.logger as logger
from azurelinuxagent.common.event import add_event, \
- mark_event_status, should_emit_event
+ mark_event_status, should_emit_event, \
+ WALAEventOperation
from azurelinuxagent.common.future import ustr
from azurelinuxagent.common.version import CURRENT_VERSION
@@ -48,8 +49,7 @@ class TestEvent(AgentTestCase):
self.assertTrue(es.event_succeeded("Foo", "1.2", "FauxOperation"))
def test_event_status_records_status(self):
- d = tempfile.mkdtemp()
- es = event.EventStatus(tempfile.mkdtemp())
+ es = event.EventStatus()
es.mark_event_status("Foo", "1.2", "FauxOperation", True)
self.assertTrue(es.event_succeeded("Foo", "1.2", "FauxOperation"))
@@ -69,7 +69,7 @@ class TestEvent(AgentTestCase):
self.assertFalse(es.event_succeeded("Foo", "1.2", "FauxOperation"))
def test_should_emit_event_ignores_unknown_operations(self):
- event.__event_status__ = event.EventStatus(tempfile.mkdtemp())
+ event.__event_status__ = event.EventStatus()
self.assertTrue(event.should_emit_event("Foo", "1.2", "FauxOperation", True))
self.assertTrue(event.should_emit_event("Foo", "1.2", "FauxOperation", False))
@@ -82,7 +82,7 @@ class TestEvent(AgentTestCase):
def test_should_emit_event_handles_known_operations(self):
- event.__event_status__ = event.EventStatus(tempfile.mkdtemp())
+ event.__event_status__ = event.EventStatus()
# Known operations always initially "fire"
for op in event.__event_status_operations__:
@@ -144,7 +144,7 @@ class TestEvent(AgentTestCase):
event.add_periodic(logger.EVERY_DAY, "FauxEvent")
self.assertEqual(1, mock_event.call_count)
- h = hash("FauxEvent"+""+ustr(True)+"")
+ h = hash("FauxEvent"+WALAEventOperation.Unknown+ustr(True))
event.__event_logger__.periodic_events[h] = \
datetime.now() - logger.EVERY_DAY - logger.EVERY_HOUR
event.add_periodic(logger.EVERY_DAY, "FauxEvent")
@@ -158,7 +158,8 @@ class TestEvent(AgentTestCase):
mock_event.assert_called_once_with(
"FauxEvent",
duration=0, evt_type='', is_internal=False, is_success=True,
- log_event=True, message='', op='', version=str(CURRENT_VERSION))
+ log_event=True, message='', op=WALAEventOperation.Unknown,
+ version=str(CURRENT_VERSION))
def test_save_event(self):
add_event('test', message='test event')
diff --git a/tests/common/test_logger.py b/tests/common/test_logger.py
index 9e298b3..005c429 100644
--- a/tests/common/test_logger.py
+++ b/tests/common/test_logger.py
@@ -15,17 +15,20 @@
# Requires Python 2.4+ and Openssl 1.0+
#
+import json
from datetime import datetime
import azurelinuxagent.common.logger as logger
+from azurelinuxagent.common.event import add_log_event
+from azurelinuxagent.common.version import CURRENT_AGENT, CURRENT_VERSION
from tests.tools import *
_MSG = "This is our test logging message {0} {1}"
_DATA = ["arg1", "arg2"]
-class TestLogger(AgentTestCase):
+class TestLogger(AgentTestCase):
@patch('azurelinuxagent.common.logger.Logger.info')
def test_periodic_emits_if_not_previously_sent(self, mock_info):
logger.reset_periodic()
@@ -64,3 +67,38 @@ class TestLogger(AgentTestCase):
logger.periodic(logger.EVERY_DAY, _MSG, *_DATA)
mock_info.assert_called_once_with(_MSG, *_DATA)
+
+ def test_telemetry_logger(self):
+ mock = MagicMock()
+ appender = logger.TelemetryAppender(logger.LogLevel.WARNING, mock)
+ appender.write(logger.LogLevel.WARNING, "--unit-test--")
+
+ mock.assert_called_once_with(logger.LogLevel.WARNING, "--unit-test--")
+
+ @patch('azurelinuxagent.common.event.EventLogger.save_event')
+ def test_telemetry_logger1(self, mock_save):
+ appender = logger.TelemetryAppender(logger.LogLevel.WARNING, add_log_event)
+ appender.write(logger.LogLevel.WARNING, "--unit-test--")
+
+ self.assertEqual(1, mock_save.call_count)
+ telemetry_json = json.loads(mock_save.call_args[0][0])
+
+ self.assertEqual('FFF0196F-EE4C-4EAF-9AA5-776F622DEB4F', telemetry_json['providerId'])
+ self.assertEqual(7, telemetry_json['eventId'])
+
+ self.assertEqual(5, len(telemetry_json['parameters']))
+ for x in telemetry_json['parameters']:
+ if x['name'] == 'EventName':
+ self.assertEqual(x['value'], 'Log')
+
+ elif x['name'] == 'CapabilityUsed':
+ self.assertEqual(x['value'], 'WARNING')
+
+ elif x['name'] == 'Context1':
+ self.assertEqual(x['value'], '--unit-test--')
+
+ elif x['name'] == 'Context2':
+ self.assertEqual(x['value'], '')
+
+ elif x['name'] == 'Context3':
+ self.assertEqual(x['value'], '')
diff --git a/tests/data/ext/sample_ext-1.2.0.zip b/tests/data/ext/sample_ext-1.3.0.zip
index 08cfaf7..08cfaf7 100644
--- a/tests/data/ext/sample_ext-1.2.0.zip
+++ b/tests/data/ext/sample_ext-1.3.0.zip
Binary files differ
diff --git a/tests/data/ext/sample_ext-1.2.0/HandlerManifest.json b/tests/data/ext/sample_ext-1.3.0/HandlerManifest.json
index 9890d0c..9890d0c 100644
--- a/tests/data/ext/sample_ext-1.2.0/HandlerManifest.json
+++ b/tests/data/ext/sample_ext-1.3.0/HandlerManifest.json
diff --git a/tests/data/ext/sample_ext-1.2.0/sample.py b/tests/data/ext/sample_ext-1.3.0/sample.py
index 74bd839..74bd839 100755
--- a/tests/data/ext/sample_ext-1.2.0/sample.py
+++ b/tests/data/ext/sample_ext-1.3.0/sample.py
diff --git a/tests/data/ga/WALinuxAgent-2.2.14.zip b/tests/data/ga/WALinuxAgent-2.2.14.zip
deleted file mode 100644
index a978207..0000000
--- a/tests/data/ga/WALinuxAgent-2.2.14.zip
+++ /dev/null
Binary files differ
diff --git a/tests/data/ga/WALinuxAgent-2.2.19.zip b/tests/data/ga/WALinuxAgent-2.2.19.zip
new file mode 100644
index 0000000..dcb8a5a
--- /dev/null
+++ b/tests/data/ga/WALinuxAgent-2.2.19.zip
Binary files differ
diff --git a/tests/data/ga/supported.json b/tests/data/ga/supported.json
deleted file mode 100644
index 2ae3753..0000000
--- a/tests/data/ga/supported.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "ubuntu.16.10-x64": {
- "versions": [
- "^Ubuntu,16.10,yakkety$"
- ],
- "slice": 10
- }
-}
diff --git a/tests/data/safe_deploy.json b/tests/data/safe_deploy.json
new file mode 100644
index 0000000..9d0e3eb
--- /dev/null
+++ b/tests/data/safe_deploy.json
@@ -0,0 +1,23 @@
+{
+ "blacklisted" : [
+ "^1.2.3$",
+ "^1.3(?:\\.\\d+)*$"
+ ],
+
+ "families" : {
+ "ubuntu-x64": {
+ "versions": [
+ "^Ubuntu,(1[4-9]|2[0-9])\\.\\d+,.*$"
+ ],
+ "require_64bit": true,
+ "partition": 85
+ },
+ "fedora-x64": {
+ "versions": [
+ "^Oracle[^,]*,([7-9]|[1-9][0-9])\\.\\d+,.*$",
+ "^Red\\sHat[^,]*,([7-9]|[1-9][0-9])\\.\\d+,.*$"
+ ],
+ "partition": 20
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/data/test_waagent.conf b/tests/data/test_waagent.conf
index edc3676..be0596a 100644
--- a/tests/data/test_waagent.conf
+++ b/tests/data/test_waagent.conf
@@ -6,6 +6,7 @@
=Value0
FauxKey1= Value1
FauxKey2=Value2 Value2
+FauxKey3=delalloc,rw,noatime,nobarrier,users,mode=777
# Enable instance creation
Provisioning.Enabled=y
@@ -19,14 +20,15 @@ Provisioning.DeleteRootPassword=y
# Generate fresh host key pair.
Provisioning.RegenerateSshHostKeyPair=y
-# Supported values are "rsa", "dsa" and "ecdsa".
-Provisioning.SshHostKeyPairType=rsa
+# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
+# The "auto" option is supported on OpenSSH 5.9 (2011) and later.
+Provisioning.SshHostKeyPairType=rsa # An EOL comment that should be ignored
# Monitor host name changes and publish changes via DHCP requests.
Provisioning.MonitorHostName=y
# Decode CustomData from Base64.
-Provisioning.DecodeCustomData=n
+Provisioning.DecodeCustomData=n#Another EOL comment that should be ignored
# Execute CustomData after provisioning.
Provisioning.ExecuteCustomData=n
@@ -63,7 +65,7 @@ ResourceDisk.MountOptions=None
Logs.Verbose=n
# Is FIPS enabled
-OS.EnableFIPS=y
+OS.EnableFIPS=y#Another EOL comment that should be ignored
# Root device timeout in seconds.
OS.RootDeviceScsiTimeout=300
@@ -71,6 +73,9 @@ OS.RootDeviceScsiTimeout=300
# If "None", the system default version is used.
OS.OpensslPath=None
+# Set the SSH ClientAliveInterval
+OS.SshClientAliveInterval=42#Yet another EOL comment with a '#' that should be ignored
+
# Set the path to SSH keys and configuration files
OS.SshDir=/notareal/path
@@ -119,5 +124,5 @@ OS.SshDir=/notareal/path
# Add firewall rules to protect access to Azure host node services
# Note:
-# - The default is false to protect the state of exising VMs
-OS.EnableFirewall=y
+# - The default is false to protect the state of existing VMs
+OS.EnableFirewall=n
diff --git a/tests/data/wire/ext_conf_autoupgrade_internalversion.xml b/tests/data/wire/ext_conf_autoupgrade_internalversion.xml
index 1e613ea..9b61556 100644
--- a/tests/data/wire/ext_conf_autoupgrade_internalversion.xml
+++ b/tests/data/wire/ext_conf_autoupgrade_internalversion.xml
@@ -17,10 +17,10 @@
</GAFamilies>
</GuestAgentExtension>
<Plugins>
- <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.2.0" location="http://rdfepirv2hknprdstr03.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" config="" state="enabled" autoUpgrade="true" failoverlocation="http://rdfepirv2hknprdstr04.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" runAsStartupTask="false" isJson="true" />
+ <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.3.0" location="http://rdfepirv2hknprdstr03.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" config="" state="enabled" autoUpgrade="true" failoverlocation="http://rdfepirv2hknprdstr04.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" runAsStartupTask="false" isJson="true" />
</Plugins>
<PluginSettings>
- <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.2.0">
+ <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.3.0">
<RuntimeSettings seqNo="0">{"runtimeSettings":[{"handlerSettings":{"protectedSettingsCertThumbprint":"4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3","protectedSettings":"MIICWgYJK","publicSettings":{"foo":"bar"}}}]}</RuntimeSettings>
</Plugin>
</PluginSettings>
diff --git a/tests/data/wire/ext_conf_internalversion.xml b/tests/data/wire/ext_conf_internalversion.xml
index 1e613ea..9b61556 100644
--- a/tests/data/wire/ext_conf_internalversion.xml
+++ b/tests/data/wire/ext_conf_internalversion.xml
@@ -17,10 +17,10 @@
</GAFamilies>
</GuestAgentExtension>
<Plugins>
- <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.2.0" location="http://rdfepirv2hknprdstr03.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" config="" state="enabled" autoUpgrade="true" failoverlocation="http://rdfepirv2hknprdstr04.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" runAsStartupTask="false" isJson="true" />
+ <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.3.0" location="http://rdfepirv2hknprdstr03.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" config="" state="enabled" autoUpgrade="true" failoverlocation="http://rdfepirv2hknprdstr04.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" runAsStartupTask="false" isJson="true" />
</Plugins>
<PluginSettings>
- <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.2.0">
+ <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.3.0">
<RuntimeSettings seqNo="0">{"runtimeSettings":[{"handlerSettings":{"protectedSettingsCertThumbprint":"4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3","protectedSettings":"MIICWgYJK","publicSettings":{"foo":"bar"}}}]}</RuntimeSettings>
</Plugin>
</PluginSettings>
diff --git a/tests/data/wire/ext_conf_upgradeguid.xml b/tests/data/wire/ext_conf_upgradeguid.xml
new file mode 100644
index 0000000..1526a93
--- /dev/null
+++ b/tests/data/wire/ext_conf_upgradeguid.xml
@@ -0,0 +1,26 @@
+<Extensions version="1.0.0.0" goalStateIncarnation="9"><GuestAgentExtension xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
+ <GAFamilies>
+ <GAFamily>
+ <Name>Prod</Name>
+ <Uris>
+ <Uri>http://manifest_of_ga.xml</Uri>
+ </Uris>
+ </GAFamily>
+ <GAFamily>
+ <Name>Test</Name>
+ <Uris>
+ <Uri>http://manifest_of_ga.xml</Uri>
+ </Uris>
+ </GAFamily>
+ </GAFamilies>
+</GuestAgentExtension>
+<Plugins>
+ <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.0.0" location="http://rdfepirv2hknprdstr03.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" config="" state="enabled" autoUpgrade="false" failoverlocation="http://rdfepirv2hknprdstr04.blob.core.windows.net/b01058962be54ceca550a390fa5ff064/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml" runAsStartupTask="false" isJson="true" upgradeGuid="12345678-09AB-ABCD-CDEF-FE0987654321" />
+</Plugins>
+<PluginSettings>
+ <Plugin name="OSTCExtensions.ExampleHandlerLinux" version="1.0.0">
+ <RuntimeSettings seqNo="0">{"runtimeSettings":[{"handlerSettings":{"protectedSettingsCertThumbprint":"4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3","protectedSettings":"MIICWgYJK","publicSettings":{"foo":"bar"}}}]}</RuntimeSettings>
+ </Plugin>
+</PluginSettings>
+<StatusUploadBlob statusBlobType="BlockBlob">https://yuezhatest.blob.core.windows.net/vhds/test-cs12.test-cs12.test-cs12.status?sr=b&amp;sp=rw&amp;se=9999-01-01&amp;sk=key1&amp;sv=2014-02-14&amp;sig=hfRh7gzUE7sUtYwke78IOlZOrTRCYvkec4hGZ9zZzXo%3D</StatusUploadBlob></Extensions>
+
diff --git a/tests/data/wire/ga_manifest.xml b/tests/data/wire/ga_manifest.xml
index f43daf5..fb7238a 100644
--- a/tests/data/wire/ga_manifest.xml
+++ b/tests/data/wire/ga_manifest.xml
@@ -14,6 +14,18 @@
</Uris>
</Plugin>
<Plugin>
+ <Version>1.1.1</Version>
+ <Uris>
+ <Uri>http://foo.bar/zar/OSTCExtensions.WALinuxAgent__1.1.1</Uri>
+ </Uris>
+ </Plugin>
+ <Plugin>
+ <Version>1.2.0</Version>
+ <Uris>
+ <Uri>http://foo.bar/zar/OSTCExtensions.WALinuxAgent__1.2.0</Uri>
+ </Uris>
+ </Plugin>
+ <Plugin>
<Version>2.0.0</Version><Uris><Uri>http://host/OSTCExtensions.WALinuxAgent__2.0.0</Uri></Uris>
</Plugin>
<Plugin>
diff --git a/tests/data/wire/ga_manifest_1.xml b/tests/data/wire/ga_manifest_1.xml
new file mode 100644
index 0000000..f2b1839
--- /dev/null
+++ b/tests/data/wire/ga_manifest_1.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PluginVersionManifest xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
+ <Plugins/>
+ <InternalPlugins>
+ <Plugin>
+ <Version>2.2.13</Version>
+ <Uris>
+ <Uri>url1_13</Uri>
+ </Uris>
+ </Plugin>
+ <Plugin>
+ <Version>2.2.14</Version>
+ <Uris>
+ <Uri>url1_14</Uri>
+ </Uris>
+ </Plugin>
+ </InternalPlugins>
+</PluginVersionManifest>
diff --git a/tests/data/wire/ga_manifest_2.xml b/tests/data/wire/ga_manifest_2.xml
new file mode 100644
index 0000000..7636f8c
--- /dev/null
+++ b/tests/data/wire/ga_manifest_2.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PluginVersionManifest xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
+ <Plugins/>
+ <InternalPlugins>
+ <Plugin>
+ <Version>2.2.13</Version>
+ <Uris>
+ <Uri>url2_13</Uri>
+ </Uris>
+ </Plugin>
+ <Plugin>
+ <Version>2.2.14</Version>
+ <Uris>
+ <Uri>url2_14</Uri>
+ </Uris>
+ </Plugin>
+ <Plugin>
+ <Version>2.2.15</Version>
+ <Uris>
+ <Uri>url1_15</Uri>
+ </Uris>
+ </Plugin>
+ </InternalPlugins>
+</PluginVersionManifest>
diff --git a/tests/data/wire/manifest.xml b/tests/data/wire/manifest.xml
index ff42b9d..eef8ade 100644
--- a/tests/data/wire/manifest.xml
+++ b/tests/data/wire/manifest.xml
@@ -14,6 +14,18 @@
</Uris>
</Plugin>
<Plugin>
+ <Version>1.1.1</Version>
+ <Uris>
+ <Uri>http://foo.bar/zar/OSTCExtensions.ExampleHandlerLinux__1.1.1</Uri>
+ </Uris>
+ </Plugin>
+ <Plugin>
+ <Version>1.2.0</Version>
+ <Uris>
+ <Uri>http://foo.bar/zar/OSTCExtensions.ExampleHandlerLinux__1.2.0</Uri>
+ </Uris>
+ </Plugin>
+ <Plugin>
<Version>2.0.0</Version><Uris><Uri>http://host/OSTCExtensions.ExampleHandlerLinux__2.0.0</Uri></Uris>
</Plugin>
<Plugin>
@@ -44,9 +56,9 @@
</Plugins>
<InternalPlugins>
<Plugin>
- <Version>1.2.0</Version>
+ <Version>1.3.0</Version>
<Uris>
- <Uri>http://foo.bar/zar/OSTCExtensions.ExampleHandlerLinux__1.2.0</Uri>
+ <Uri>http://foo.bar/zar/OSTCExtensions.ExampleHandlerLinux__1.3.0</Uri>
</Uris>
</Plugin>
<Plugin>
diff --git a/tests/ga/test_env.py b/tests/ga/test_env.py
new file mode 100644
index 0000000..06e9a64
--- /dev/null
+++ b/tests/ga/test_env.py
@@ -0,0 +1,86 @@
+# 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 glob
+import tempfile
+
+import os
+from mock import patch
+
+from azurelinuxagent.common.utils import fileutil
+from azurelinuxagent.ga.env import MAXIMUM_CACHED_FILES, EnvHandler
+from tests.tools import AgentTestCase
+
+
+class TestEnv(AgentTestCase):
+ @patch("azurelinuxagent.common.conf.get_lib_dir")
+ def test_purge_disk_cache(self, mock_conf, *args):
+ names = [
+ ("Prod", "agentsManifest"),
+ ("Test", "agentsManifest"),
+ ("FauxExtension1", "manifest.xml"),
+ ("FauxExtension2", "manifest.xml"),
+ ("GoalState", "xml"),
+ ("ExtensionsConfig", "xml")
+ ]
+
+ env = EnvHandler()
+
+ tmp_dir = tempfile.mkdtemp()
+ mock_conf.return_value = tmp_dir
+
+ # write incarnations 1-100
+ for t in names:
+ self._create_files(tmp_dir,
+ t[0],
+ t[1],
+ 2 * MAXIMUM_CACHED_FILES,
+ with_sleep=0.001)
+
+ # update incarnation 1 with the latest timestamp
+ for t in names:
+ f = os.path.join(tmp_dir, '.'.join((t[0], '1', t[1])))
+ fileutil.write_file(f, "faux content")
+
+ # ensure the expected number of files are created
+ for t in names:
+ p = os.path.join(tmp_dir, '{0}.*.{1}'.format(*t))
+ self.assertEqual(2 * MAXIMUM_CACHED_FILES, len(glob.glob(p)))
+
+ env.purge_disk_cache()
+
+ # ensure the expected number of files remain
+ for t in names:
+ p = os.path.join(tmp_dir, '{0}.*.{1}'.format(*t))
+ incarnation1 = os.path.join(tmp_dir, '{0}.1.{1}'.format(t[0], t[1]))
+ incarnation2 = os.path.join(tmp_dir, '{0}.2.{1}'.format(t[0], t[1]))
+ self.assertEqual(MAXIMUM_CACHED_FILES, len(glob.glob(p)))
+ self.assertTrue(os.path.exists(incarnation1))
+ self.assertFalse(os.path.exists(incarnation2))
+
+ # write incarnation 101
+ for t in names:
+ f = os.path.join(tmp_dir, '.'.join((t[0], '101', t[1])))
+ fileutil.write_file(f, "faux content")
+
+ # call to purge should be ignored, since interval has not elapsed
+ env.purge_disk_cache()
+
+ for t in names:
+ p = os.path.join(tmp_dir, '{0}.*.{1}'.format(*t))
+ incarnation1 = os.path.join(tmp_dir, '{0}.1.{1}'.format(t[0], t[1]))
+ self.assertEqual(MAXIMUM_CACHED_FILES + 1, len(glob.glob(p)))
+ self.assertTrue(os.path.exists(incarnation1))
diff --git a/tests/ga/test_extension.py b/tests/ga/test_extension.py
index 2a60ea3..9a72989 100644
--- a/tests/ga/test_extension.py
+++ b/tests/ga/test_extension.py
@@ -46,8 +46,8 @@ class TestExtensionCleanup(AgentTestCase):
def _install_handlers(self, start=0, count=1,
handler_state=ExtHandlerState.Installed):
- src = os.path.join(data_dir, "ext", "sample_ext-1.2.0.zip")
- version = FlexibleVersion("1.2.0")
+ src = os.path.join(data_dir, "ext", "sample_ext-1.3.0.zip")
+ version = FlexibleVersion("1.3.0")
version += start - version.patch
for i in range(start, start+count):
@@ -318,34 +318,44 @@ class TestExtension(AgentTestCase):
self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
self._assert_ext_status(protocol.report_ext_status, "success", 1)
- #Test upgrade
+ #Test hotfix
test_data.goal_state = test_data.goal_state.replace("<Incarnation>2<",
"<Incarnation>3<")
test_data.ext_conf = test_data.ext_conf.replace("1.0.0", "1.1.0")
test_data.ext_conf = test_data.ext_conf.replace("seqNo=\"1\"",
"seqNo=\"2\"")
exthandlers_handler.run()
- self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.1.0")
+ self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.1.1")
self._assert_ext_status(protocol.report_ext_status, "success", 2)
- #Test disable
+ #Test upgrade
test_data.goal_state = test_data.goal_state.replace("<Incarnation>3<",
"<Incarnation>4<")
+ test_data.ext_conf = test_data.ext_conf.replace("1.1.0", "1.2.0")
+ test_data.ext_conf = test_data.ext_conf.replace("seqNo=\"2\"",
+ "seqNo=\"3\"")
+ exthandlers_handler.run()
+ self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.2.0")
+ self._assert_ext_status(protocol.report_ext_status, "success", 3)
+
+ #Test disable
+ test_data.goal_state = test_data.goal_state.replace("<Incarnation>4<",
+ "<Incarnation>5<")
test_data.ext_conf = test_data.ext_conf.replace("enabled", "disabled")
exthandlers_handler.run()
self._assert_handler_status(protocol.report_vm_status, "NotReady",
- 1, "1.1.0")
+ 1, "1.2.0")
#Test uninstall
- test_data.goal_state = test_data.goal_state.replace("<Incarnation>4<",
- "<Incarnation>5<")
+ test_data.goal_state = test_data.goal_state.replace("<Incarnation>5<",
+ "<Incarnation>6<")
test_data.ext_conf = test_data.ext_conf.replace("disabled", "uninstall")
exthandlers_handler.run()
self._assert_no_handler_status(protocol.report_vm_status)
#Test uninstall again!
- test_data.goal_state = test_data.goal_state.replace("<Incarnation>5<",
- "<Incarnation>6<")
+ test_data.goal_state = test_data.goal_state.replace("<Incarnation>6<",
+ "<Incarnation>7<")
exthandlers_handler.run()
self._assert_no_handler_status(protocol.report_vm_status)
@@ -371,6 +381,95 @@ class TestExtension(AgentTestCase):
exthandlers_handler.run()
self._assert_no_handler_status(protocol.report_vm_status)
+ def test_ext_handler_rollingupgrade(self, *args):
+ test_data = WireProtocolData(DATA_FILE_EXT_ROLLINGUPGRADE)
+ exthandlers_handler, protocol = self._create_mock(test_data, *args)
+
+ #Test enable scenario.
+ exthandlers_handler.run()
+ self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
+ self._assert_ext_status(protocol.report_ext_status, "success", 0)
+
+ #Test goal state not changed
+ exthandlers_handler.run()
+ self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
+
+ #Test goal state changed without new GUID
+ test_data.goal_state = test_data.goal_state.replace("<Incarnation>1<",
+ "<Incarnation>2<")
+ exthandlers_handler.run()
+ self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
+ self._assert_ext_status(protocol.report_ext_status, "success", 0)
+
+ #Test GUID change without new version available
+ test_data.goal_state = test_data.goal_state.replace("<Incarnation>2<",
+ "<Incarnation>3<")
+ test_data.ext_conf = test_data.ext_conf.replace("FE0987654321", "FE0987654322")
+ exthandlers_handler.run()
+ self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
+ self._assert_ext_status(protocol.report_ext_status, "success", 0)
+
+ #Test hotfix available without GUID change
+ test_data.goal_state = test_data.goal_state.replace("<Incarnation>3<",
+ "<Incarnation>4<")
+ test_data.ext_conf = test_data.ext_conf.replace("1.0.0", "1.1.0")
+ exthandlers_handler.run()
+ self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.0.0")
+ self._assert_ext_status(protocol.report_ext_status, "success", 0)
+
+ #Test GUID change with hotfix
+ test_data.goal_state = test_data.goal_state.replace("<Incarnation>4<",
+ "<Incarnation>5<")
+ test_data.ext_conf = test_data.ext_conf.replace("FE0987654322", "FE0987654323")
+ exthandlers_handler.run()
+ self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.1.1")
+ self._assert_ext_status(protocol.report_ext_status, "success", 0)
+
+ #Test disable
+ test_data.goal_state = test_data.goal_state.replace("<Incarnation>5<",
+ "<Incarnation>6<")
+ test_data.ext_conf = test_data.ext_conf.replace("enabled", "disabled")
+ exthandlers_handler.run()
+ self._assert_handler_status(protocol.report_vm_status, "NotReady",
+ 1, "1.1.1")
+
+ #Test uninstall
+ test_data.goal_state = test_data.goal_state.replace("<Incarnation>6<",
+ "<Incarnation>7<")
+ test_data.ext_conf = test_data.ext_conf.replace("disabled", "uninstall")
+ exthandlers_handler.run()
+ self._assert_no_handler_status(protocol.report_vm_status)
+
+ #Test uninstall again!
+ test_data.goal_state = test_data.goal_state.replace("<Incarnation>7<",
+ "<Incarnation>8<")
+ exthandlers_handler.run()
+ self._assert_no_handler_status(protocol.report_vm_status)
+
+ #Test re-install
+ test_data.goal_state = test_data.goal_state.replace("<Incarnation>8<",
+ "<Incarnation>9<")
+ test_data.ext_conf = test_data.ext_conf.replace("uninstall", "enabled")
+ exthandlers_handler.run()
+ self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.1.1")
+ self._assert_ext_status(protocol.report_ext_status, "success", 0)
+
+ #Test upgrade available without GUID change
+ test_data.goal_state = test_data.goal_state.replace("<Incarnation>9<",
+ "<Incarnation>10<")
+ test_data.ext_conf = test_data.ext_conf.replace("1.1.0", "1.2.0")
+ exthandlers_handler.run()
+ self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.1.1")
+ self._assert_ext_status(protocol.report_ext_status, "success", 0)
+
+ #Test GUID change with upgrade available
+ test_data.goal_state = test_data.goal_state.replace("<Incarnation>10<",
+ "<Incarnation>11<")
+ test_data.ext_conf = test_data.ext_conf.replace("FE0987654323", "FE0987654324")
+ exthandlers_handler.run()
+ self._assert_handler_status(protocol.report_vm_status, "Ready", 1, "1.2.0")
+ self._assert_ext_status(protocol.report_ext_status, "success", 0)
+
@patch('azurelinuxagent.ga.exthandlers.add_event')
def test_ext_handler_download_failure(self, mock_add_event, *args):
test_data = WireProtocolData(DATA_FILE)
@@ -464,8 +563,8 @@ class TestExtension(AgentTestCase):
for internal in [False, True]:
for autoupgrade in [False, True]:
if internal:
- config_version = '1.2.0'
- decision_version = '1.2.0'
+ config_version = '1.3.0'
+ decision_version = '1.3.0'
if autoupgrade:
datafile = DATA_FILE_EXT_AUTOUPGRADE_INTERNALVERSION
else:
@@ -474,18 +573,18 @@ class TestExtension(AgentTestCase):
config_version = '1.0.0'
if autoupgrade:
datafile = DATA_FILE_EXT_AUTOUPGRADE
- decision_version = '1.1.0'
+ decision_version = '1.2.0'
else:
datafile = DATA_FILE
decision_version = '1.0.0'
_, protocol = self._create_mock(WireProtocolData(datafile), *args)
- ext_handlers, _ = protocol.get_ext_handlers()
+ ext_handlers, etag = protocol.get_ext_handlers()
self.assertEqual(1, len(ext_handlers.extHandlers))
ext_handler = ext_handlers.extHandlers[0]
self.assertEqual('OSTCExtensions.ExampleHandlerLinux', ext_handler.name)
self.assertEqual(config_version, ext_handler.properties.version, "config version.")
- ExtHandlerInstance(ext_handler, protocol).decide_version()
+ ExtHandlerInstance(ext_handler, protocol).decide_version(etag)
self.assertEqual(decision_version, ext_handler.properties.version, "decision version.")
def test_ext_handler_version_decide_between_minor_versions(self, *args):
@@ -500,7 +599,7 @@ class TestExtension(AgentTestCase):
cases = [
(None, '2.0', '2.0.0', '2.2.0'),
(None, '2.0.0', '2.0.0', '2.2.0'),
- ('1.0', '1.0.0', '1.0.0', '1.1.0'),
+ ('1.0', '1.0.0', '1.0.0', '1.2.0'),
(None, '2.1.0', '2.1.1', '2.2.0'),
(None, '2.2.0', '2.2.0', '2.2.0'),
(None, '2.3.0', '2.3.0', '2.3.0'),
@@ -512,6 +611,7 @@ class TestExtension(AgentTestCase):
_, protocol = self._create_mock(WireProtocolData(DATA_FILE), *args)
version_uri = Mock()
version_uri.uri = 'http://some/Microsoft.OSTCExtensions_ExampleHandlerLinux_asiaeast_manifest.xml'
+ incarnation = 1
for (installed_version, config_version, expected_version, autoupgrade_expected_version) in cases:
ext_handler = Mock()
@@ -523,8 +623,9 @@ class TestExtension(AgentTestCase):
ext_handler_instance = ExtHandlerInstance(ext_handler, protocol)
ext_handler_instance.get_installed_version = Mock(return_value=installed_version)
- ext_handler_instance.decide_version()
+ ext_handler_instance.decide_version(incarnation)
self.assertEqual(expected_version, ext_handler.properties.version)
+ incarnation += 1
ext_handler.properties.version = config_version
ext_handler.properties.upgradePolicy = 'auto'
@@ -532,8 +633,9 @@ class TestExtension(AgentTestCase):
ext_handler_instance = ExtHandlerInstance(ext_handler, protocol)
ext_handler_instance.get_installed_version = Mock(return_value=installed_version)
- ext_handler_instance.decide_version()
+ ext_handler_instance.decide_version(incarnation)
self.assertEqual(autoupgrade_expected_version, ext_handler.properties.version)
+ incarnation += 1
if __name__ == '__main__':
diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py
index 59251cb..21c81e9 100644
--- a/tests/ga/test_update.py
+++ b/tests/ga/test_update.py
@@ -17,16 +17,12 @@
from __future__ import print_function
-from datetime import datetime
-
-import json
-import shutil
-
from azurelinuxagent.common.event import *
from azurelinuxagent.common.protocol.hostplugin import *
from azurelinuxagent.common.protocol.metadata import *
from azurelinuxagent.common.protocol.wire import *
from azurelinuxagent.common.utils.fileutil import *
+from azurelinuxagent.common.version import AGENT_PKG_GLOB, AGENT_DIR_GLOB
from azurelinuxagent.ga.update import *
from tests.tools import *
@@ -43,12 +39,6 @@ FATAL_ERROR = {
"was_fatal" : True
}
-SENTINEL_ERROR = {
- "last_failure" : 0.0,
- "failure_count" : 0,
- "was_fatal" : True
-}
-
WITH_ERROR = {
"last_failure" : 42.42,
"failure_count" : 2,
@@ -107,9 +97,6 @@ def faux_logger():
class UpdateTestCase(AgentTestCase):
- def setUp(self):
- AgentTestCase.setUp(self)
- return
def agent_bin(self, version, suffix):
return "bin/{0}-{1}{2}.egg".format(AGENT_NAME, version, suffix)
@@ -118,6 +105,9 @@ class UpdateTestCase(AgentTestCase):
src_bin = glob.glob(os.path.join(path, self.agent_bin(src_v, '*')))[0]
dst_bin = os.path.join(path, self.agent_bin(dst_v, ''))
shutil.move(src_bin, dst_bin)
+
+ def agents(self):
+ return [GuestAgent(path=path) for path in self.agent_dirs()]
def agent_count(self):
return len(self.agent_dirs())
@@ -160,25 +150,17 @@ class UpdateTestCase(AgentTestCase):
fileutil.copy_file(agent, to_dir=self.tmp_dir)
return
- def expand_agents(self, mark_optional=False):
+ def expand_agents(self):
for agent in self.agent_pkgs():
path = os.path.join(self.tmp_dir, fileutil.trim_ext(agent, "zip"))
zipfile.ZipFile(agent).extractall(path)
- if mark_optional:
- src = os.path.join(data_dir, 'ga', 'supported.json')
- dst = os.path.join(path, 'supported.json')
- shutil.copy(src, dst)
-
- dst = os.path.join(path, 'error.json')
- fileutil.write_file(dst, json.dumps(SENTINEL_ERROR))
- return
- def prepare_agent(self, version, mark_optional=False):
+ def prepare_agent(self, version):
"""
Create a download for the current agent version, copied from test data
"""
self.copy_agents(get_agent_pkgs()[0])
- self.expand_agents(mark_optional=mark_optional)
+ self.expand_agents()
versions = self.agent_versions()
src_v = FlexibleVersion(str(versions[0]))
@@ -243,64 +225,6 @@ class UpdateTestCase(AgentTestCase):
return dst_v
-class TestSupportedDistribution(UpdateTestCase):
- def setUp(self):
- UpdateTestCase.setUp(self)
- self.sd = SupportedDistribution({
- 'slice':10,
- 'versions': ['^Ubuntu,16.10,yakkety$']})
-
- def test_creation(self):
- self.assertRaises(TypeError, SupportedDistribution)
- self.assertRaises(UpdateError, SupportedDistribution, None)
-
- self.assertEqual(self.sd.slice, 10)
- self.assertEqual(self.sd.versions, ['^Ubuntu,16.10,yakkety$'])
-
- @patch('platform.linux_distribution')
- def test_is_supported(self, mock_dist):
- mock_dist.return_value = ['Ubuntu', '16.10', 'yakkety']
- self.assertTrue(self.sd.is_supported)
-
- mock_dist.return_value = ['something', 'else', 'entirely']
- self.assertFalse(self.sd.is_supported)
-
- @patch('azurelinuxagent.ga.update.datetime')
- def test_in_slice(self, mock_dt):
- mock_dt.utcnow = Mock(return_value=datetime(2017, 1, 1, 0, 0, 5))
- self.assertTrue(self.sd.in_slice)
-
- mock_dt.utcnow = Mock(return_value=datetime(2017, 1, 1, 0, 0, 42))
- self.assertFalse(self.sd.in_slice)
-
-
-class TestSupported(UpdateTestCase):
- def setUp(self):
- UpdateTestCase.setUp(self)
- self.sp = Supported(os.path.join(data_dir, 'ga', 'supported.json'))
- self.sp.load()
-
- def test_creation(self):
- self.assertRaises(TypeError, Supported)
- self.assertRaises(UpdateError, Supported, None)
-
- @patch('platform.linux_distribution')
- def test_is_supported(self, mock_dist):
- mock_dist.return_value = ['Ubuntu', '16.10', 'yakkety']
- self.assertTrue(self.sp.is_supported)
-
- mock_dist.return_value = ['something', 'else', 'entirely']
- self.assertFalse(self.sp.is_supported)
-
- @patch('platform.linux_distribution', return_value=['Ubuntu', '16.10', 'yakkety'])
- @patch('azurelinuxagent.ga.update.datetime')
- def test_in_slice(self, mock_dt, mock_dist):
- mock_dt.utcnow = Mock(return_value=datetime(2017, 1, 1, 0, 0, 5))
- self.assertTrue(self.sp.in_slice)
-
- mock_dt.utcnow = Mock(return_value=datetime(2017, 1, 1, 0, 0, 42))
- self.assertFalse(self.sp.in_slice)
-
class TestGuestAgentError(UpdateTestCase):
def test_creation(self):
self.assertRaises(TypeError, GuestAgentError)
@@ -330,19 +254,6 @@ class TestGuestAgentError(UpdateTestCase):
self.assertEqual(NO_ERROR["was_fatal"], err.was_fatal)
return
- def test_is_sentinel(self):
- with self.get_error_file(error_data=SENTINEL_ERROR) as path:
- err = GuestAgentError(path.name)
- err.load()
- self.assertTrue(err.is_blacklisted)
- self.assertTrue(err.is_sentinel)
-
- with self.get_error_file(error_data=FATAL_ERROR) as path:
- err = GuestAgentError(path.name)
- err.load()
- self.assertTrue(err.is_blacklisted)
- self.assertFalse(err.is_sentinel)
-
def test_save(self):
err1 = self.create_error()
err1.mark_failure()
@@ -398,7 +309,6 @@ class TestGuestAgent(UpdateTestCase):
UpdateTestCase.setUp(self)
self.copy_agents(get_agent_file_path())
self.agent_path = os.path.join(self.tmp_dir, get_agent_name())
- return
def test_creation(self):
self.assertRaises(UpdateError, GuestAgent, "A very bad file name")
@@ -412,9 +322,6 @@ class TestGuestAgent(UpdateTestCase):
self.assertEqual(get_agent_name(), agent.name)
self.assertEqual(get_agent_version(), agent.version)
- self.assertFalse(agent._is_optional)
- self.assertFalse(agent._in_slice)
-
self.assertEqual(self.agent_path, agent.get_agent_dir())
path = os.path.join(self.agent_path, AGENT_MANIFEST_FILE)
@@ -430,7 +337,6 @@ class TestGuestAgent(UpdateTestCase):
self.assertTrue(agent.is_downloaded)
self.assertFalse(agent.is_blacklisted)
self.assertTrue(agent.is_available)
- return
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
def test_clear_error(self, mock_downloaded):
@@ -449,7 +355,6 @@ class TestGuestAgent(UpdateTestCase):
self.assertEqual(0, agent.error.failure_count)
self.assertFalse(agent.is_blacklisted)
self.assertEqual(agent.is_blacklisted, agent.error.is_blacklisted)
- return
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
@@ -462,7 +367,6 @@ class TestGuestAgent(UpdateTestCase):
agent.mark_failure(is_fatal=True)
self.assertFalse(agent.is_available)
- return
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
@@ -477,7 +381,30 @@ class TestGuestAgent(UpdateTestCase):
agent.mark_failure(is_fatal=True)
self.assertTrue(agent.is_blacklisted)
self.assertEqual(agent.is_blacklisted, agent.error.is_blacklisted)
- return
+
+ @patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
+ @patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
+ def test_resource_gone_error_not_blacklisted(self, mock_loaded, mock_downloaded):
+ try:
+ mock_downloaded.side_effect = ResourceGoneError()
+ agent = GuestAgent(path=self.agent_path)
+ self.assertFalse(agent.is_blacklisted)
+ except ResourceGoneError:
+ pass
+ except:
+ self.fail("Exception was not expected!")
+
+ @patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
+ @patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
+ def test_ioerror_not_blacklisted(self, mock_loaded, mock_downloaded):
+ try:
+ mock_downloaded.side_effect = IOError()
+ agent = GuestAgent(path=self.agent_path)
+ self.assertFalse(agent.is_blacklisted)
+ except IOError:
+ pass
+ except:
+ self.fail("Exception was not expected!")
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
@@ -486,44 +413,6 @@ class TestGuestAgent(UpdateTestCase):
self.assertFalse(agent.is_downloaded)
agent._unpack()
self.assertTrue(agent.is_downloaded)
- return
-
- @patch('platform.linux_distribution', return_value=['Ubuntu', '16.10', 'yakkety'])
- @patch('azurelinuxagent.ga.update.GuestAgent._enable')
- def test_is_optional(self, mock_enable, mock_dist):
- self.expand_agents(mark_optional=True)
- agent = GuestAgent(path=self.agent_path)
-
- self.assertTrue(agent.is_blacklisted)
- self.assertTrue(agent._is_optional)
-
- @patch('platform.linux_distribution', return_value=['Ubuntu', '16.10', 'yakkety'])
- @patch('azurelinuxagent.ga.update.datetime')
- def test_in_slice(self, mock_dt, mock_dist):
- self.expand_agents(mark_optional=True)
- agent = GuestAgent(path=self.agent_path)
-
- mock_dt.utcnow = Mock(return_value=datetime(2017, 1, 1, 0, 0, 5))
- self.assertTrue(agent._in_slice)
-
- mock_dt.utcnow = Mock(return_value=datetime(2017, 1, 1, 0, 0, 42))
- self.assertFalse(agent._in_slice)
-
- @patch('platform.linux_distribution', return_value=['Ubuntu', '16.10', 'yakkety'])
- @patch('azurelinuxagent.ga.update.datetime')
- def test_enable(self, mock_dt, mock_dist):
- mock_dt.utcnow = Mock(return_value=datetime(2017, 1, 1, 0, 0, 5))
-
- self.expand_agents(mark_optional=True)
- agent = GuestAgent(path=self.agent_path)
-
- self.assertFalse(agent.is_blacklisted)
- self.assertFalse(agent._is_optional)
-
- # Ensure the new state is preserved to disk
- agent = GuestAgent(path=self.agent_path)
- self.assertFalse(agent.is_blacklisted)
- self.assertFalse(agent._is_optional)
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
@@ -536,7 +425,6 @@ class TestGuestAgent(UpdateTestCase):
agent.mark_failure(is_fatal=True)
self.assertEqual(2, agent.error.failure_count)
self.assertTrue(agent.is_blacklisted)
- return
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
@@ -546,7 +434,6 @@ class TestGuestAgent(UpdateTestCase):
agent._unpack()
self.assertTrue(os.path.isdir(agent.get_agent_dir()))
self.assertTrue(os.path.isfile(agent.get_agent_manifest_path()))
- return
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
@@ -555,7 +442,6 @@ class TestGuestAgent(UpdateTestCase):
self.assertFalse(os.path.isdir(agent.get_agent_dir()))
os.remove(agent.get_agent_pkg_path())
self.assertRaises(UpdateError, agent._unpack)
- return
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
@@ -565,7 +451,6 @@ class TestGuestAgent(UpdateTestCase):
agent._load_manifest()
self.assertEqual(agent.manifest.get_enable_command(),
agent.get_agent_cmd())
- return
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
@@ -575,7 +460,6 @@ class TestGuestAgent(UpdateTestCase):
agent._unpack()
os.remove(agent.get_agent_manifest_path())
self.assertRaises(UpdateError, agent._load_manifest)
- return
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
@@ -588,7 +472,6 @@ class TestGuestAgent(UpdateTestCase):
with open(agent.get_agent_manifest_path(), "w") as file:
json.dump(EMPTY_MANIFEST, file)
self.assertRaises(UpdateError, agent._load_manifest)
- return
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
@@ -601,7 +484,6 @@ class TestGuestAgent(UpdateTestCase):
with open(agent.get_agent_manifest_path(), "w") as file:
file.write("This is not JSON data")
self.assertRaises(UpdateError, agent._load_manifest)
- return
def test_load_error(self):
agent = GuestAgent(path=self.agent_path)
@@ -609,7 +491,6 @@ class TestGuestAgent(UpdateTestCase):
agent._load_error()
self.assertTrue(agent.error is not None)
- return
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
@@ -627,7 +508,6 @@ class TestGuestAgent(UpdateTestCase):
agent._download()
self.assertTrue(os.path.isfile(agent.get_agent_pkg_path()))
- return
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
@@ -645,7 +525,6 @@ class TestGuestAgent(UpdateTestCase):
self.assertRaises(UpdateError, agent._download)
self.assertFalse(os.path.isfile(agent.get_agent_pkg_path()))
self.assertFalse(agent.is_downloaded)
- return
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
@patch("azurelinuxagent.ga.update.GuestAgent._ensure_loaded")
@@ -721,7 +600,6 @@ class TestGuestAgent(UpdateTestCase):
self.assertTrue(os.path.isfile(agent.get_agent_manifest_path()))
self.assertTrue(agent.is_downloaded)
- return
@patch("azurelinuxagent.ga.update.GuestAgent._download", side_effect=UpdateError)
def test_ensure_downloaded_download_fails(self, mock_download):
@@ -735,7 +613,6 @@ class TestGuestAgent(UpdateTestCase):
self.assertEqual(1, agent.error.failure_count)
self.assertFalse(agent.error.was_fatal)
self.assertFalse(agent.is_blacklisted)
- return
@patch("azurelinuxagent.ga.update.GuestAgent._download")
@patch("azurelinuxagent.ga.update.GuestAgent._unpack", side_effect=UpdateError)
@@ -749,7 +626,6 @@ class TestGuestAgent(UpdateTestCase):
self.assertEqual(1, agent.error.failure_count)
self.assertTrue(agent.error.was_fatal)
self.assertTrue(agent.is_blacklisted)
- return
@patch("azurelinuxagent.ga.update.GuestAgent._download")
@patch("azurelinuxagent.ga.update.GuestAgent._unpack")
@@ -764,7 +640,6 @@ class TestGuestAgent(UpdateTestCase):
self.assertEqual(1, agent.error.failure_count)
self.assertTrue(agent.error.was_fatal)
self.assertTrue(agent.is_blacklisted)
- return
@patch("azurelinuxagent.ga.update.GuestAgent._download")
@patch("azurelinuxagent.ga.update.GuestAgent._unpack")
@@ -786,7 +661,6 @@ class TestGuestAgent(UpdateTestCase):
self.assertTrue(agent.is_blacklisted)
self.assertEqual(0, mock_download.call_count)
self.assertEqual(0, mock_unpack.call_count)
- return
class TestUpdate(UpdateTestCase):
@@ -794,7 +668,7 @@ class TestUpdate(UpdateTestCase):
UpdateTestCase.setUp(self)
self.event_patch = patch('azurelinuxagent.common.event.add_event')
self.update_handler = get_update_handler()
- return
+ self.update_handler.protocol_util = Mock()
def test_creation(self):
self.assertTrue(self.update_handler.running)
@@ -809,13 +683,11 @@ class TestUpdate(UpdateTestCase):
self.assertEqual(None, self.update_handler.child_process)
self.assertEqual(None, self.update_handler.signal_handler)
- return
def test_emit_restart_event_writes_sentinal_file(self):
self.assertFalse(os.path.isfile(self.update_handler._sentinal_file_path()))
self.update_handler._emit_restart_event()
self.assertTrue(os.path.isfile(self.update_handler._sentinal_file_path()))
- return
def test_emit_restart_event_emits_event_if_not_clean_start(self):
try:
@@ -826,7 +698,6 @@ class TestUpdate(UpdateTestCase):
except Exception as e:
pass
self.event_patch.stop()
- return
def _create_protocol(self, count=5, versions=None):
latest_version = self.prepare_agents(count=count)
@@ -834,110 +705,6 @@ class TestUpdate(UpdateTestCase):
versions = [latest_version]
return ProtocolMock(versions=versions)
- def _test_upgrade_available(
- self,
- base_version=FlexibleVersion(AGENT_VERSION),
- protocol=None,
- versions=None,
- count=5):
-
- if protocol is None:
- protocol = self._create_protocol(count=count, versions=versions)
-
- self.update_handler.protocol_util = protocol
- conf.get_autoupdate_gafamily = Mock(return_value=protocol.family)
-
- return self.update_handler._upgrade_available(base_version=base_version)
-
- def test_upgrade_available_returns_true_on_first_use(self):
- self.assertTrue(self._test_upgrade_available())
- return
-
- def test_upgrade_available_will_refresh_goal_state(self):
- protocol = self._create_protocol()
- protocol.emulate_stale_goal_state()
- self.assertTrue(self._test_upgrade_available(protocol=protocol))
- self.assertEqual(2, protocol.call_counts["get_vmagent_manifests"])
- self.assertEqual(1, protocol.call_counts["get_vmagent_pkgs"])
- self.assertEqual(1, protocol.call_counts["update_goal_state"])
- self.assertTrue(protocol.goal_state_forced)
- return
-
- def test_get_latest_agent_excluded(self):
- self.prepare_agent(AGENT_VERSION)
- self.assertFalse(self._test_upgrade_available(
- versions=self.agent_versions(),
- count=1))
- self.assertEqual(None, self.update_handler.get_latest_agent())
- return
-
- def test_upgrade_available_handles_missing_family(self):
- extensions_config = ExtensionsConfig(load_data("wire/ext_conf_missing_family.xml"))
- protocol = ProtocolMock()
- protocol.family = "Prod"
- protocol.agent_manifests = extensions_config.vmagent_manifests
- self.update_handler.protocol_util = protocol
- with patch('azurelinuxagent.common.logger.warn') as mock_logger:
- with patch('tests.ga.test_update.ProtocolMock.get_vmagent_pkgs', side_effect=ProtocolError):
- self.assertFalse(self.update_handler._upgrade_available(base_version=CURRENT_VERSION))
- self.assertEqual(0, mock_logger.call_count)
- return
-
- def test_upgrade_available_includes_old_agents(self):
- self.prepare_agents()
-
- old_version = self.agent_versions()[-1]
- old_count = old_version.version[-1]
-
- self.replicate_agents(src_v=old_version, count=old_count, increment=-1)
- all_count = len(self.agent_versions())
-
- self.assertTrue(self._test_upgrade_available(versions=self.agent_versions()))
- self.assertEqual(all_count, len(self.update_handler.agents))
- return
-
- def test_upgrade_available_purges_old_agents(self):
- self.prepare_agents()
- agent_count = self.agent_count()
- self.assertEqual(5, agent_count)
-
- agent_versions = self.agent_versions()[:3]
- self.assertTrue(self._test_upgrade_available(versions=agent_versions))
- self.assertEqual(len(agent_versions), len(self.update_handler.agents))
- self.assertEqual(agent_versions, self.agent_versions())
- return
-
- def test_upgrade_available_skips_if_too_frequent(self):
- conf.get_autoupdate_frequency = Mock(return_value=10000)
- self.update_handler.last_attempt_time = time.time()
- self.assertFalse(self._test_upgrade_available())
- return
-
- def test_upgrade_available_skips_if_when_no_new_versions(self):
- self.prepare_agents()
- base_version = self.agent_versions()[0] + 1
- self.assertFalse(self._test_upgrade_available(base_version=base_version))
- return
-
- def test_upgrade_available_skips_when_no_versions(self):
- self.assertFalse(self._test_upgrade_available(protocol=ProtocolMock()))
- return
-
- def test_upgrade_available_skips_when_updates_are_disabled(self):
- conf.get_autoupdate_enabled = Mock(return_value=False)
- self.assertFalse(self._test_upgrade_available())
- return
-
- def test_upgrade_available_sorts(self):
- self.prepare_agents()
- self._test_upgrade_available()
-
- v = FlexibleVersion("100000")
- for a in self.update_handler.agents:
- self.assertTrue(v > a.version)
- v = a.version
- return
-
def _test_ensure_no_orphans(self, invocations=3, interval=ORPHAN_WAIT_INTERVAL, pid_count=0):
with patch.object(self.update_handler, 'osutil') as mock_util:
# Note:
@@ -962,27 +729,23 @@ class TestUpdate(UpdateTestCase):
for pid_file in pid_files:
self.assertFalse(os.path.exists(pid_file))
return mock_util.check_pid_alive.call_count, mock_sleep.call_count
- return
def test_ensure_no_orphans(self):
fileutil.write_file(os.path.join(self.tmp_dir, "0_waagent.pid"), ustr(41))
calls, sleeps = self._test_ensure_no_orphans(invocations=3, pid_count=1)
self.assertEqual(3, calls)
self.assertEqual(2, sleeps)
- return
def test_ensure_no_orphans_skips_if_no_orphans(self):
calls, sleeps = self._test_ensure_no_orphans(invocations=3)
self.assertEqual(0, calls)
self.assertEqual(0, sleeps)
- return
def test_ensure_no_orphans_ignores_exceptions(self):
with patch('azurelinuxagent.common.utils.fileutil.read_file', side_effect=Exception):
calls, sleeps = self._test_ensure_no_orphans(invocations=3)
self.assertEqual(0, calls)
self.assertEqual(0, sleeps)
- return
def test_ensure_no_orphans_kills_after_interval(self):
fileutil.write_file(os.path.join(self.tmp_dir, "0_waagent.pid"), ustr(41))
@@ -994,7 +757,64 @@ class TestUpdate(UpdateTestCase):
self.assertEqual(3, calls)
self.assertEqual(2, sleeps)
self.assertEqual(1, mock_kill.call_count)
- return
+
+ @patch('azurelinuxagent.ga.update.datetime')
+ def test_ensure_partition_assigned(self, mock_time):
+ path = os.path.join(conf.get_lib_dir(), AGENT_PARTITION_FILE)
+ mock_time.utcnow = Mock()
+
+ self.assertFalse(os.path.exists(path))
+
+ for n in range(0,99):
+ mock_time.utcnow.return_value = Mock(microsecond=n* 10000)
+
+ self.update_handler._ensure_partition_assigned()
+
+ self.assertTrue(os.path.exists(path))
+ s = fileutil.read_file(path)
+ self.assertEqual(n, int(s))
+ os.remove(path)
+
+ def test_ensure_readonly_sets_readonly(self):
+ test_files = [
+ os.path.join(conf.get_lib_dir(), "faux_certificate.crt"),
+ os.path.join(conf.get_lib_dir(), "faux_certificate.p7m"),
+ os.path.join(conf.get_lib_dir(), "faux_certificate.pem"),
+ os.path.join(conf.get_lib_dir(), "faux_certificate.prv"),
+ os.path.join(conf.get_lib_dir(), "ovf-env.xml")
+ ]
+ for path in test_files:
+ fileutil.write_file(path, "Faux content")
+ os.chmod(path,
+ stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
+
+ self.update_handler._ensure_readonly_files()
+
+ for path in test_files:
+ mode = os.stat(path).st_mode
+ mode &= (stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+ self.assertEqual(0, mode ^ stat.S_IRUSR)
+
+ def test_ensure_readonly_leaves_unmodified(self):
+ test_files = [
+ os.path.join(conf.get_lib_dir(), "faux.xml"),
+ os.path.join(conf.get_lib_dir(), "faux.json"),
+ os.path.join(conf.get_lib_dir(), "faux.txt"),
+ os.path.join(conf.get_lib_dir(), "faux")
+ ]
+ for path in test_files:
+ fileutil.write_file(path, "Faux content")
+ os.chmod(path,
+ stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
+
+ self.update_handler._ensure_readonly_files()
+
+ for path in test_files:
+ mode = os.stat(path).st_mode
+ mode &= (stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+ self.assertEqual(
+ stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH,
+ mode)
def _test_evaluate_agent_health(self, child_agent_index=0):
self.prepare_agents()
@@ -1010,36 +830,30 @@ class TestUpdate(UpdateTestCase):
self.update_handler.child_agent = child_agent
self.update_handler._evaluate_agent_health(latest_agent)
- return
def test_evaluate_agent_health_ignores_installed_agent(self):
self.update_handler._evaluate_agent_health(None)
- return
def test_evaluate_agent_health_raises_exception_for_restarting_agent(self):
self.update_handler.child_launch_time = time.time() - (4 * 60)
self.update_handler.child_launch_attempts = CHILD_LAUNCH_RESTART_MAX - 1
self.assertRaises(Exception, self._test_evaluate_agent_health)
- return
def test_evaluate_agent_health_will_not_raise_exception_for_long_restarts(self):
self.update_handler.child_launch_time = time.time() - 24 * 60
self.update_handler.child_launch_attempts = CHILD_LAUNCH_RESTART_MAX
self._test_evaluate_agent_health()
- return
def test_evaluate_agent_health_will_not_raise_exception_too_few_restarts(self):
self.update_handler.child_launch_time = time.time()
self.update_handler.child_launch_attempts = CHILD_LAUNCH_RESTART_MAX - 2
self._test_evaluate_agent_health()
- return
def test_evaluate_agent_health_resets_with_new_agent(self):
self.update_handler.child_launch_time = time.time() - (4 * 60)
self.update_handler.child_launch_attempts = CHILD_LAUNCH_RESTART_MAX - 1
self._test_evaluate_agent_health(child_agent_index=1)
self.assertEqual(1, self.update_handler.child_launch_attempts)
- return
def test_filter_blacklisted_agents(self):
self.prepare_agents()
@@ -1053,7 +867,31 @@ class TestUpdate(UpdateTestCase):
agent.mark_failure(is_fatal=True)
self.update_handler._filter_blacklisted_agents()
self.assertEqual(kept_agents, self.update_handler.agents)
- return
+
+ def test_find_agents(self):
+ self.prepare_agents()
+
+ self.assertTrue(0 <= len(self.update_handler.agents))
+ self.update_handler._find_agents()
+ self.assertEqual(len(get_agents(self.tmp_dir)), len(self.update_handler.agents))
+
+ def test_find_agents_does_reload(self):
+ self.prepare_agents()
+
+ self.update_handler._find_agents()
+ agents = self.update_handler.agents
+
+ self.update_handler._find_agents()
+ self.assertNotEqual(agents, self.update_handler.agents)
+
+ def test_find_agents_sorts(self):
+ self.prepare_agents()
+ self.update_handler._find_agents()
+
+ v = FlexibleVersion("100000")
+ for a in self.update_handler.agents:
+ self.assertTrue(v > a.version)
+ v = a.version
@patch('azurelinuxagent.common.protocol.wire.WireClient.get_host_plugin')
def test_get_host_plugin_returns_host_for_wireserver(self, mock_get_host):
@@ -1076,16 +914,20 @@ class TestUpdate(UpdateTestCase):
latest_agent = self.update_handler.get_latest_agent()
self.assertEqual(len(get_agents(self.tmp_dir)), len(self.update_handler.agents))
self.assertEqual(latest_version, latest_agent.version)
- return
+
+ def test_get_latest_agent_excluded(self):
+ self.prepare_agent(AGENT_VERSION)
+ self.assertFalse(self._test_upgrade_available(
+ versions=self.agent_versions(),
+ count=1))
+ self.assertEqual(None, self.update_handler.get_latest_agent())
def test_get_latest_agent_no_updates(self):
self.assertEqual(None, self.update_handler.get_latest_agent())
- return
def test_get_latest_agent_skip_updates(self):
conf.get_autoupdate_enabled = Mock(return_value=False)
self.assertEqual(None, self.update_handler.get_latest_agent())
- return
def test_get_latest_agent_skips_unavailable(self):
self.prepare_agents()
@@ -1098,12 +940,10 @@ class TestUpdate(UpdateTestCase):
latest_agent = self.update_handler.get_latest_agent()
self.assertTrue(latest_agent.version < latest_version)
self.assertEqual(latest_agent.version, prior_agent.version)
- return
def test_get_pid_files(self):
pid_files = self.update_handler._get_pid_files()
self.assertEqual(0, len(pid_files))
- return
def test_get_pid_files_returns_previous(self):
for n in range(1250):
@@ -1114,74 +954,61 @@ class TestUpdate(UpdateTestCase):
pid_dir, pid_name, pid_re = self.update_handler._get_pid_parts()
for p in pid_files:
self.assertTrue(pid_re.match(os.path.basename(p)))
- return
def test_is_clean_start_returns_true_when_no_sentinal(self):
self.assertFalse(os.path.isfile(self.update_handler._sentinal_file_path()))
self.assertTrue(self.update_handler._is_clean_start)
- return
- def test_is_clean_start_returns_true_sentinal_agent_is_not_current(self):
- self.update_handler._set_sentinal(agent="Not the Current Agent")
- self.assertTrue(os.path.isfile(self.update_handler._sentinal_file_path()))
- self.assertTrue(self.update_handler._is_clean_start)
- return
-
- def test_is_clean_start_returns_false_for_current_agent(self):
+ def test_is_clean_start_returns_false_when_sentinal_exists(self):
self.update_handler._set_sentinal(agent=CURRENT_AGENT)
self.assertFalse(self.update_handler._is_clean_start)
- return
def test_is_clean_start_returns_false_for_exceptions(self):
self.update_handler._set_sentinal()
with patch("azurelinuxagent.common.utils.fileutil.read_file", side_effect=Exception):
self.assertFalse(self.update_handler._is_clean_start)
- return
def test_is_orphaned_returns_false_if_parent_exists(self):
fileutil.write_file(conf.get_agent_pid_file_path(), ustr(42))
with patch('os.getppid', return_value=42):
self.assertFalse(self.update_handler._is_orphaned)
- return
def test_is_orphaned_returns_true_if_parent_is_init(self):
with patch('os.getppid', return_value=1):
self.assertTrue(self.update_handler._is_orphaned)
- return
def test_is_orphaned_returns_true_if_parent_does_not_exist(self):
fileutil.write_file(conf.get_agent_pid_file_path(), ustr(24))
with patch('os.getppid', return_value=42):
self.assertTrue(self.update_handler._is_orphaned)
- return
- def test_find_agents(self):
- self.prepare_agents()
+ def test_is_version_available(self):
+ self.prepare_agents(is_available=True)
+ self.update_handler.agents = self.agents()
- self.assertTrue(0 <= len(self.update_handler.agents))
- self.update_handler._find_agents()
- self.assertEqual(len(get_agents(self.tmp_dir)), len(self.update_handler.agents))
- return
+ for agent in self.agents():
+ self.assertTrue(self.update_handler._is_version_eligible(agent.version))
- def test_find_agents_does_reload(self):
- self.prepare_agents()
+ @patch("azurelinuxagent.ga.update.is_current_agent_installed", return_value=False)
+ def test_is_version_available_rejects(self, mock_current):
+ self.prepare_agents(is_available=True)
+ self.update_handler.agents = self.agents()
- self.update_handler._find_agents()
- agents = self.update_handler.agents
+ self.update_handler.agents[0].mark_failure(is_fatal=True)
+ self.assertFalse(self.update_handler._is_version_eligible(self.agents()[0].version))
- self.update_handler._find_agents()
- self.assertNotEqual(agents, self.update_handler.agents)
- return
+ @patch("azurelinuxagent.ga.update.is_current_agent_installed", return_value=True)
+ def test_is_version_available_accepts_current(self, mock_current):
+ self.update_handler.agents = []
+ self.assertTrue(self.update_handler._is_version_eligible(CURRENT_VERSION))
- def test_find_agents_sorts(self):
+ @patch("azurelinuxagent.ga.update.is_current_agent_installed", return_value=False)
+ def test_is_version_available_rejects_by_default(self, mock_current):
self.prepare_agents()
- self.update_handler._find_agents()
+ self.update_handler.agents = []
- v = FlexibleVersion("100000")
- for a in self.update_handler.agents:
- self.assertTrue(v > a.version)
- v = a.version
- return
+ v = self.agents()[0].version
+ self.assertFalse(self.update_handler._is_version_eligible(v))
def test_purge_agents(self):
self.prepare_agents()
@@ -1191,8 +1018,8 @@ class TestUpdate(UpdateTestCase):
self.assertTrue(2 < len(self.update_handler.agents))
# Purge every other agent
- kept_agents = self.update_handler.agents[1::2]
- purged_agents = self.update_handler.agents[::2]
+ purged_agents = self.update_handler.agents[1::2]
+ kept_agents = self.update_handler.agents[::2]
# Reload and assert only the kept agents remain on disk
self.update_handler.agents = kept_agents
@@ -1213,7 +1040,6 @@ class TestUpdate(UpdateTestCase):
agent_path = os.path.join(self.tmp_dir, "{0}-{1}".format(AGENT_NAME, agent.version))
self.assertTrue(os.path.exists(agent_path))
self.assertTrue(os.path.exists(agent_path + ".zip"))
- return
def _test_run_latest(self, mock_child=None, mock_time=None, child_args=None):
if mock_child is None:
@@ -1246,7 +1072,6 @@ class TestUpdate(UpdateTestCase):
self.assertEqual(True, 'cwd' in kwargs)
self.assertEqual(agent.get_agent_dir(), kwargs['cwd'])
self.assertEqual(False, '\x00' in cmds[0])
- return
def test_run_latest_passes_child_args(self):
self.prepare_agents()
@@ -1258,7 +1083,6 @@ class TestUpdate(UpdateTestCase):
self.assertTrue(len(args) > 1)
self.assertTrue(args[0].startswith("python"))
self.assertEqual("AnArgument", args[len(args)-1])
- return
def test_run_latest_polls_and_waits_for_success(self):
mock_child = ChildMock(return_value=None)
@@ -1266,7 +1090,6 @@ class TestUpdate(UpdateTestCase):
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)
@@ -1274,7 +1097,6 @@ class TestUpdate(UpdateTestCase):
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)
@@ -1282,14 +1104,12 @@ class TestUpdate(UpdateTestCase):
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_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()
@@ -1298,7 +1118,6 @@ class TestUpdate(UpdateTestCase):
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())
@@ -1308,7 +1127,6 @@ class TestUpdate(UpdateTestCase):
self.assertEqual(args[0], [get_python_cmd(), "-u", sys.argv[0], "-run-exthandlers"])
self.assertEqual(True, 'cwd' in kwargs)
self.assertEqual(os.getcwd(), kwargs['cwd'])
- return
def test_run_latest_forwards_output(self):
try:
@@ -1332,7 +1150,6 @@ class TestUpdate(UpdateTestCase):
self.assertEqual(1, len(stderr.readlines()))
finally:
shutil.rmtree(tempdir, True)
- return
def test_run_latest_nonzero_code_marks_failures(self):
# logger.add_logger_appender(logger.AppenderType.STDOUT)
@@ -1350,7 +1167,6 @@ class TestUpdate(UpdateTestCase):
self.assertFalse(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_exception_blacklists(self):
self.prepare_agents()
@@ -1367,7 +1183,6 @@ class TestUpdate(UpdateTestCase):
self.assertTrue(latest_agent.error.is_blacklisted)
self.assertNotEqual(0.0, latest_agent.error.last_failure)
self.assertEqual(1, latest_agent.error.failure_count)
- return
def test_run_latest_exception_does_not_blacklist_if_terminating(self):
self.prepare_agents()
@@ -1385,20 +1200,17 @@ class TestUpdate(UpdateTestCase):
self.assertFalse(latest_agent.error.is_blacklisted)
self.assertEqual(0.0, latest_agent.error.last_failure)
self.assertEqual(0, 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)
@@ -1436,42 +1248,34 @@ class TestUpdate(UpdateTestCase):
self.assertEqual(1, mock_env.call_count)
self.assertEqual(1, mock_exit.call_count)
- return
def test_run(self):
self._test_run()
- return
def test_run_keeps_running(self):
self._test_run(invocations=15, calls=[call.run()]*15)
- return
def test_run_stops_if_update_available(self):
self.update_handler._upgrade_available = Mock(return_value=True)
self._test_run(invocations=0, calls=[], enable_updates=True)
- return
def test_run_stops_if_orphaned(self):
with patch('os.getppid', return_value=1):
self._test_run(invocations=0, calls=[], enable_updates=True)
- return
def test_run_clears_sentinal_on_successful_exit(self):
self._test_run()
self.assertFalse(os.path.isfile(self.update_handler._sentinal_file_path()))
- return
def test_run_leaves_sentinal_on_unsuccessful_exit(self):
self.update_handler._upgrade_available = Mock(side_effect=Exception)
self._test_run(invocations=0, calls=[], enable_updates=True)
self.assertTrue(os.path.isfile(self.update_handler._sentinal_file_path()))
- return
def test_run_emits_restart_event(self):
self.update_handler._emit_restart_event = Mock()
self._test_run()
self.assertEqual(1, self.update_handler._emit_restart_event.call_count)
- return
def test_set_agents_sets_agents(self):
self.prepare_agents()
@@ -1479,7 +1283,6 @@ class TestUpdate(UpdateTestCase):
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()
@@ -1490,34 +1293,29 @@ class TestUpdate(UpdateTestCase):
for a in self.update_handler.agents:
self.assertTrue(v > a.version)
v = a.version
- return
def test_set_sentinal(self):
self.assertFalse(os.path.isfile(self.update_handler._sentinal_file_path()))
self.update_handler._set_sentinal()
self.assertTrue(os.path.isfile(self.update_handler._sentinal_file_path()))
- return
def test_set_sentinal_writes_current_agent(self):
self.update_handler._set_sentinal()
self.assertTrue(
fileutil.read_file(self.update_handler._sentinal_file_path()),
CURRENT_AGENT)
- return
def test_shutdown(self):
self.update_handler._set_sentinal()
self.update_handler._shutdown()
self.assertFalse(self.update_handler.running)
self.assertFalse(os.path.isfile(self.update_handler._sentinal_file_path()))
- return
def test_shutdown_ignores_missing_sentinal_file(self):
self.assertFalse(os.path.isfile(self.update_handler._sentinal_file_path()))
self.update_handler._shutdown()
self.assertFalse(self.update_handler.running)
self.assertFalse(os.path.isfile(self.update_handler._sentinal_file_path()))
- return
def test_shutdown_ignores_exceptions(self):
self.update_handler._set_sentinal()
@@ -1527,7 +1325,101 @@ class TestUpdate(UpdateTestCase):
self.update_handler._shutdown()
except Exception as e:
self.assertTrue(False, "Unexpected exception")
- return
+
+ def _test_upgrade_available(
+ self,
+ base_version=FlexibleVersion(AGENT_VERSION),
+ protocol=None,
+ versions=None,
+ count=5):
+
+ if protocol is None:
+ protocol = self._create_protocol(count=count, versions=versions)
+
+ self.update_handler.protocol_util = protocol
+ conf.get_autoupdate_gafamily = Mock(return_value=protocol.family)
+
+ return self.update_handler._upgrade_available(base_version=base_version)
+
+ def test_upgrade_available_returns_true_on_first_use(self):
+ self.assertTrue(self._test_upgrade_available())
+
+ def test_upgrade_available_will_refresh_goal_state(self):
+ protocol = self._create_protocol()
+ protocol.emulate_stale_goal_state()
+ self.assertTrue(self._test_upgrade_available(protocol=protocol))
+ self.assertEqual(2, protocol.call_counts["get_vmagent_manifests"])
+ self.assertEqual(1, protocol.call_counts["get_vmagent_pkgs"])
+ self.assertEqual(1, protocol.call_counts["update_goal_state"])
+ self.assertTrue(protocol.goal_state_forced)
+
+ def test_upgrade_available_handles_missing_family(self):
+ extensions_config = ExtensionsConfig(load_data("wire/ext_conf_missing_family.xml"))
+ protocol = ProtocolMock()
+ protocol.family = "Prod"
+ protocol.agent_manifests = extensions_config.vmagent_manifests
+ self.update_handler.protocol_util = protocol
+ with patch('azurelinuxagent.common.logger.warn') as mock_logger:
+ with patch('tests.ga.test_update.ProtocolMock.get_vmagent_pkgs', side_effect=ProtocolError):
+ self.assertFalse(self.update_handler._upgrade_available(base_version=CURRENT_VERSION))
+ self.assertEqual(0, mock_logger.call_count)
+
+ def test_upgrade_available_includes_old_agents(self):
+ self.prepare_agents()
+
+ old_version = self.agent_versions()[-1]
+ old_count = old_version.version[-1]
+
+ self.replicate_agents(src_v=old_version, count=old_count, increment=-1)
+ all_count = len(self.agent_versions())
+
+ self.assertTrue(self._test_upgrade_available(versions=self.agent_versions()))
+ self.assertEqual(all_count, len(self.update_handler.agents))
+
+ def test_upgrade_available_purges_old_agents(self):
+ self.prepare_agents()
+ agent_count = self.agent_count()
+ self.assertEqual(5, agent_count)
+
+ agent_versions = self.agent_versions()[:3]
+ self.assertTrue(self._test_upgrade_available(versions=agent_versions))
+ self.assertEqual(len(agent_versions), len(self.update_handler.agents))
+
+ # Purging always keeps the running agent
+ if CURRENT_VERSION not in agent_versions:
+ agent_versions.append(CURRENT_VERSION)
+ self.assertEqual(agent_versions, self.agent_versions())
+
+ def test_update_available_returns_true_if_current_gets_blacklisted(self):
+ self.update_handler._is_version_eligible = Mock(return_value=False)
+ self.assertTrue(self._test_upgrade_available())
+
+ def test_upgrade_available_skips_if_too_frequent(self):
+ conf.get_autoupdate_frequency = Mock(return_value=10000)
+ self.update_handler.last_attempt_time = time.time()
+ self.assertFalse(self._test_upgrade_available())
+
+ def test_upgrade_available_skips_if_when_no_new_versions(self):
+ self.prepare_agents()
+ base_version = self.agent_versions()[0] + 1
+ self.update_handler._is_version_eligible = lambda x: x == base_version
+ self.assertFalse(self._test_upgrade_available(base_version=base_version))
+
+ def test_upgrade_available_skips_when_no_versions(self):
+ self.assertFalse(self._test_upgrade_available(protocol=ProtocolMock()))
+
+ def test_upgrade_available_skips_when_updates_are_disabled(self):
+ conf.get_autoupdate_enabled = Mock(return_value=False)
+ self.assertFalse(self._test_upgrade_available())
+
+ def test_upgrade_available_sorts(self):
+ self.prepare_agents()
+ self._test_upgrade_available()
+
+ v = FlexibleVersion("100000")
+ for a in self.update_handler.agents:
+ self.assertTrue(v > a.version)
+ v = a.version
def test_write_pid_file(self):
for n in range(1112):
@@ -1538,7 +1430,6 @@ class TestUpdate(UpdateTestCase):
self.assertEqual("1111_waagent.pid", os.path.basename(pid_files[-1]))
self.assertEqual("1112_waagent.pid", os.path.basename(pid_file))
self.assertEqual(fileutil.read_file(pid_file), ustr(1112))
- return
def test_write_pid_file_ignores_exceptions(self):
with patch('azurelinuxagent.common.utils.fileutil.write_file', side_effect=Exception):
@@ -1546,7 +1437,64 @@ class TestUpdate(UpdateTestCase):
pid_files, pid_file = self.update_handler._write_pid_file()
self.assertEqual(0, len(pid_files))
self.assertEqual(None, pid_file)
- return
+
+ @patch('azurelinuxagent.common.protocol.wire.WireClient.get_goal_state',
+ return_value=GoalState(load_data('wire/goal_state.xml')))
+ def test_package_filter_for_agent_manifest(self, _):
+
+ protocol = WireProtocol('12.34.56.78')
+ extension_config = ExtensionsConfig(load_data('wire/ext_conf.xml'))
+ agent_manifest = extension_config.vmagent_manifests.vmAgentManifests[0]
+
+ # has agent versions 13, 14
+ ga_manifest_1 = ExtensionManifest(load_data('wire/ga_manifest_1.xml'))
+
+ # has agent versions 13, 14, 15
+ ga_manifest_2 = ExtensionManifest(load_data('wire/ga_manifest_2.xml'))
+
+ goal_state = protocol.client.get_goal_state()
+ disk_cache = os.path.join(conf.get_lib_dir(),
+ AGENTS_MANIFEST_FILE_NAME.format(
+ agent_manifest.family,
+ goal_state.incarnation))
+
+ self.assertFalse(os.path.exists(disk_cache))
+ self.assertTrue(ga_manifest_1.allowed_versions is None)
+
+ with patch(
+ 'azurelinuxagent.common.protocol.wire.WireClient'
+ '.get_gafamily_manifest',
+ return_value=ga_manifest_1):
+
+ pkg_list_1 = protocol.get_vmagent_pkgs(agent_manifest)
+ self.assertTrue(pkg_list_1 is not None)
+ self.assertTrue(len(pkg_list_1.versions) == 2)
+ self.assertTrue(pkg_list_1.versions[0].version == '2.2.13')
+ self.assertTrue(pkg_list_1.versions[0].uris[0].uri == 'url1_13')
+ self.assertTrue(pkg_list_1.versions[1].version == '2.2.14')
+ self.assertTrue(pkg_list_1.versions[1].uris[0].uri == 'url1_14')
+
+ self.assertTrue(os.path.exists(disk_cache))
+
+ with patch(
+ 'azurelinuxagent.common.protocol.wire.WireClient'
+ '.get_gafamily_manifest',
+ return_value=ga_manifest_2):
+
+ pkg_list_2 = protocol.get_vmagent_pkgs(agent_manifest)
+ self.assertTrue(pkg_list_2 is not None)
+ self.assertTrue(len(pkg_list_2.versions) == 2)
+ self.assertTrue(pkg_list_2.versions[0].version == '2.2.13')
+ self.assertTrue(pkg_list_2.versions[0].uris[0].uri == 'url2_13')
+ self.assertTrue(pkg_list_2.versions[1].version == '2.2.14')
+ self.assertTrue(pkg_list_2.versions[1].uris[0].uri == 'url2_14')
+ # does not contain 2.2.15
+
+ self.assertTrue(os.path.exists(disk_cache))
+ self.assertTrue(ga_manifest_2.allowed_versions is not None)
+ self.assertTrue(len(ga_manifest_2.allowed_versions) == 2)
+ self.assertTrue(ga_manifest_2.allowed_versions[0] == '2.2.13')
+ self.assertTrue(ga_manifest_2.allowed_versions[1] == '2.2.14')
class ChildMock(Mock):
@@ -1555,7 +1503,6 @@ class ChildMock(Mock):
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):
@@ -1573,7 +1520,6 @@ class ProtocolMock(object):
self.versions = versions if versions is not None else []
self.create_manifests()
self.create_packages()
- return
def emulate_stale_goal_state(self):
self.goal_state_is_stale = True
@@ -1589,7 +1535,6 @@ class ProtocolMock(object):
manifest_uri = "https://nowhere.msft/agent/{0}".format(i)
manifest.versionsManifestUris.append(VMAgentManifestUri(uri=manifest_uri))
self.agent_manifests.vmAgentManifests.append(manifest)
- return
def create_packages(self):
self.agent_packages = ExtHandlerPackageList()
@@ -1602,7 +1547,6 @@ class ProtocolMock(object):
package_uri = "https://nowhere.msft/agent_pkg/{0}".format(i)
package.uris.append(ExtHandlerPackageUri(uri=package_uri))
self.agent_packages.versions.append(package)
- return
def get_protocol(self):
return self
@@ -1624,7 +1568,7 @@ class ProtocolMock(object):
def update_goal_state(self, forced=False, max_retry=3):
self.call_counts["update_goal_state"] += 1
self.goal_state_forced = self.goal_state_forced or forced
- return
+
class ResponseMock(Mock):
def __init__(self, status=restutil.httpclient.OK, response=None, reason=None):
@@ -1632,7 +1576,6 @@ class ResponseMock(Mock):
self.status = status
self.reason = reason
self.response = response
- return
def read(self):
return self.response
@@ -1646,11 +1589,9 @@ class TimeMock(Mock):
self.time_increment = time_increment
self.sleep_interval = None
- return
def sleep(self, n):
self.sleep_interval = n
- return
def time(self):
self.time_call_count += 1
@@ -1658,5 +1599,6 @@ class TimeMock(Mock):
self.next_time += self.time_increment
return current_time
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/pa/test_provision.py b/tests/pa/test_provision.py
index 7045fcc..2c2d2c9 100644
--- a/tests/pa/test_provision.py
+++ b/tests/pa/test_provision.py
@@ -15,9 +15,13 @@
# Requires Python 2.4+ and Openssl 1.0+
#
+import json
+import socket
+
import azurelinuxagent.common.utils.fileutil as fileutil
-from azurelinuxagent.common.exception import ProtocolError
+from azurelinuxagent.common.event import WALAEventOperation
+from azurelinuxagent.common.exception import ProvisionError
from azurelinuxagent.common.osutil.default import DefaultOSUtil
from azurelinuxagent.common.protocol import OVF_FILE_NAME
from azurelinuxagent.pa.provision import get_provision_handler
@@ -115,6 +119,85 @@ class TestProvision(AgentTestCase):
ph.osutil.is_current_instance_id.assert_called_once()
deprovision_handler.run_changed_unique_id.assert_called_once()
+ @distros()
+ @patch('azurelinuxagent.common.osutil.default.DefaultOSUtil.get_instance_id',
+ return_value='B9F3C233-9913-9F42-8EB3-BA656DF32502')
+ def test_provision_telemetry_success(self, mock_util, distro_name, distro_version,
+ distro_full_name):
+ """
+ Assert that the agent issues two telemetry messages as part of a
+ successful provisioning.
+
+ 1. Provision
+ 2. GuestState
+ """
+ ph = get_provision_handler(distro_name, distro_version,
+ distro_full_name)
+ ph.report_event = MagicMock()
+ ph.reg_ssh_host_key = MagicMock(return_value='--thumprint--')
+
+ mock_osutil = MagicMock()
+ mock_osutil.decode_customdata = Mock(return_value="")
+
+ ph.osutil = mock_osutil
+ ph.protocol_util.osutil = mock_osutil
+ ph.protocol_util.get_protocol_by_file = MagicMock()
+ ph.protocol_util.get_protocol = MagicMock()
+
+ conf.get_dvd_mount_point = Mock(return_value=self.tmp_dir)
+ ovfenv_file = os.path.join(self.tmp_dir, OVF_FILE_NAME)
+ ovfenv_data = load_data("ovf-env.xml")
+ fileutil.write_file(ovfenv_file, ovfenv_data)
+
+ ph.run()
+
+ call1 = call("Provisioning succeeded", duration=ANY, is_success=True)
+ call2 = call(ANY, is_success=True, operation=WALAEventOperation.GuestState)
+ ph.report_event.assert_has_calls([call1, call2])
+
+ args, kwargs = ph.report_event.call_args_list[1]
+ guest_state_json = json.loads(args[0])
+ self.assertTrue(1 <= guest_state_json['cpu'])
+ self.assertTrue(1 <= guest_state_json['mem'])
+ self.assertEqual(socket.gethostname(), guest_state_json['hostname'])
+
+ @distros()
+ @patch(
+ 'azurelinuxagent.common.osutil.default.DefaultOSUtil.get_instance_id',
+ return_value='B9F3C233-9913-9F42-8EB3-BA656DF32502')
+ def test_provision_telemetry_fail(self, mock_util, distro_name,
+ distro_version,
+ distro_full_name):
+ """
+ Assert that the agent issues one telemetry message as part of a
+ failed provisioning.
+
+ 1. Provision
+ """
+ ph = get_provision_handler(distro_name, distro_version,
+ distro_full_name)
+ ph.report_event = MagicMock()
+ ph.reg_ssh_host_key = MagicMock(side_effect=ProvisionError(
+ "--unit-test--"))
+
+ mock_osutil = MagicMock()
+ mock_osutil.decode_customdata = Mock(return_value="")
+
+ ph.osutil = mock_osutil
+ ph.protocol_util.osutil = mock_osutil
+ ph.protocol_util.get_protocol_by_file = MagicMock()
+ ph.protocol_util.get_protocol = MagicMock()
+
+ conf.get_dvd_mount_point = Mock(return_value=self.tmp_dir)
+ ovfenv_file = os.path.join(self.tmp_dir, OVF_FILE_NAME)
+ ovfenv_data = load_data("ovf-env.xml")
+ fileutil.write_file(ovfenv_file, ovfenv_data)
+
+ ph.run()
+ ph.report_event.assert_called_once_with(
+ "[ProvisionError] --unit-test--")
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/protocol/mockwiredata.py b/tests/protocol/mockwiredata.py
index 5924719..db59ece 100644
--- a/tests/protocol/mockwiredata.py
+++ b/tests/protocol/mockwiredata.py
@@ -31,7 +31,7 @@ DATA_FILE = {
"ga_manifest" : "wire/ga_manifest.xml",
"trans_prv": "wire/trans_prv",
"trans_cert": "wire/trans_cert",
- "test_ext": "ext/sample_ext-1.2.0.zip"
+ "test_ext": "ext/sample_ext-1.3.0.zip"
}
DATA_FILE_NO_EXT = DATA_FILE.copy()
@@ -52,6 +52,9 @@ DATA_FILE_EXT_INTERNALVERSION["ext_conf"] = "wire/ext_conf_internalversion.xml"
DATA_FILE_EXT_AUTOUPGRADE_INTERNALVERSION = DATA_FILE.copy()
DATA_FILE_EXT_AUTOUPGRADE_INTERNALVERSION["ext_conf"] = "wire/ext_conf_autoupgrade_internalversion.xml"
+DATA_FILE_EXT_ROLLINGUPGRADE = DATA_FILE.copy()
+DATA_FILE_EXT_ROLLINGUPGRADE["ext_conf"] = "wire/ext_conf_upgradeguid.xml"
+
class WireProtocolData(object):
def __init__(self, data_files=DATA_FILE):
self.emulate_stale_goal_state = False
diff --git a/tests/protocol/test_metadata.py b/tests/protocol/test_metadata.py
index 5047b86..5f90f12 100644
--- a/tests/protocol/test_metadata.py
+++ b/tests/protocol/test_metadata.py
@@ -39,7 +39,7 @@ class TestMetadataProtocolGetters(AgentTestCase):
protocol.get_certs()
ext_handlers, etag = protocol.get_ext_handlers()
for ext_handler in ext_handlers.extHandlers:
- protocol.get_ext_handler_pkgs(ext_handler)
+ protocol.get_ext_handler_pkgs(ext_handler, etag)
def test_getters(self, *args):
test_data = MetadataProtocolData(DATA_FILE)
diff --git a/tests/protocol/test_wire.py b/tests/protocol/test_wire.py
index d19bab1..9e475ec 100644
--- a/tests/protocol/test_wire.py
+++ b/tests/protocol/test_wire.py
@@ -14,6 +14,9 @@
#
# Requires Python 2.4+ and Openssl 1.0+
#
+
+import glob
+
from azurelinuxagent.common import event
from azurelinuxagent.common.protocol.wire import *
from tests.protocol.mockwiredata import *
@@ -25,10 +28,10 @@ wireserver_url = '168.63.129.16'
@patch("time.sleep")
@patch("azurelinuxagent.common.protocol.wire.CryptUtil")
-class TestWireProtocolGetters(AgentTestCase):
+class TestWireProtocol(AgentTestCase):
def setUp(self):
- super(TestWireProtocolGetters, self).setUp()
+ super(TestWireProtocol, self).setUp()
HostPluginProtocol.set_default_channel(False)
def _test_getters(self, test_data, MockCryptUtil, _):
@@ -40,8 +43,9 @@ class TestWireProtocolGetters(AgentTestCase):
protocol.get_vminfo()
protocol.get_certs()
ext_handlers, etag = protocol.get_ext_handlers()
+ self.assertEqual("1", etag)
for ext_handler in ext_handlers.extHandlers:
- protocol.get_ext_handler_pkgs(ext_handler)
+ protocol.get_ext_handler_pkgs(ext_handler, etag)
crt1 = os.path.join(self.tmp_dir,
'33B0ABCE4673538650971C10F7D7397E71561F35.crt')
@@ -54,6 +58,8 @@ class TestWireProtocolGetters(AgentTestCase):
self.assertTrue(os.path.isfile(crt2))
self.assertTrue(os.path.isfile(prv2))
+ self.assertEqual("1", protocol.get_incarnation())
+
def test_getters(self, *args):
"""Normal case"""
test_data = WireProtocolData(DATA_FILE)
@@ -88,6 +94,7 @@ class TestWireProtocolGetters(AgentTestCase):
# HostingEnvironmentConfig, will be retrieved the expected number
self.assertEqual(2, test_data.call_counts["hostingenvuri"])
+
def test_call_storage_kwargs(self,
mock_cryptutil,
mock_sleep):
@@ -365,11 +372,14 @@ class TestWireProtocolGetters(AgentTestCase):
v1_ga_status = {
'version': str(CURRENT_VERSION),
'status': status,
- 'osversion': DISTRO_VERSION,
- 'osname': DISTRO_NAME,
- 'hostname': socket.gethostname(),
'formattedMessage': formatted_msg
}
+ v1_ga_guest_info = {
+ 'computerName': socket.gethostname(),
+ 'osName': DISTRO_NAME,
+ 'osVersion': DISTRO_VERSION,
+ 'version': str(CURRENT_VERSION),
+ }
v1_agg_status = {
'guestAgentStatus': v1_ga_status,
'handlerAggregateStatus': []
@@ -377,7 +387,8 @@ class TestWireProtocolGetters(AgentTestCase):
v1_vm_status = {
'version': '1.1',
'timestampUTC': timestamp,
- 'aggregateStatus': v1_agg_status
+ 'aggregateStatus': v1_agg_status,
+ 'guestOSInfo' : v1_ga_guest_info
}
self.assertEqual(json.dumps(v1_vm_status), actual.to_json())
@@ -390,5 +401,6 @@ class MockResponse:
def read(self):
return self.body
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_agent.py b/tests/test_agent.py
index 77be07a..2095db0 100644
--- a/tests/test_agent.py
+++ b/tests/test_agent.py
@@ -15,9 +15,7 @@
# Requires Python 2.4+ and Openssl 1.0+
#
-import mock
import os.path
-import sys
from azurelinuxagent.agent import *
from azurelinuxagent.common.conf import *
@@ -39,12 +37,13 @@ Logs.Verbose = False
OS.AllowHTTP = False
OS.CheckRdmaDriver = False
OS.EnableFIPS = True
-OS.EnableFirewall = True
+OS.EnableFirewall = False
OS.EnableRDMA = False
OS.HomeDir = /home
OS.OpensslPath = /usr/bin/openssl
OS.PasswordPath = /etc/shadow
OS.RootDeviceScsiTimeout = 300
+OS.SshClientAliveInterval = 42
OS.SshDir = /notareal/path
OS.SudoersDir = /etc/sudoers.d
OS.UpdateRdmaDriver = False
@@ -65,8 +64,7 @@ ResourceDisk.Filesystem = ext4
ResourceDisk.Format = True
ResourceDisk.MountOptions = None
ResourceDisk.MountPoint = /mnt/resource
-ResourceDisk.SwapSizeMB = 0
-""".split('\n')
+ResourceDisk.SwapSizeMB = 0""".split('\n')
class TestAgent(AgentTestCase):
@@ -160,10 +158,30 @@ class TestAgent(AgentTestCase):
self.assertFalse(os.path.isdir(ext_log_dir))
mock_log.assert_called_once()
- def test_agent_show_configuration(self):
- if not hasattr(sys.stdout, 'getvalue'):
- self.fail('Test requires at least Python 2.7 with buffered output')
- agent = Agent(False,
- conf_file_path=os.path.join(data_dir, "test_waagent.conf"))
- agent.show_configuration()
- self.assertEqual(EXPECTED_CONFIGURATION, sys.stdout.getvalue().split('\n'))
+ def test_agent_get_configuration(self):
+ Agent(False, conf_file_path=os.path.join(data_dir, "test_waagent.conf"))
+
+ actual_configuration = []
+ configuration = conf.get_configuration()
+ for k in sorted(configuration.keys()):
+ actual_configuration.append("{0} = {1}".format(k, configuration[k]))
+ self.assertEqual(EXPECTED_CONFIGURATION, actual_configuration)
+
+ def test_agent_usage_message(self):
+ message = usage()
+
+ # Python 2.6 does not have assertIn()
+ self.assertTrue("-verbose" in message)
+ self.assertTrue("-force" in message)
+ self.assertTrue("-help" in message)
+ self.assertTrue("-configuration-path" in message)
+ self.assertTrue("-deprovision" in message)
+ self.assertTrue("-register-service" in message)
+ self.assertTrue("-version" in message)
+ self.assertTrue("-daemon" in message)
+ self.assertTrue("-start" in message)
+ self.assertTrue("-run-exthandlers" in message)
+ self.assertTrue("-show-configuration" in message)
+
+ # sanity check
+ self.assertFalse("-not-a-valid-option" in message)
diff --git a/tests/tools.py b/tests/tools.py
index 94fab7f..5c65847 100644
--- a/tests/tools.py
+++ b/tests/tools.py
@@ -26,17 +26,20 @@ import tempfile
import unittest
from functools import wraps
+import time
+
import azurelinuxagent.common.event as event
import azurelinuxagent.common.conf as conf
import azurelinuxagent.common.logger as logger
+from azurelinuxagent.common.utils import fileutil
from azurelinuxagent.common.version import PY_VERSION_MAJOR
-#Import mock module for Python2 and Python3
+# Import mock module for Python2 and Python3
try:
- from unittest.mock import Mock, patch, MagicMock, DEFAULT, call
+ from unittest.mock import Mock, patch, MagicMock, DEFAULT, ANY, call
except ImportError:
- from mock import Mock, patch, MagicMock, DEFAULT, call
+ from mock import Mock, patch, MagicMock, DEFAULT, ANY, call
test_dir = os.path.dirname(os.path.abspath(__file__))
data_dir = os.path.join(test_dir, "data")
@@ -45,11 +48,12 @@ debug = False
if os.environ.get('DEBUG') == '1':
debug = True
-#Enable verbose logger to stdout
+# Enable verbose logger to stdout
if debug:
logger.add_logger_appender(logger.AppenderType.STDOUT,
logger.LogLevel.VERBOSE)
+
class AgentTestCase(unittest.TestCase):
def setUp(self):
prefix = "{0}_".format(self.__class__.__name__)
@@ -72,12 +76,20 @@ class AgentTestCase(unittest.TestCase):
if not debug and self.tmp_dir is not None:
shutil.rmtree(self.tmp_dir)
+ def _create_files(self, tmp_dir, prefix, suffix, count, with_sleep=0):
+ for i in range(count):
+ f = os.path.join(tmp_dir, '.'.join((prefix, str(i), suffix)))
+ fileutil.write_file(f, "faux content")
+ time.sleep(with_sleep)
+
+
def load_data(name):
"""Load test data"""
path = os.path.join(data_dir, name)
with open(path, "r") as data_file:
return data_file.read()
+
def load_bin_data(name):
"""Load test bin data"""
path = os.path.join(data_dir, name)
@@ -106,12 +118,14 @@ supported_distro = [
]
+
def open_patch():
open_name = '__builtin__.open'
if PY_VERSION_MAJOR == 3:
open_name = 'builtins.open'
return open_name
+
def distros(distro_name=".*", distro_version=".*", distro_full_name=".*"):
"""Run test on multiple distros"""
def decorator(test_method):
@@ -128,8 +142,8 @@ def distros(distro_name=".*", distro_version=".*", distro_full_name=".*"):
new_args.extend(args)
new_args.extend(distro)
test_method(self, *new_args, **kwargs)
- #Call tearDown and setUp to create seprated environment for
- #distro testing
+ # Call tearDown and setUp to create separated environment
+ # for distro testing
self.tearDown()
self.setUp()
return wrapper
diff --git a/tests/utils/test_flexible_version.py b/tests/utils/test_flexible_version.py
index 1162022..89a7dbf 100644
--- a/tests/utils/test_flexible_version.py
+++ b/tests/utils/test_flexible_version.py
@@ -405,6 +405,60 @@ class TestFlexibleVersion(unittest.TestCase):
self.assertEqual(test, str(FlexibleVersion(test)))
return
+ def test_creation_from_flexible_version(self):
+ tests = [
+ '1',
+ '1.2',
+ '1.2.3',
+ '1.2.3.4',
+ '1.2.3.4.5',
+
+ '1alpha',
+ '1.alpha',
+ '1-alpha',
+ '1alpha0',
+ '1.alpha0',
+ '1-alpha0',
+ '1.2alpha',
+ '1.2.alpha',
+ '1.2-alpha',
+ '1.2alpha0',
+ '1.2.alpha0',
+ '1.2-alpha0',
+
+ '1beta',
+ '1.beta',
+ '1-beta',
+ '1beta0',
+ '1.beta0',
+ '1-beta0',
+ '1.2beta',
+ '1.2.beta',
+ '1.2-beta',
+ '1.2beta0',
+ '1.2.beta0',
+ '1.2-beta0',
+
+ '1rc',
+ '1.rc',
+ '1-rc',
+ '1rc0',
+ '1.rc0',
+ '1-rc0',
+ '1.2rc',
+ '1.2.rc',
+ '1.2-rc',
+ '1.2rc0',
+ '1.2.rc0',
+ '1.2-rc0',
+
+ '1.2.3.4alpha5',
+ ]
+ for test in tests:
+ v = FlexibleVersion(test)
+ self.assertEqual(test, str(FlexibleVersion(v)))
+ return
+
def test_repr(self):
v = FlexibleVersion('1,2,3rc4', ',', ['lol', 'rc'])
expected = "FlexibleVersion ('1,2,3rc4', ',', ('lol', 'rc'))"
diff --git a/tests/utils/test_rest_util.py b/tests/utils/test_rest_util.py
index 52674da..bde0c3d 100644
--- a/tests/utils/test_rest_util.py
+++ b/tests/utils/test_rest_util.py
@@ -24,9 +24,70 @@ from azurelinuxagent.common.exception import HttpError, \
import azurelinuxagent.common.utils.restutil as restutil
from azurelinuxagent.common.future import httpclient, ustr
+
from tests.tools import *
+class TestIOErrorCounter(AgentTestCase):
+ def test_increment_hostplugin(self):
+ restutil.IOErrorCounter.reset()
+ restutil.IOErrorCounter.set_protocol_endpoint()
+
+ restutil.IOErrorCounter.increment(
+ restutil.DEFAULT_PROTOCOL_ENDPOINT, restutil.HOST_PLUGIN_PORT)
+
+ counts = restutil.IOErrorCounter.get_and_reset()
+ self.assertEqual(1, counts["hostplugin"])
+ self.assertEqual(0, counts["protocol"])
+ self.assertEqual(0, counts["other"])
+
+ def test_increment_protocol(self):
+ restutil.IOErrorCounter.reset()
+ restutil.IOErrorCounter.set_protocol_endpoint()
+
+ restutil.IOErrorCounter.increment(
+ restutil.DEFAULT_PROTOCOL_ENDPOINT, 80)
+
+ counts = restutil.IOErrorCounter.get_and_reset()
+ self.assertEqual(0, counts["hostplugin"])
+ self.assertEqual(1, counts["protocol"])
+ self.assertEqual(0, counts["other"])
+
+ def test_increment_other(self):
+ restutil.IOErrorCounter.reset()
+ restutil.IOErrorCounter.set_protocol_endpoint()
+
+ restutil.IOErrorCounter.increment(
+ '169.254.169.254', 80)
+
+ counts = restutil.IOErrorCounter.get_and_reset()
+ self.assertEqual(0, counts["hostplugin"])
+ self.assertEqual(0, counts["protocol"])
+ self.assertEqual(1, counts["other"])
+
+ def test_get_and_reset(self):
+ restutil.IOErrorCounter.reset()
+ restutil.IOErrorCounter.set_protocol_endpoint()
+
+ restutil.IOErrorCounter.increment(
+ restutil.DEFAULT_PROTOCOL_ENDPOINT, restutil.HOST_PLUGIN_PORT)
+ restutil.IOErrorCounter.increment(
+ restutil.DEFAULT_PROTOCOL_ENDPOINT, restutil.HOST_PLUGIN_PORT)
+ restutil.IOErrorCounter.increment(
+ restutil.DEFAULT_PROTOCOL_ENDPOINT, 80)
+ restutil.IOErrorCounter.increment(
+ '169.254.169.254', 80)
+ restutil.IOErrorCounter.increment(
+ '169.254.169.254', 80)
+
+ counts = restutil.IOErrorCounter.get_and_reset()
+ self.assertEqual(2, counts.get("hostplugin"))
+ self.assertEqual(1, counts.get("protocol"))
+ self.assertEqual(2, counts.get("other"))
+ self.assertEqual(
+ {"hostplugin":0, "protocol":0, "other":0},
+ restutil.IOErrorCounter._counts)
+
class TestHttpOperations(AgentTestCase):
def test_parse_url(self):
test_uri = "http://abc.def/ghi#hash?jkl=mn"
@@ -268,6 +329,68 @@ class TestHttpOperations(AgentTestCase):
@patch("time.sleep")
@patch("azurelinuxagent.common.utils.restutil._http_request")
+ def test_http_request_retries_with_fibonacci_delay(self, _http_request, _sleep):
+ # Ensure the code is not a throttle code
+ self.assertFalse(httpclient.BAD_GATEWAY in restutil.THROTTLE_CODES)
+
+ _http_request.side_effect = [
+ Mock(status=httpclient.BAD_GATEWAY)
+ for i in range(restutil.DEFAULT_RETRIES)
+ ] + [Mock(status=httpclient.OK)]
+
+ restutil.http_get("https://foo.bar",
+ max_retry=restutil.DEFAULT_RETRIES+1)
+
+ self.assertEqual(restutil.DEFAULT_RETRIES+1, _http_request.call_count)
+ self.assertEqual(restutil.DEFAULT_RETRIES, _sleep.call_count)
+ self.assertEqual(
+ [
+ call(restutil._compute_delay(i+1, restutil.DELAY_IN_SECONDS))
+ for i in range(restutil.DEFAULT_RETRIES)],
+ _sleep.call_args_list)
+
+ @patch("time.sleep")
+ @patch("azurelinuxagent.common.utils.restutil._http_request")
+ def test_http_request_retries_with_constant_delay_when_throttled(self, _http_request, _sleep):
+ # Ensure the code is a throttle code
+ self.assertTrue(httpclient.SERVICE_UNAVAILABLE in restutil.THROTTLE_CODES)
+
+ _http_request.side_effect = [
+ Mock(status=httpclient.SERVICE_UNAVAILABLE)
+ for i in range(restutil.DEFAULT_RETRIES)
+ ] + [Mock(status=httpclient.OK)]
+
+ restutil.http_get("https://foo.bar",
+ max_retry=restutil.DEFAULT_RETRIES+1)
+
+ self.assertEqual(restutil.DEFAULT_RETRIES+1, _http_request.call_count)
+ self.assertEqual(restutil.DEFAULT_RETRIES, _sleep.call_count)
+ self.assertEqual(
+ [call(1) for i in range(restutil.DEFAULT_RETRIES)],
+ _sleep.call_args_list)
+
+ @patch("time.sleep")
+ @patch("azurelinuxagent.common.utils.restutil._http_request")
+ def test_http_request_retries_for_safe_minimum_number_when_throttled(self, _http_request, _sleep):
+ # Ensure the code is a throttle code
+ self.assertTrue(httpclient.SERVICE_UNAVAILABLE in restutil.THROTTLE_CODES)
+
+ _http_request.side_effect = [
+ Mock(status=httpclient.SERVICE_UNAVAILABLE)
+ for i in range(restutil.THROTTLE_RETRIES-1)
+ ] + [Mock(status=httpclient.OK)]
+
+ restutil.http_get("https://foo.bar",
+ max_retry=1)
+
+ self.assertEqual(restutil.THROTTLE_RETRIES, _http_request.call_count)
+ self.assertEqual(restutil.THROTTLE_RETRIES-1, _sleep.call_count)
+ self.assertEqual(
+ [call(1) for i in range(restutil.THROTTLE_RETRIES-1)],
+ _sleep.call_args_list)
+
+ @patch("time.sleep")
+ @patch("azurelinuxagent.common.utils.restutil._http_request")
def test_http_request_raises_for_bad_request(self, _http_request, _sleep):
_http_request.side_effect = [
Mock(status=httpclient.BAD_REQUEST)