Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log collector should not fetch full goal state with extensions #2713

Merged
merged 29 commits into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
da72c37
Update version to dummy 1.0.0.0'
maddieford Nov 8, 2022
59dbd22
Revert version change
maddieford Nov 8, 2022
633a826
Merge remote-tracking branch 'upstream/develop' into develop
maddieford Nov 21, 2022
14a743f
Merge remote-tracking branch 'upstream/develop' into develop
maddieford Dec 8, 2022
55e261a
Update goalstate to take list of properties to process
maddieford Dec 8, 2022
908985e
Update protocol to not process extensions in goal state update
maddieford Dec 8, 2022
8fc6471
Update logcollector to not process extensions when updating goal state
maddieford Dec 8, 2022
3d370d0
Remove comments
maddieford Dec 8, 2022
d5bb016
Remove import enum
maddieford Dec 8, 2022
2633959
Update parameter name to goalstate_properties
maddieford Dec 8, 2022
b8d98a5
Add default value for goalstate_properties
maddieford Dec 8, 2022
e578129
Use integers and bitwise operations for goal state properties
maddieford Dec 9, 2022
c642900
Initialize protocol for logcollector with only necessary goal state p…
maddieford Dec 12, 2022
041dee3
Separate update into reset and made goal state properties a private m…
maddieford Dec 15, 2022
cbfb6ad
Remove goal_state_properties param from _detect_protocol
maddieford Dec 15, 2022
05f4b79
Update tests to remove force_update
maddieford Dec 15, 2022
dc2fe69
Remove unused import
maddieford Dec 15, 2022
a8c5a7f
Remove update_goal_State from wire protocol
maddieford Dec 15, 2022
011fbef
Remove update_goal_State from wire protocol
maddieford Dec 15, 2022
b833735
Separate extensionsconfig and certificates property
maddieford Dec 15, 2022
d762a03
Address PR comments
maddieford Dec 20, 2022
11f9c3a
Add flag to determine if goal state should be init
maddieford Dec 20, 2022
212a72b
Add test case for goal_state_properties
maddieford Dec 21, 2022
7805dc1
Correct pylint errors
maddieford Dec 21, 2022
b1d21ea
Add reset_goal_state test with goal_state_properties
maddieford Dec 21, 2022
c61a067
Add reset test cases
maddieford Dec 21, 2022
dd9d8da
Remove Certificates property in GoalState
maddieford Dec 22, 2022
7711ae2
Revert certs change
maddieford Dec 22, 2022
3926b35
Merge branch 'develop' into update_protocol_init
maddieford Jan 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions azurelinuxagent/common/logcollector.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

# Please note: be careful when adding agent dependencies in this module.
# This module uses its own logger and logs to its own file, not to the agent log.
from azurelinuxagent.common.protocol.goal_state import GoalStateProperties
from azurelinuxagent.common.protocol.util import get_protocol_util

_EXTENSION_LOG_DIR = get_ext_log_dir()
Expand Down Expand Up @@ -117,8 +118,11 @@ def _set_resource_usage_cgroups(cpu_cgroup_path, memory_cgroup_path):

@staticmethod
def _initialize_telemetry():
protocol = get_protocol_util().get_protocol()
protocol.client.update_goal_state(force_update=True)
goalstate_properties = GoalStateProperties.RoleConfig | GoalStateProperties.HostingEnv
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the naked eye, it looks like either of those but logically it's all of them :)

protocol = get_protocol_util().get_protocol(goalstate_properties=goalstate_properties)
protocol.client.update_goal_state(
goalstate_properties=goalstate_properties,
force_update=True)
# Initialize the common parameters for telemetry events
initialize_event_logger_vminfo_common_parameters(protocol)

Expand Down
97 changes: 61 additions & 36 deletions azurelinuxagent/common/protocol/goal_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@
_GET_GOAL_STATE_MAX_ATTEMPTS = 6


class GoalStateProperties(object):
"""
Enum for defining the properties that we fetch in the goal state
"""
RoleConfig = 1
HostingEnv = 2
SharedConfig = 4
ExtensionsConfig_Certs = 8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically the extensionconfig and certs are two separate properties even though both needs to be fetched and parsed inherently for extension run. So, separating those makes things clear

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to separate the two

RemoteAccessInfo = 16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: using hex instead of decimal can help visualize exactly which bit is being used for the value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated


@staticmethod
def default_properties():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is OK, though "default" in the name doesn't quite fit.

An alternative is to add an "All" item to the enum.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to add All item

return GoalStateProperties.RoleConfig | GoalStateProperties.HostingEnv | GoalStateProperties.SharedConfig | \
GoalStateProperties.ExtensionsConfig_Certs | GoalStateProperties.RemoteAccessInfo


class GoalStateInconsistentError(ProtocolError):
"""
Indicates an inconsistency in the goal state (e.g. missing tenant certificate)
Expand All @@ -57,7 +73,7 @@ def __init__(self, msg, inner=None):


class GoalState(object):
def __init__(self, wire_client, silent=False):
def __init__(self, wire_client, goalstate_properties=GoalStateProperties.default_properties(), silent=False):
"""
Fetches the goal state using the given wire client.

Expand Down Expand Up @@ -85,7 +101,7 @@ def __init__(self, wire_client, silent=False):
self._certs = EmptyCertificates()
self._remote_access = None

self.update(silent=silent)
self.update(goalstate_properties=goalstate_properties, silent=silent)

except ProtocolError:
raise
Expand Down Expand Up @@ -160,20 +176,20 @@ def update_host_plugin_headers(wire_client):
# Fetching the goal state updates the HostGAPlugin so simply trigger the request
GoalState._fetch_goal_state(wire_client)

def update(self, silent=False):
def update(self, goalstate_properties=GoalStateProperties.default_properties(), silent=False):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The update method should probably not take the properties as parameter, since it opens the possibility of passing a value different to the one passed to init() and that would lead to an inconsistent goal state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been removed

"""
Updates the current GoalState instance fetching values from the WireServer/HostGAPlugin as needed
"""
self.logger.silent = silent

try:
self._update(force_update=False)
self._update(goalstate_properties=goalstate_properties, force_update=False)
except GoalStateInconsistentError as e:
self.logger.warn("Detected an inconsistency in the goal state: {0}", ustr(e))
self._update(force_update=True)
self._update(goalstate_properties=goalstate_properties, force_update=True)
self.logger.info("The goal state is consistent")

def _update(self, force_update):
def _update(self, goalstate_properties, force_update):
#
# Fetch the goal state from both the HGAP and the WireServer
#
Expand Down Expand Up @@ -226,7 +242,7 @@ def _update(self, force_update):
#
extensions_config = None
if goal_state_updated:
extensions_config = self._fetch_full_wire_server_goal_state(incarnation, xml_doc)
extensions_config = self._fetch_full_wire_server_goal_state(incarnation, xml_doc, goalstate_properties)

#
# Lastly, decide whether to use the vmSettings or extensionsConfig for the extensions goal state
Expand Down Expand Up @@ -343,7 +359,7 @@ def _fetch_vm_settings(wire_client, force_update=False):

return vm_settings, vm_settings_updated

def _fetch_full_wire_server_goal_state(self, incarnation, xml_doc):
def _fetch_full_wire_server_goal_state(self, incarnation, xml_doc, goalstate_properties=GoalStateProperties.default_properties()):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method looks like internal to Goalstate and only called in _update. So I don't see the need for default there as parameter needs to come from parent methods.

I see samething few other places too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed goal_state_properties parameter for this method and created private member for the goal state class

"""
Issues HTTP requests (to the WireServer) for each of the URIs in the goal state (ExtensionsConfig, Certificate, Remote Access users, etc)
and populates the corresponding properties.
Expand All @@ -356,40 +372,48 @@ def _fetch_full_wire_server_goal_state(self, incarnation, xml_doc):
self.logger.info(message)
add_event(op=WALAEventOperation.GoalState, message=message)

role_instance = find(xml_doc, "RoleInstance")
role_instance_id = findtext(role_instance, "InstanceId")
role_config = find(role_instance, "Configuration")
role_config_name = findtext(role_config, "ConfigName")
container = find(xml_doc, "Container")
container_id = findtext(container, "ContainerId")
role_instance_id = None
role_config_name = None
container_id = None
if GoalStateProperties.RoleConfig & goalstate_properties:
role_instance = find(xml_doc, "RoleInstance")
role_instance_id = findtext(role_instance, "InstanceId")
role_config = find(role_instance, "Configuration")
role_config_name = findtext(role_config, "ConfigName")
container = find(xml_doc, "Container")
container_id = findtext(container, "ContainerId")

extensions_config_uri = findtext(xml_doc, "ExtensionsConfig")
if extensions_config_uri is None:
if not (GoalStateProperties.ExtensionsConfig_Certs & goalstate_properties) or extensions_config_uri is None:
extensions_config = ExtensionsGoalStateFactory.create_empty(incarnation)
else:
xml_text = self._wire_client.fetch_config(extensions_config_uri, self._wire_client.get_header())
extensions_config = ExtensionsGoalStateFactory.create_from_extensions_config(incarnation, xml_text, self._wire_client)
self._history.save_extensions_config(extensions_config.get_redacted_text())

hosting_env_uri = findtext(xml_doc, "HostingEnvironmentConfig")
xml_text = self._wire_client.fetch_config(hosting_env_uri, self._wire_client.get_header())
hosting_env = HostingEnv(xml_text)
self._history.save_hosting_env(xml_text)

shared_conf_uri = findtext(xml_doc, "SharedConfig")
xml_text = self._wire_client.fetch_config(shared_conf_uri, self._wire_client.get_header())
shared_config = SharedConfig(xml_text)
self._history.save_shared_conf(xml_text)
# SharedConfig.xml is used by other components (Azsec and Singularity/HPC Infiniband), so save it to the agent's root directory as well
shared_config_file = os.path.join(conf.get_lib_dir(), SHARED_CONF_FILE_NAME)
try:
fileutil.write_file(shared_config_file, xml_text)
except Exception as e:
logger.warn("Failed to save {0}: {1}".format(shared_config, e))
hosting_env = None
if GoalStateProperties.HostingEnv & goalstate_properties:
hosting_env_uri = findtext(xml_doc, "HostingEnvironmentConfig")
xml_text = self._wire_client.fetch_config(hosting_env_uri, self._wire_client.get_header())
hosting_env = HostingEnv(xml_text)
self._history.save_hosting_env(xml_text)

shared_config = None
if GoalStateProperties.SharedConfig & goalstate_properties:
shared_conf_uri = findtext(xml_doc, "SharedConfig")
xml_text = self._wire_client.fetch_config(shared_conf_uri, self._wire_client.get_header())
shared_config = SharedConfig(xml_text)
self._history.save_shared_conf(xml_text)
# SharedConfig.xml is used by other components (Azsec and Singularity/HPC Infiniband), so save it to the agent's root directory as well
shared_config_file = os.path.join(conf.get_lib_dir(), SHARED_CONF_FILE_NAME)
try:
fileutil.write_file(shared_config_file, xml_text)
except Exception as e:
logger.warn("Failed to save {0}: {1}".format(shared_config, e))

certs = EmptyCertificates()
certs_uri = findtext(xml_doc, "Certificates")
if certs_uri is not None:
if (GoalStateProperties.ExtensionsConfig_Certs & goalstate_properties) and certs_uri is not None:
xml_text = self._wire_client.fetch_config(certs_uri, self._wire_client.get_header_for_cert())
certs = Certificates(xml_text, self.logger)
# Log and save the certificates summary (i.e. the thumbprint but not the certificate itself) to the goal state history
Expand All @@ -403,11 +427,12 @@ def _fetch_full_wire_server_goal_state(self, incarnation, xml_doc):
self._history.save_certificates(json.dumps(certs.summary))

remote_access = None
remote_access_uri = findtext(container, "RemoteAccessInfo")
if remote_access_uri is not None:
xml_text = self._wire_client.fetch_config(remote_access_uri, self._wire_client.get_header_for_cert())
remote_access = RemoteAccess(xml_text)
self._history.save_remote_access(xml_text)
if GoalStateProperties.RemoteAccessInfo & goalstate_properties:
remote_access_uri = findtext(container, "RemoteAccessInfo")
if remote_access_uri is not None:
xml_text = self._wire_client.fetch_config(remote_access_uri, self._wire_client.get_header_for_cert())
remote_access = RemoteAccess(xml_text)
self._history.save_remote_access(xml_text)

self._incarnation = incarnation
self._role_instance_id = role_instance_id
Expand Down
9 changes: 5 additions & 4 deletions azurelinuxagent/common/protocol/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import azurelinuxagent.common.conf as conf
import azurelinuxagent.common.logger as logger
import azurelinuxagent.common.utils.fileutil as fileutil
from azurelinuxagent.common.protocol.goal_state import GoalStateProperties
from azurelinuxagent.common.singletonperthread import SingletonPerThread

from azurelinuxagent.common.exception import ProtocolError, OSUtilError, \
Expand Down Expand Up @@ -188,7 +189,7 @@ def _clear_wireserver_endpoint(self):
return
logger.error("Failed to clear wiresever endpoint: {0}", e)

def _detect_protocol(self):
def _detect_protocol(self, goalstate_properties=GoalStateProperties.default_properties()):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing on default paramter

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed goal_state_properties from detect

"""
Probe protocol endpoints in turn.
"""
Expand Down Expand Up @@ -217,7 +218,7 @@ def _detect_protocol(self):

try:
protocol = WireProtocol(endpoint)
protocol.detect()
protocol.detect(goalstate_properties=goalstate_properties)
self._set_wireserver_endpoint(endpoint)
return protocol

Expand Down Expand Up @@ -268,7 +269,7 @@ def clear_protocol(self):
finally:
self._lock.release()

def get_protocol(self):
def get_protocol(self, goalstate_properties=GoalStateProperties.default_properties()):
"""
Detect protocol by endpoint.
:returns: protocol instance
Expand Down Expand Up @@ -296,7 +297,7 @@ def get_protocol(self):

logger.info("Detect protocol endpoint")

protocol = self._detect_protocol()
protocol = self._detect_protocol(goalstate_properties=goalstate_properties)

IOErrorCounter.set_protocol_endpoint(endpoint=protocol.get_endpoint())
self._save_protocol(WIRE_PROTOCOL_NAME)
Expand Down
13 changes: 7 additions & 6 deletions azurelinuxagent/common/protocol/wire.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
from azurelinuxagent.common.exception import ProtocolNotFoundError, \
ResourceGoneError, ExtensionDownloadError, InvalidContainerError, ProtocolError, HttpError, ExtensionErrorCodes
from azurelinuxagent.common.future import httpclient, bytebuffer, ustr
from azurelinuxagent.common.protocol.goal_state import GoalState, TRANSPORT_CERT_FILE_NAME, TRANSPORT_PRV_FILE_NAME
from azurelinuxagent.common.protocol.goal_state import GoalState, TRANSPORT_CERT_FILE_NAME, TRANSPORT_PRV_FILE_NAME, \
GoalStateProperties
from azurelinuxagent.common.protocol.hostplugin import HostPluginProtocol
from azurelinuxagent.common.protocol.restapi import DataContract, ProvisionStatus, VMInfo, VMStatus
from azurelinuxagent.common.telemetryevent import GuestAgentExtensionEventsSchema
Expand Down Expand Up @@ -72,7 +73,7 @@ def __init__(self, endpoint):
raise ProtocolError("WireProtocol endpoint is None")
self.client = WireClient(endpoint)

def detect(self):
def detect(self, goalstate_properties=GoalStateProperties.default_properties()):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to expose the properties in this method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been removed

self.client.check_wire_protocol_version()

trans_prv_file = os.path.join(conf.get_lib_dir(),
Expand All @@ -84,7 +85,7 @@ def detect(self):

# Initialize the goal state, including all the inner properties
logger.info('Initializing goal state during protocol detection')
self.client.update_goal_state(force_update=True)
self.client.update_goal_state(goalstate_properties=goalstate_properties, force_update=True)

def update_goal_state(self, silent=False):
self.client.update_goal_state(silent=silent)
Expand Down Expand Up @@ -778,7 +779,7 @@ def update_host_plugin(self, container_id, role_config_name):
self._host_plugin.update_container_id(container_id)
self._host_plugin.update_role_config_name(role_config_name)

def update_goal_state(self, force_update=False, silent=False):
def update_goal_state(self, goalstate_properties=GoalStateProperties.default_properties(), force_update=False, silent=False):
"""
Updates the goal state if the incarnation or etag changed or if 'force_update' is True
"""
Expand All @@ -787,9 +788,9 @@ def update_goal_state(self, force_update=False, silent=False):
logger.info("Forcing an update of the goal state.")

if self._goal_state is None or force_update:
self._goal_state = GoalState(self, silent=silent)
self._goal_state = GoalState(self, goalstate_properties=goalstate_properties, silent=silent)
else:
self._goal_state.update(silent=silent)
self._goal_state.update(goalstate_properties=goalstate_properties, silent=silent)

except ProtocolError:
raise
Expand Down