Skip to content

Commit

Permalink
Merge branch 'develop' into agent-manifest-window
Browse files Browse the repository at this point in the history
  • Loading branch information
nagworld9 authored Jun 30, 2023
2 parents d02c4fb + 2ddd736 commit a1684f5
Show file tree
Hide file tree
Showing 26 changed files with 523 additions and 132 deletions.
1 change: 1 addition & 0 deletions azurelinuxagent/common/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class WALAEventOperation:
InitializeHostPlugin = "InitializeHostPlugin"
Log = "Log"
LogCollection = "LogCollection"
NoExec = "NoExec"
OSInfo = "OSInfo"
Partition = "Partition"
PersistFirewallRules = "PersistFirewallRules"
Expand Down
54 changes: 51 additions & 3 deletions azurelinuxagent/common/utils/extensionprocessutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
import signal
import time

from azurelinuxagent.common import conf
from azurelinuxagent.common import logger
from azurelinuxagent.common.event import WALAEventOperation, add_event
from azurelinuxagent.common.exception import ExtensionErrorCodes, ExtensionOperationError, ExtensionError
from azurelinuxagent.common.future import ustr

Expand Down Expand Up @@ -74,20 +76,66 @@ def handle_process_completion(process, command, timeout, stdout, stderr, error_c
process_output = read_output(stdout, stderr)

if timed_out:
if cpu_cgroup is not None:# Report CPUThrottledTime when timeout happens
if cpu_cgroup is not None: # Report CPUThrottledTime when timeout happens
raise ExtensionError("Timeout({0});CPUThrottledTime({1}secs): {2}\n{3}".format(timeout, throttled_time, command, process_output),
code=ExtensionErrorCodes.PluginHandlerScriptTimedout)

raise ExtensionError("Timeout({0}): {1}\n{2}".format(timeout, command, process_output),
code=ExtensionErrorCodes.PluginHandlerScriptTimedout)

if return_code != 0:
raise ExtensionOperationError("Non-zero exit code: {0}, {1}\n{2}".format(return_code, command, process_output),
code=error_code, exit_code=return_code)
noexec_warning = ""
if return_code == 126: # Permission denied
noexec_path = _check_noexec()
if noexec_path is not None:
noexec_warning = "\nWARNING: {0} is mounted with the noexec flag, which can prevent execution of VM Extensions.".format(noexec_path)
raise ExtensionOperationError(
"Non-zero exit code: {0}, {1}{2}\n{3}".format(return_code, command, noexec_warning, process_output),
code=error_code,
exit_code=return_code)

return process_output


#
# Collect a sample of errors while checking for the noexec flag. Consider removing this telemetry after a few releases.
#
_COLLECT_NOEXEC_ERRORS = True


def _check_noexec():
"""
Check if /var is mounted with the noexec flag.
"""
try:
agent_dir = conf.get_lib_dir()
with open('/proc/mounts', 'r') as f:
while True:
line = f.readline()
if line == "": # EOF
break
# The mount point is on the second column, and the flags are on the fourth. e.g.
#
# # grep /var /proc/mounts
# /dev/mapper/rootvg-varlv /var xfs rw,seclabel,noexec,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0
#
columns = line.split()
mount_point = columns[1]
flags = columns[3]
if agent_dir.startswith(mount_point) and "noexec" in flags:
message = "The noexec flag is set on {0}. This can prevent extensions from executing.".format(mount_point)
logger.warn(message)
add_event(op=WALAEventOperation.NoExec, is_success=False, message=message)
return mount_point
except Exception as e:
message = "Error while checking the noexec flag: {0}".format(e)
logger.warn(message)
if _COLLECT_NOEXEC_ERRORS:
_COLLECT_NOEXEC_ERRORS = False
add_event(op=WALAEventOperation.NoExec, is_success=False, log_event=False, message="Error while checking the noexec flag: {0}".format(e))
return None


SAS_TOKEN_RE = re.compile(r'(https://\S+\?)((sv|st|se|sr|sp|sip|spr|sig)=\S+)+', flags=re.IGNORECASE)


Expand Down
16 changes: 13 additions & 3 deletions azurelinuxagent/common/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import sys

import azurelinuxagent.common.conf as conf
from azurelinuxagent.common import logger
import azurelinuxagent.common.utils.shellutil as shellutil
from azurelinuxagent.common.utils.flexible_version import FlexibleVersion
from azurelinuxagent.common.future import ustr, get_linux_distribution
Expand Down Expand Up @@ -48,12 +49,21 @@ def get_daemon_version():
The value indicates the version of the daemon that started the current agent process or, if the current
process is the daemon, the version of the current process.
If the variable is not set (because the agent is < 2.2.53, or the process was not started by the daemon and
the process is not the daemon itself) the function returns "0.0.0.0"
the process is not the daemon itself) the function returns version of agent which started by the python
"""
if __DAEMON_VERSION_ENV_VARIABLE in os.environ:
return FlexibleVersion(os.environ[__DAEMON_VERSION_ENV_VARIABLE])
return FlexibleVersion("0.0.0.0")

else:
# The agent process which execute the extensions can have different version(after upgrades) and importing version from that process may provide wrong version for daemon.
# so launching new process with sys.executable python provides the correct version for daemon which preinstalled in the image.
try:
cmd = ["{0}".format(sys.executable), "-c", "\'from azurelinuxagent.common.version import AGENT_VERSION; print(AGENT_VERSION)\'"]
version = shellutil.run_command(cmd)
return FlexibleVersion(version)
except Exception as e: # Make the best effort to get the daemon version, but don't fail the update if we can't. So default to 2.2.53 as env variable is not set < 2.2.53
logger.warn("Failed to get the daemon version: {0}", ustr(e))
return FlexibleVersion("2.2.53")


def get_f5_platform():
"""
Expand Down
15 changes: 3 additions & 12 deletions azurelinuxagent/ga/agent_update_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from azurelinuxagent.common.future import ustr
from azurelinuxagent.common.logger import LogLevel
from azurelinuxagent.common.protocol.extensions_goal_state import GoalStateSource
from azurelinuxagent.common.protocol.restapi import VERSION_0, VMAgentUpdateStatuses, VMAgentUpdateStatus
from azurelinuxagent.common.protocol.restapi import VMAgentUpdateStatuses, VMAgentUpdateStatus
from azurelinuxagent.common.utils import fileutil, textutil
from azurelinuxagent.common.utils.flexible_version import FlexibleVersion
from azurelinuxagent.common.version import get_daemon_version, CURRENT_VERSION, AGENT_NAME, AGENT_DIR_PATTERN
Expand Down Expand Up @@ -249,7 +249,7 @@ def __proceed_with_update(self, requested_version):
# In case of an upgrade, we don't need to exclude anything as the daemon will automatically
# start the next available highest version which would be the target version
prefix = "upgrade"
raise AgentUpgradeExitException("Agent update found, Exiting current process to {0} to the new Agent version {1}".format(prefix, requested_version))
raise AgentUpgradeExitException("Agent update found, exiting current process to {0} to the new Agent version {1}".format(prefix, requested_version))

@staticmethod
def __get_available_agents_on_disk():
Expand All @@ -261,15 +261,6 @@ def __get_all_agents_on_disk():
path = os.path.join(conf.get_lib_dir(), "{0}-*".format(AGENT_NAME))
return [GuestAgent.from_installed_agent(path=agent_dir) for agent_dir in glob.iglob(path) if os.path.isdir(agent_dir)]

@staticmethod
def __get_daemon_version_for_update():
daemon_version = get_daemon_version()
if daemon_version != FlexibleVersion(VERSION_0):
return daemon_version
# We return 0.0.0.0 if daemon version is not specified. In that case,
# use the min version as 2.2.53 as we started setting the daemon version starting 2.2.53.
return FlexibleVersion("2.2.53")

@staticmethod
def __log_event(level, msg, success=True):
if level == LogLevel.INFO:
Expand Down Expand Up @@ -320,7 +311,7 @@ def run(self, goal_state):
self.__log_event(LogLevel.WARNING, warn_msg)

try:
daemon_version = self.__get_daemon_version_for_update()
daemon_version = get_daemon_version()
if requested_version < daemon_version:
# Don't process the update if the requested version is less than daemon version,
# as historically we don't support downgrades below daemon versions. So daemon will not pickup that requested version rather start with
Expand Down
11 changes: 6 additions & 5 deletions tests/common/test_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,12 @@ def test_get_daemon_version_should_return_the_version_that_was_previously_set(se
finally:
os.environ.pop(DAEMON_VERSION_ENV_VARIABLE)

def test_get_daemon_version_should_return_zero_when_the_version_has_not_been_set(self):
self.assertEqual(
FlexibleVersion("0.0.0.0"), get_daemon_version(),
"The daemon version should not be defined. Environment={0}".format(os.environ)
)
def test_get_daemon_version_from_fallback_when_the_version_has_not_been_set(self):
with patch("azurelinuxagent.common.utils.shellutil.run_command", return_value=FlexibleVersion("2.2.53")):
self.assertEqual(
FlexibleVersion("2.2.53"), get_daemon_version(),
"The daemon version should not be defined. Environment={0}".format(os.environ)
)


class TestCurrentAgentName(AgentTestCase):
Expand Down
10 changes: 5 additions & 5 deletions tests/ga/test_agent_update_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def test_it_should_update_to_largest_version_if_ga_versioning_disabled(self):
agent_update_handler.run(agent_update_handler._protocol.get_goal_state())
self.__assert_agent_requested_version_in_goal_state(mock_telemetry, inc=2, version="99999.0.0.0")
self.__assert_agent_directories_exist_and_others_dont_exist(versions=[str(CURRENT_VERSION), "99999.0.0.0"])
self.assertIn("Agent update found, Exiting current process", ustr(context.exception.reason))
self.assertIn("Agent update found, exiting current process", ustr(context.exception.reason))

def test_it_should_update_to_largest_version_if_time_window_not_elapsed(self):
self.prepare_agents(count=1)
Expand Down Expand Up @@ -146,7 +146,7 @@ def test_it_should_update_to_largest_version_if_time_window_elapsed(self):
agent_update_handler.run(agent_update_handler._protocol.get_goal_state())
self.__assert_agent_requested_version_in_goal_state(mock_telemetry, inc=2, version="99999.0.0.0")
self.__assert_agent_directories_exist_and_others_dont_exist(versions=[str(CURRENT_VERSION), "99999.0.0.0"])
self.assertIn("Agent update found, Exiting current process", ustr(context.exception.reason))
self.assertIn("Agent update found, exiting current process", ustr(context.exception.reason))

def test_it_should_not_agent_update_if_last_attempted_update_time_not_elapsed(self):
self.prepare_agents(count=1)
Expand Down Expand Up @@ -176,7 +176,7 @@ def test_it_should_update_to_largest_version_if_requested_version_not_available(
agent_update_handler.run(agent_update_handler._protocol.get_goal_state())
self.__assert_agent_requested_version_in_goal_state(mock_telemetry, inc=2, version="99999.0.0.0")
self.__assert_agent_directories_exist_and_others_dont_exist(versions=[str(CURRENT_VERSION), "99999.0.0.0"])
self.assertIn("Agent update found, Exiting current process", ustr(context.exception.reason))
self.assertIn("Agent update found, exiting current process", ustr(context.exception.reason))

def test_it_should_not_download_manifest_again_if_last_attempted_download_time_not_elapsed(self):
self.prepare_agents(count=1)
Expand Down Expand Up @@ -238,7 +238,7 @@ def test_it_should_upgrade_agent_if_requested_version_is_available_greater_than_
agent_update_handler.run(agent_update_handler._protocol.get_goal_state())
self.__assert_agent_requested_version_in_goal_state(mock_telemetry, version="9.9.9.10")
self.__assert_agent_directories_exist_and_others_dont_exist(versions=["9.9.9.10", str(CURRENT_VERSION)])
self.assertIn("Agent update found, Exiting current process", ustr(context.exception.reason))
self.assertIn("Agent update found, exiting current process", ustr(context.exception.reason))

def test_it_should_downgrade_agent_if_requested_version_is_available_less_than_current_version(self):
data_file = DATA_FILE.copy()
Expand All @@ -259,7 +259,7 @@ def test_it_should_downgrade_agent_if_requested_version_is_available_less_than_c
self.__assert_agent_requested_version_in_goal_state(mock_telemetry, inc=2, version=downgraded_version)
self.__assert_agent_directories_exist_and_others_dont_exist(
versions=[downgraded_version, str(CURRENT_VERSION)])
self.assertIn("Agent update found, Exiting current process", ustr(context.exception.reason))
self.assertIn("Agent update found, exiting current process", ustr(context.exception.reason))

def test_it_should_not_downgrade_below_daemon_version(self):
data_file = DATA_FILE.copy()
Expand Down
3 changes: 2 additions & 1 deletion tests/ga/test_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -3209,7 +3209,8 @@ def tearDown(self):
AgentTestCase.tearDown(self)

@patch('time.gmtime', MagicMock(return_value=time.gmtime(0)))
def test_ext_handler_reporting_status_file(self):
@patch("azurelinuxagent.common.version.get_daemon_version", return_value=FlexibleVersion("0.0.0.0"))
def test_ext_handler_reporting_status_file(self, _):
with mock_wire_protocol(mockwiredata.DATA_FILE) as protocol:

def mock_http_put(url, *args, **_):
Expand Down
Loading

0 comments on commit a1684f5

Please sign in to comment.