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

Add x-ms-verify-from-artifacts-blob to extensionsArtifact calls #2655

Merged
merged 4 commits into from
Sep 3, 2022

Conversation

narrieta
Copy link
Member

The HostGAPlugin requires the x-ms-verify-from-artifacts-blob header in extensionsArtifact requests for Fast Track Goal states.

This PR adds that header. Most of the changes in the PR are code refactoring that makes this simple, plus some refactoring that has been delayed in previous changes, as well as a few changes for Python 3.10

@@ -17,7 +17,7 @@
# Requires Python 2.6+ and Openssl 1.0+
import datetime

import azurelinuxagent.common.logger as logger
from azurelinuxagent.common import logger
Copy link
Member Author

Choose a reason for hiding this comment

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

These changes in import statements address new pylint warnings for Python 3.10, there are several of them in the PR

@codecov
Copy link

codecov bot commented Aug 19, 2022

Codecov Report

Merging #2655 (8d86688) into develop (51b27a1) will decrease coverage by 0.00%.
The diff coverage is 94.11%.

@@             Coverage Diff             @@
##           develop    #2655      +/-   ##
===========================================
- Coverage    71.97%   71.96%   -0.01%     
===========================================
  Files          103      103              
  Lines        15688    15679       -9     
  Branches      2485     2486       +1     
===========================================
- Hits         11291    11283       -8     
+ Misses        3881     3879       -2     
- Partials       516      517       +1     
Impacted Files Coverage Δ
azurelinuxagent/common/protocol/hostplugin.py 87.23% <66.66%> (-0.44%) ⬇️
azurelinuxagent/common/protocol/goal_state.py 94.58% <89.36%> (-0.74%) ⬇️
...inuxagent/common/protocol/extensions_goal_state.py 74.46% <100.00%> (ø)
...ol/extensions_goal_state_from_extensions_config.py 92.05% <100.00%> (ø)
...protocol/extensions_goal_state_from_vm_settings.py 83.45% <100.00%> (ø)
azurelinuxagent/common/protocol/restapi.py 97.50% <100.00%> (ø)
azurelinuxagent/common/protocol/wire.py 77.83% <100.00%> (-0.71%) ⬇️
azurelinuxagent/common/utils/archive.py 81.92% <100.00%> (+0.20%) ⬆️
azurelinuxagent/ga/exthandlers.py 85.70% <100.00%> (+0.01%) ⬆️
azurelinuxagent/ga/update.py 89.34% <100.00%> (ø)

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@@ -126,7 +126,7 @@ def on_hold(self):
raise NotImplementedError()

@property
def agent_manifests(self):
def agent_families(self):
Copy link
Member Author

Choose a reason for hiding this comment

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

There was a class in restapi.py named VMAgentManifest which did not represent a manifest, but rather the GAFamilies element in ExtensionConfigs (which includes the name of the family (Prod vs Test), the required agent version, and the URIs of the actual manifests). This naming made the code extremely confusing, especially in code that deals with VMAgentManifest and actual agent manifest. I renamed VMAgentManifest to VMAgentFamily so you will see those renames throughout this PR.

class VMAgentManifest(object):
def __init__(self, family, version=None):
self.family = family
class VMAgentFamily(object):
Copy link
Member Author

Choose a reason for hiding this comment

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

I renamed VMAgentManifest to VMAgentFamily because this class does not represent a manifest, but rather the GAFamilies element in ExtensionConfigs (which includes the name of the family (Prod vs Test), the required agent version, and the URIs of the actual manifests). This naming made the code extremely confusing, especially in code that deals with VMAgentManifest and actual agent manifest. You will see those renames throughout this PR.

@@ -129,6 +129,36 @@ def shared_conf(self):
def remote_access(self):
return self._remote_access

def fetch_agent_manifest(self, family_name, uris):
Copy link
Member Author

Choose a reason for hiding this comment

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

The extensionsArtifact API requires the use of the x-ms-verify-from-artifacts-blob header for FastTrack goal states. I added these convenience functions to the GoalState class to make this easier.

@@ -562,3 +592,46 @@ def _parse_user(user):
remote_access_user = RemoteAccessUser(name, encrypted_password, expiration)
return remote_access_user


Copy link
Member Author

Choose a reason for hiding this comment

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

This class was moved here from wire.py

@@ -111,22 +109,6 @@ def get_certs(self):
certificates = self.client.get_certs()
return certificates.cert_list

def get_vmagent_manifests(self):
Copy link
Member Author

Choose a reason for hiding this comment

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

WireClient maintains a reference to the goal state (returned by get_goal_state). WireClient is held within a WireProtocol, which is returned by ProtocolUtil, which is a singleton. Basically, this makes the goal state a global variable, and it has introduced threading issues in the past. We have been removing the use of WireClient.get_goal_state() over time. This PR presented an opportunity to remove a couple of them.

@@ -55,6 +55,7 @@
_HEADER_HOST_CONFIG_NAME = "x-ms-host-config-name"
_HEADER_ARTIFACT_LOCATION = "x-ms-artifact-location"
_HEADER_ARTIFACT_MANIFEST_LOCATION = "x-ms-artifact-manifest-location"
_HEADER_VERIFY_FROM_ARTIFACTS_BLOB = "x-ms-verify-from-artifacts-blob"
Copy link
Member Author

@narrieta narrieta Aug 19, 2022

Choose a reason for hiding this comment

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

This is the header required by the HostGAPlugin for Fast Track goal states. It's value is arbitrary and it is set to "true", but the HostGAPlugin just checks for its presence, not its value.

@@ -808,30 +790,11 @@ def get_certs(self):
raise ProtocolError("Trying to fetch Certificates before initialization!")
return self._goal_state.certs

def get_ext_manifest(self, ext_handler):
Copy link
Member Author

Choose a reason for hiding this comment

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

Move to the GoalState class

@@ -1186,48 +1149,6 @@ def get_supported(self):
return self.supported


class ExtensionManifest(object):
Copy link
Member Author

Choose a reason for hiding this comment

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

moved to goal_state.py

@@ -306,3 +306,6 @@ def save_hosting_env(self, text):

def save_shared_conf(self, text):
self.save(text, SHARED_CONF_FILE_NAME)

Copy link
Member Author

Choose a reason for hiding this comment

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

Convenience function to save manifests to the history folder

@@ -949,9 +949,9 @@ def get_ext_handlers_status_debug_info(self, vm_status):
if status_blob_text is None:
status_blob_text = ""

support_multi_config = dict()
support_multi_config = {}
Copy link
Member Author

Choose a reason for hiding this comment

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

pylint warning about dict() vs {}

@@ -1259,7 +1260,7 @@ def download(self):
self.logger.info("The existing extension package is invalid, will ignore it.")

if not package_exists:
self.protocol.client.download_extension(self.pkg.uris, destination, on_downloaded=lambda: self._unzip_extension_package(destination, self.get_base_dir()))
self.protocol.get_goal_state().download_extension(self.pkg.uris, destination, on_downloaded=lambda: self._unzip_extension_package(destination, self.get_base_dir()))
Copy link
Member Author

Choose a reason for hiding this comment

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

We need to get rid of WireProtocol.get_goal_state(), since it returns a global. That requires deeper refactoring, using it for the moment

@@ -576,7 +575,7 @@ def handle_updates_for_requested_version():
# The call to get_latest_agent_greater_than_daemon() also finds all agents in directory and sets the self.agents property.
# This state is used to find the GuestAgent object with the current version later if requested version is available in last GS.
available_agent = self.get_latest_agent_greater_than_daemon()
requested_version, _ = self.__get_requested_version_and_manifest_from_last_gs(protocol)
requested_version, _ = self.__get_requested_version_and_agent_family_from_last_gs()
Copy link
Member Author

Choose a reason for hiding this comment

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

Part of renaming "agent manifest" to "agent family"

@@ -986,7 +985,7 @@ def _is_orphaned(self):

def _load_agents(self):
path = os.path.join(conf.get_lib_dir(), "{0}-*".format(AGENT_NAME))
return [GuestAgent(path=agent_dir)
return [GuestAgent(is_fast_track_goal_state=self._goal_state.extensions_goal_state.source == GoalStateSource.FastTrack, path=agent_dir)
Copy link
Member Author

Choose a reason for hiding this comment

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

Class GuestAgent actually downloads the agent packages. That download still needs to be refactored into the common download logic. For the moment, this sets up the x-ms-verify-from-artifacts-blob header

@@ -3,18 +3,22 @@
[MESSAGES CONTROL]

disable=C, # (C) convention, for programming standard violation
broad-except, # (W0703): *Catching too general exception %s*
Copy link
Member Author

@narrieta narrieta Aug 19, 2022

Choose a reason for hiding this comment

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

I sorted the warnings alphabetically and added redundant-u-string-prefix (which is pointed out by Python 3.10)

@@ -1361,7 +1361,7 @@ def test_ext_handler_report_status_resource_gone(self, mock_add_event, *args):
def test_ext_handler_download_failure_permanent_ProtocolError(self, mock_add_event, mock_error_state, *args):
test_data = mockwiredata.WireProtocolData(mockwiredata.DATA_FILE)
exthandlers_handler, protocol = self._create_mock(test_data, *args) # pylint: disable=no-value-for-parameter
protocol.get_ext_handler_pkgs = Mock(side_effect=ProtocolError)
protocol.get_goal_state().fetch_extension_manifest = Mock(side_effect=ProtocolError)
Copy link
Member Author

Choose a reason for hiding this comment

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

These mocks in the tests use knowledge of internals of the code they are testing. They need to be rewritten. For the moment I just changed them to match the new production code.

class GoalStateMock(object):
def __init__(self, incarnation):
def __init__(self, incarnation, family, versions):
Copy link
Member Author

Choose a reason for hiding this comment

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

Some of the logic in the production code was moved from WireProtocol to GoalState. Updating the mocks to match those changes. These mocks need to be rewritten.

@@ -597,14 +579,14 @@ def call_storage_service(http_req, *args, **kwargs):
return http_req(*args, **kwargs)

def fetch_artifacts_profile_blob(self, uri):
return self._fetch_content("artifacts profile blob", [uri])[1] # _fetch_content returns a (uri, content) tuple
return self._fetch_content("artifacts profile blob", [uri], use_verify_header=False)[1] # _fetch_content returns a (uri, content) tuple
Copy link
Contributor

Choose a reason for hiding this comment

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

is this called only on fabric goal state?

Copy link
Member Author

Choose a reason for hiding this comment

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

The header is needed only when downloading manifests or packages (extension's or agent's). I'll add a comment in the code,

And also, you are right, this is called only for Fabric. We need it to get the onHold property, which comes in HGAP's vmSettings for FT goal states.

@narrieta narrieta merged commit ef996e2 into Azure:develop Sep 3, 2022
@narrieta narrieta deleted the verify-header branch September 3, 2022 00:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants