-
Notifications
You must be signed in to change notification settings - Fork 372
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
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #1777 +/- ##
==========================================
Coverage ? 68.95%
==========================================
Files ? 81
Lines ? 11322
Branches ? 1597
==========================================
Hits ? 7807
Misses ? 3187
Partials ? 328
Continue to review full report at Codecov.
|
from azurelinuxagent.common.protocol.util import get_protocol_util, \ | ||
OVF_FILE_NAME, \ | ||
TAG_FILE_NAME | ||
|
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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!
There was a problem hiding this 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) | ||
|
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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!
|
||
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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!
tests/protocol/mock_wire_protocol.py
Outdated
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/')): |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
|
||
# assert direct route is called | ||
self.assertEqual(1, patch_upload.call_count, "Direct channel was not used") | ||
# assert host plugin route is called |
There was a problem hiding this comment.
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"?
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