-
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
Improvements in run_command #1614
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #1614 +/- ##
==========================================
+ Coverage 66.02% 66.12% +0.1%
==========================================
Files 77 78 +1
Lines 11049 11160 +111
Branches 1557 1574 +17
==========================================
+ Hits 7295 7380 +85
- Misses 3421 3445 +24
- Partials 333 335 +2
Continue to review full report at Codecov.
|
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 comments.
@@ -117,30 +117,51 @@ def _encode_command_output(output): | |||
return ustr(output, encoding='utf-8', errors="backslashreplace") | |||
|
|||
|
|||
def run_command(command): | |||
class CommandError(Exception): |
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.
Can we define this in azurelinuxagent.common.exception.py
instead?
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 know that is what we've doing so far. but I do not think that is a good pattern. Basically we are dumping small pieces (the exceptions) from completely separate modules into a single file. I believe it is cleaner to define the exception in the module that originates it, to avoid unneeded/unwanted cross-module dependencies.
retcode = 0 | ||
@staticmethod | ||
def _get_message(command, returncode): | ||
command_name = command[0] if isinstance(command, list) and len(command) > 0 else command |
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.
Don't we want to log the entire command?
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.
No, the parameters may contain sensitive data that we do not want in the exception. The caller knows the command and whether it is appropriate to include the parameters or not; it can always extend the exception with the parameters if needed.
tests/common/osutil/test_alpine.py
Outdated
@@ -0,0 +1,34 @@ | |||
# Copyright 2018 Microsoft Corporation |
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.
It's 2019. 😀
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.
do we even need a year? I might as well remove 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.
I honestly have no idea, maybe it's needed from a legal perspective, but in general I don't see any use for 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.
some file have it, some don't. will add it
tests/ga/test_env.py
Outdated
from tests.tools import * | ||
|
||
|
||
class TestMonitor(AgentTestCase): |
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 called TestEnvironment as we're testing the environment thread, not the monitoring thread?
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 magic of copy-n-paste... will fix it
tests/ga/test_env.py
Outdated
message = args[0] | ||
self.assertEquals("Dhcp client is not running.", message) | ||
|
||
def test_get_dhcp_client_pid_should_return_none_nad_log_an_error_when_an_invalid_command_is_used(self): |
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.
nit: 'nad' -> 'and'
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.
copy-n-paste should fix typos... fixed
tests/ga/test_env.py
Outdated
pids = EnvHandler().get_dhcp_client_pid() | ||
self.assertEquals(pids, ["11", "22", "4", "5", "6", "9"]) | ||
|
||
def test_get_dhcp_client_pid_should_return_none_nad_log_a_warning_when_dhcp_client_is_not_running(self): |
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.
nit: 'nad' -> 'and'
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.
fixed
azurelinuxagent/ga/env.py
Outdated
def get_dhcp_client_pid(self): | ||
pid = None | ||
try: | ||
# get_dhcp_pid may return multiple PIDs; we split them and return an (alphabetically) sorted list |
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.
Can you clarify here why it's alphabetically sorted?
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.
will do
self.assertIsNone(pids) | ||
self.assertEquals(mock_warn.call_count, 2) | ||
|
||
def test_handle_dhclient_restart_should_reconfigure_network_routes_when_dhcp_client_restarts(self): |
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.
nit: 'dhclient' -> 'dhcpclient'
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 pattern i'm following is
test_<subject>_should_<expected_behavior>
in this case the subject (the function I am testing) is handle_dhclient_restart
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.
You're right, it was me who didn't read carefully!
except Exception as e: | ||
logger.error(u"Cannot execute [{0}]. Error: [{1}]".format(command, ustr(e))) | ||
if log_error: | ||
logger.error(u"Command [{0}] raised unexpected exception: [{1}]", command, ustr(e)) |
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.
Since command
is an array, should we convert it to a string before logging?
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.
sure, i'll do that
if log_error: | ||
logger.error( | ||
"Command: [{0}], return code: [{1}], stdout: [{2}] stderr: [{3}]", | ||
command, |
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.
Since command is an array, should we convert it to a string before logging?
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.
will do
Thanks, Norberto! LGTM |
@larohra - sorry, missed one of your comments about formatting the command when we log it... can you check my last commit? thanks |
if 'log_error' is True, it also logs details about the error. | ||
""" | ||
def format_command(cmd): | ||
return " ".join(cmd) if isinstance(cmd, list) else command |
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.
if isinstance(cmd, list) else command
you dont need this check everywhere, the join command would work even if its just a string
a = ['a', 'b']
'.'.join(a)
'a.b'
a = 'a'
'.'.join(a)
'a'
' '.join(a)
'a'
Else everything else looks good
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.
" ".join("/var/lib/waagent")
'/ v a r / l i b / w a a g e n t'
:)
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.
Approved with a minor suggestion
Another round of changes to run_command (which will replace the current run_get_output):
Errors are now NOT logged by default; the caller needs to explicitly ask for it. This is to avoid situations in which inadvertently we produce too much log output (e.g. the 'pidof' message on the serial console) or disclose sensitive information.
Created the CommandError exception, to report errors in the command
I also undid the periodic logging in run_get_output since that may lose important logging information.
I changed the implementations of osutil.get_dhcp_pid that use pidof to now use run_command instead of run_get_output.
The environment thread already had logic to report issues in get_dhcp_pid only once, but the implementations in some distros (e.g. Ubuntu 18) were using run_get_output without turning off error logging.
I fixed an issue in EnvHandler.handle_dhclient_restart that I found during testing: some distros (e.g. Ubuntu 18) may have multiple instances of the DHCP client.
I also started unit test suites for some of the distro-dependent implementations of osutil.
I will continue replacing run_get_output with run_command in later PRs.
This change is