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

Separate goal state from protocol #1777

Merged
merged 7 commits into from
Feb 13, 2020
Merged

Conversation

narrieta
Copy link
Member

@narrieta narrieta commented Feb 11, 2020

if an error occurs while _update_from_goal_state is populating the goal state the retry loop may end up producing a goal state that is only partially populated.

This PR addresses that issue and starts to refactor the goal state out of the protocol. I will do further refactoring to improve that separation once we remove the metadata protocol.

The code refactored in this PR is heavily exercised by the current tests. I will add tests specific to the goal state class in my next round of refactoring.

I did manual verification using the debugger on a live VM, and automation reported no errors.


This change is Reviewable

@codecov
Copy link

codecov bot commented Feb 11, 2020

Codecov Report

❗ No coverage uploaded for pull request base (develop@d19c41c). Click here to learn what that means.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##             develop    #1777   +/-   ##
==========================================
  Coverage           ?   68.95%           
==========================================
  Files              ?       81           
  Lines              ?    11322           
  Branches           ?     1597           
==========================================
  Hits               ?     7807           
  Misses             ?     3187           
  Partials           ?      328
Impacted Files Coverage Δ
azurelinuxagent/common/version.py 71.73% <ø> (ø)
azurelinuxagent/pa/provision/default.py 73.97% <100%> (ø)
azurelinuxagent/ga/update.py 88.22% <100%> (ø)
azurelinuxagent/common/protocol/util.py 79.76% <100%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d19c41c...7f2e6a6. Read the comment docs.

from azurelinuxagent.common.protocol.util import get_protocol_util, \
OVF_FILE_NAME, \
TAG_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.

This import was creating a dependency on protocol.util to code that imported anything under protocol and was leading to a couple of circular dependencies. I remove the statement and updated the code to always import get_procotol_util from protocol.util instead of protocol.

@@ -0,0 +1,411 @@
# Microsoft Azure Linux Agent
Copy link
Member Author

Choose a reason for hiding this comment

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

Most of the code here comes from wire.py, but please do a careful review. Thanks!

else:
xml_text = wire_client.fetch_config(uri, wire_client.get_header_for_cert())
self.remote_access = RemoteAccess(xml_text)

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'm planning on moving the fetch_* methods below to protocol, and remove the current interface that exposes the goal state from the protocol class. I.e. code would do "goal_state = protocol.fetch_goal_state()" and then use "goal_state" directly instead of using "protocol" to retrieve the properties of the goal state.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds like a good idea, thanks for doing this!



class GoalState(object):
def __init__(self, wire_client, full_goal_state=False, base_incarnation=None):
Copy link
Member Author

@narrieta narrieta Feb 11, 2020

Choose a reason for hiding this comment

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

Now the constructor of GoalState has a dependency on the protocol, which makes unit testing a little harder. To compensate for that I created mock_wire_protocol.py in the tests directory). Many tests cleanup nicely using mock_wire_protocol; I only updated the tests affected by this PR but will cleanup other tests as I touch the corresponding code.

from azurelinuxagent.common.version import AGENT_NAME, CURRENT_VERSION

VERSION_INFO_URI = "http://{0}/?comp=versions"
GOAL_STATE_URI = "http://{0}/machine/?comp=goalstate"
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 variables and methods were moved to goal_state.py

new_goal_state = GoalState.fetch_full_goal_state_if_incarnation_different_than(self, self._goal_state.incarnation)

if new_goal_state is not None:
self._goal_state = new_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.

this fixes the original issue: the entire goal state is updated or no updates are done

self._ext_conf = new_ext_conf
if goal_state_property is not None and goal_state_property.xml_text is not None:
self.save_cache(file_path, goal_state_property.xml_text)
else: # remove the file from the previous goal state if it exists
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 forgot to remove this else (removing the older files is not needed, since self.goal_state_flusher.flush() above moves the files to the history folder

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for clarifying that! This did confuse me a bit

@@ -3,19 +3,19 @@
<GAFamily>
<Name>Prod</Name>
<Uris>
<Uri>http://manifest_of_ga.xml</Uri>
<Uri>http://mock-goal-state/manifest_of_ga.xml</Uri>
Copy link
Member Author

Choose a reason for hiding this comment

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

see mock_wire_protocol.py for the motivation of these updates in the test data

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for creating that file, really helpful!

Copy link
Contributor

@larohra larohra left a comment

Choose a reason for hiding this comment

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

Left some minor comments

else:
xml_text = wire_client.fetch_config(uri, wire_client.get_header_for_cert())
self.remote_access = RemoteAccess(xml_text)

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds like a good idea, thanks for doing this!

save_if_not_none(self._goal_state.hosting_env, HOSTING_ENV_FILE_NAME)
save_if_not_none(self._goal_state.shared_conf, SHARED_CONF_FILE_NAME)
save_if_not_none(self._goal_state.ext_conf, EXT_CONF_FILE_NAME.format(self._goal_state.incarnation))
save_if_not_none(self._goal_state.remote_access, REMOTE_ACCESS_FILE_NAME.format(self._goal_state.incarnation))
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious, why is "Certificates.xml" handled differently in the goal_state.py file whereas all these other files are handled here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I forgot to add a comment here.

Certificates doesn't hold a reference to xml_text. I did not add one since I'm guessing the original intention was not to hold the data on memory for too long.

self._ext_conf = new_ext_conf
if goal_state_property is not None and goal_state_property.xml_text is not None:
self.save_cache(file_path, goal_state_property.xml_text)
else: # remove the file from the previous goal state if it exists
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for clarifying that! This did confuse me a bit

@@ -867,29 +790,29 @@ def get_goal_state(self):
return self._goal_state

def get_hosting_env(self):
if self._hosting_env is None:
if self._goal_state is None:
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't this be self._goal_state.hosting_env rather than just self._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 intention of this check was to ensure that update_goal_state was called before retrieving any of the properties of the goal state, wasn't it?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ohh sorry I missed the raise in the next line. When I introduced this code we werent raising error rather just logging a warning which is what I thought was still happening here. Thanks for clarifying!

azurelinuxagent/common/protocol/wire.py Show resolved Hide resolved

def get_certs(self):
# This property can be None in the GoalState, so not returning a ProtocolError here
return self._certs
return self._goal_state.certs
Copy link
Contributor

Choose a reason for hiding this comment

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

This will throw an error if self._goal_state is None, we should handle that case

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, added

@@ -3,19 +3,19 @@
<GAFamily>
<Name>Prod</Name>
<Uris>
<Uri>http://manifest_of_ga.xml</Uri>
<Uri>http://mock-goal-state/manifest_of_ga.xml</Uri>
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for creating that file, really helpful!

wire_server_endpoint = 'http://{0}'.format(KNOWN_WIRESERVER_IP)

def mock_http_get(url, *args, **kwargs):
if not (url.startswith(wire_server_endpoint) or url.startswith('http://mock-goal-state/') or url.startswith('https://mock-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.

this should be case insensitive

Copy link
Contributor

@larohra larohra left a comment

Choose a reason for hiding this comment

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

LGTM

@narrieta narrieta merged commit ce8e736 into Azure:develop Feb 13, 2020
@narrieta narrieta deleted the goal-state branch February 13, 2020 22:24

# assert direct route is called
self.assertEqual(1, patch_upload.call_count, "Direct channel was not used")
# assert host plugin route is called
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be "host plugin route is NOT called"?

@narrieta narrieta mentioned this pull request Mar 6, 2020
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.

3 participants