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 Batch data plane commands. #1875
Conversation
Hi @xingwu1, I'm your friendly neighborhood Azure Pull Request Bot (You can call me AZPRBOT). Thanks for your contribution! TTYL, AZPRBOT; |
ba902d4
to
8a0029d
Compare
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 pull request is fairly big. Without sufficient knowledge of Azure Batch itself, I'm afraid I won't get the full picture of it. However, after went through the pull request, I do have a concern I hope to address.
The concern is that the batch command's loading performance is poorer comparing to other commands.
DEBUG: Loaded module 'acs' in 0.065 seconds.
DEBUG: Loaded module 'appservice' in 0.063 seconds.
DEBUG: Loaded module 'batch' in 0.191 seconds.
DEBUG: Loaded module 'cloud' in 0.000 seconds.
DEBUG: Loaded module 'component' in 0.001 seconds.
DEBUG: Loaded module 'configure' in 0.001 seconds.
DEBUG: Loaded module 'container' in 0.001 seconds.
DEBUG: Loaded module 'context' in 0.000 seconds.
DEBUG: Loaded module 'feedback' in 0.000 seconds.
DEBUG: Loaded module 'iot' in 0.012 seconds.
DEBUG: Loaded module 'keyvault' in 0.028 seconds.
DEBUG: Loaded module 'network' in 0.087 seconds.
DEBUG: Loaded module 'profile' in 0.001 seconds.
DEBUG: Loaded module 'redis' in 0.006 seconds.
DEBUG: Loaded module 'resource' in 0.003 seconds.
DEBUG: Loaded module 'role' in 0.006 seconds.
DEBUG: Loaded module 'sql' in 0.015 seconds.
DEBUG: Loaded module 'storage' in 0.032 seconds.
DEBUG: Loaded module 'taskhelp' in 0.001 seconds.
DEBUG: Loaded module 'vm' in 0.020 seconds.
You can find the data at the end of the CI log.
I suspect the bad performance is caused by the complex logic which generates commands. A lot of regular expressions are used, which I'm wondering if can be replaced by the usage of inspect
module. Were the alternative solution considered?
In addition, a lot of the reflections (inspection) are based on the doc string. I'm not sure how reliable it is. If a documentation issue can escalate into runtime issue, I would say the design is too fragile.
/cc @derekbekoe
@@ -0,0 +1,9 @@ | |||
{ | |||
"startTask": { | |||
"commandLine": "cmd /c echo updated", |
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 won't work in shell (non-Windows). Will this cause any problem?
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, this is just a string to the test case, doesn't mean anything.
except (ValidationError, ClientRequestError) as ex: | ||
raise CLIError(ex) | ||
|
||
resize_pool.__doc__ = PoolResizeParameter.__doc__ |
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 can use this decroator on resize_pool
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 couldn't figure out how to use double decroator wrappers (need pass the parameter to decrator) and keep the original function signature. Could you give me a hint or example?
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.
raise CLIError(ex) | ||
|
||
|
||
def resize_pool(client, pool_id, target_dedicated=None, #pylint:disable=too-many-arguments |
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 trailing comments here is not PEP8 compliant.
- two spaces between the last char and
#
. - once space between the
#
and following char.
It is not a major issue. Just a reminding, once PEP8 check is turned on for this command module errors will show up.
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.
Resolved.
from ._client_factory import batch_client_factory | ||
from ._client_factory import batch_client_factory, batch_data_service_factory | ||
from ._command_type import cli_data_plane_command | ||
from ._validators import validate_pool_settings |
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 is recommended to use full path instead of relative path. We do have a lot of relative path import in existing code base. I think they're bad practice, too.
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.
resolved.
# TODO: Enum choice lists | ||
|
||
|
||
_CLASS_NAME = re.compile(r"<(.*?)>") # Strip model name from class docstring |
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.
Is there a way to avoid regular expression?
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.
investigating.
Note, if you want to check all PEP8 violation just run:
|
@@ -95,8 +95,10 @@ def _wrapped_func(*args, **kwargs): | |||
return _decorator | |||
|
|||
|
|||
def transfer_doc(source_func): | |||
def transfer_doc(source_func, *additional_source_funcs): |
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 additional use case is a rare use case. Concatenating docstring is not use case in any other command module anytime soon. Therefore it is not ideal to modify the decorator just to fit a corner case. Please revoke the change and write a customized decorator in your own module.
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.
resolve
@@ -265,8 +262,6 @@ def update_pool(client, pool_id, json_file=None, command_line=None, # pylint:di | |||
except (ValidationError, ClientRequestError) as ex: | |||
raise CLIError(ex) | |||
|
|||
update_pool.__doc__ = PoolUpdatePropertiesParameter.__doc__ + "\n" + StartTask.__doc__ |
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.
Out of curiosity, why concatenate two docstrings? Isn't the format of the new doc string broken?
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 one will intend to directly call the this custom function. The docstring here is only used for build up the cli param help string. So the broken doc string doesn't bother any real user.
@troydai, |
@annatisch thanks for the clarification. I'm glad to know that. However, I think you should probe what hit the performance during the loading. The |
@troydai, |
@troydai, additionally - do you know what cmd I can use to specifically run and display those loading stats? |
@annatisch |
@troydai - thanks, I just figured that out ;) |
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.
Is all the introspection in src/command_modules/azure-cli-batch/azure/cli/command_modules/batch/_command_type.py
needed?
As @troydai pointed out, keep an eye on the time it takes to load the module with ‘az —debug’.
Right now, it’s taking a lot longer than others and this affects perf..
@@ -5,7 +5,20 @@ | |||
|
|||
from azure.mgmt.batch import BatchManagementClient | |||
|
|||
import azure.batch.batch_service_client as batch | |||
import azure.batch.batch_auth as batchauth |
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.
In client factories, imports of SDK should go inside the factory methods themselves.
This is why the azure-cli-batch
package takes so long to load.
The CLI delays calling the factory methods until a command that needs it is about to be executed.
e.g. on my core i7 macbook:
$ time python -c "from azure.mgmt.batch import BatchManagementClient"
real 0m0.317s
user 0m0.250s
sys 0m0.064s
$ time python -c "import azure.batch.batch_auth as batchauth"
real 0m0.515s
user 0m0.354s
sys 0m0.111s
FYI: @tjprescott, @troydai, @yugangw-msft, @johanste
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.
Oh good catch - thanks!
Seems like imports are one of our major loading perf issues at this point
for command in ['list', 'show', 'create', 'set', 'delete', 'package']: | ||
register_cli_argument('batch application {}'.format(command), 'account_name', batch_name_type, options_list=('--name', '-n'), validator=application_enabled) | ||
|
||
register_cli_argument('batch pool resize', 'if_modified_since', help='Specify this header to perform the operation only if the resource has been modified since the specified date/time.', type=datetime_format, arg_group='Pre-condition') |
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.
Would suggest removing Specify this header to
from the help text.
Users of the command specify arguments not headers. This feels more like SDK documentation.
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.
@derekbekoe - yeah these strings were copied straight from our swagger spec - we're endevouring to clean up this kind of terminology both here and in the spec itself :)
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.
resolved.
for item in ['batch certificate delete', 'batch certificate create', 'batch pool resize', 'batch pool reset', 'batch job list', 'batch task create']: | ||
register_extra_cli_argument(item, 'account_name', arg_group='Batch Account', | ||
validator=validate_client_parameters, | ||
help='Batch account name. Environment variable: AZURE_BATCH_ACCOUNT') |
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.
What does Environment variable: AZURE_BATCH_ACCOUNT'
mean?
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.
@derekbekoe - These three environment variables are for authentication with our data plan API and are named as such to maintain consistency with the Xplat CLI.
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.
resolve.
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.
Consider using az_config
, the CLI configuration feature for this.
This would allow the batch account to be set with AZURE_BATCH_ACCOUNT
(as required) but also allow setting the config in the CLI configuration file.
Another example https://github.com/Azure/azure-cli/blob/master/src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/_validators.py#L51.
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.
Already use az_config, if you look _validator.validate_client_parameters function.
try: | ||
from urllib.parse import urlsplit | ||
except ImportError: | ||
from urlparse import urlsplit # pylint: disable=import-error |
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.
from six.moves.urllib.parse import urlsplit
instead.
https://pythonhosted.org/six/#module-six.moves.urllib.parse
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.
resolved.
from ._client_factory import batch_client_factory | ||
from azure.cli.command_modules.batch._client_factory import ( | ||
batch_client_factory, batch_data_service_factory) | ||
from azure.cli.command_modules.batch._command_type import cli_data_plane_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.
_command_type
has a lot of code in it including several uses of regex
.
All of that is executed before importing cli_data_plane_command
.
raise CLIError(ex) | ||
|
||
|
||
def delete_certificate(client, thumbprint, thumbprint_algorithm, abort=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.
Since in here, the action is store_true
, abort=False
makes more sense IMHO.
except AttributeError: | ||
raise CLIError(ex) | ||
except (ValidationError, ClientRequestError) as ex: | ||
raise CLIError(ex) |
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 try/except logic is used multiple times.
Consider moving out to helper function that takes a callable.
e.g.:
def delete_certificate(...):
def handler():
if abort:
client.cancel_deletion(thumbprint_algorithm, thumbprint)
else:
client.delete(thumbprint_algorithm, thumbprint)
_ex_handle_patch_exceptions(handler)
def _ex_handle_patch_exceptions(some_handler):
try:
some_handler()
except BatchErrorException ...
...
Something like that.
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.
resolved.
if_unmodified_since=if_unmodified_since) | ||
|
||
try: | ||
return client.resize(pool_id, param, pool_resize_options=resize_option) |
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 question this convention that I've seen in this PR of having an abort
flag that executes a different operation.
Should consider having these as two separate commands.
Does client.stop_resize
return the same object structure as client.resize
?
If not, this is another reason why you may want these as two separate commands.
|
||
|
||
@transfer_doc(PoolUpdatePropertiesParameter, StartTask) | ||
def update_pool(client, pool_id, json_file=None, command_line=None, # pylint:disable=too-many-arguments, W0613 |
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.
Replace W0613
with the appropriate short-message.
This link should help http://pylint-messages.wikidot.com/all-messages.
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.
Same with other instances.
"osFamily": "4", | ||
"targetOSVersion": "*" | ||
} | ||
"targetDedicated": 2, |
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 character is highlighted in red on GitHub which makes me suspicious.
Can you verify that it's a :
(colon)?
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.
Missing a ,
following previous entity (on line 7).
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.
@troydai, @derekbekoe,
This is a deliberately incorrect JSON file to test for bad input handling. Hence the file name suffix "invalid.json" :)
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.
ahhh okay!
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.
Overall, LGTM. A few questions on some things. I'll be interested to try out your logic for exposing complex types and seeing if we can eventually move that up into the CLI core.
account_name = kwargs.pop('account_name', None) | ||
account_key = kwargs.pop('account_key', None) | ||
account_endpoint = kwargs.pop('account_endpoint', None) | ||
kwargs.pop('force', 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.
What is this for?
self.done = True | ||
|
||
|
||
class AzureDataPlaneCommand(object): |
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 probably be AzureBatchDataPlaneCommand
help=docstring)) | ||
|
||
|
||
def cli_data_plane_command(name, operation, client_factory, transform=None, # pylint:disable=too-many-arguments |
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 cli_batch_data_plane_command
.
register_cli_argument('batch certificate', 'thumbprint', help='The certificate thumbprint.') | ||
register_cli_argument('batch certificate', 'thumbprint_algorithm', help='The certificate thumbprint algorithm.') | ||
register_cli_argument('batch certificate', 'password', help='The password to access the certificate\'s private key.') | ||
register_cli_argument('batch certificate', 'cert_file', type=file_type, help='The certificate file: cer file or pfx file.', validator=validate_cert_file, completer=FilesCompleter()) |
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 should also specify type=file_type
here and import file_type from azure.cli.core.commands.parameters
. This ensures the leading ~ is expanded correctly.
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 has already set the type to file_type.
register_cli_argument('batch certificate', 'cert_file', type=file_type, help='The certificate file: cer file or pfx file.', validator=validate_cert_file, completer=FilesCompleter()) | ||
register_cli_argument('batch certificate delete', 'abort', action='store_true', help='Cancel the failed certificate deletion operation.') | ||
|
||
register_cli_argument('batch task create', 'json_file', type=file_type, help='The file containing the task(s) to create in JSON format, if this parameter is specified, all other parameters are ignored.', validator=validate_json_file, completer=FilesCompleter()) |
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.
Same here. type=file_type
. Also, an alias to --task-file
might be more intuitive than "--json-file".
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 already be file_type type. I will udpate the name.
help='Batch account name. Environment variable: AZURE_BATCH_ACCOUNT') | ||
register_extra_cli_argument(item, 'account_key', arg_group='Batch Account', | ||
help='Batch account key. Must be used in conjunction with Batch account name and endpoint. Environment variable: AZURE_BATCH_ACCESS_KEY') | ||
register_extra_cli_argument(item, 'account_endpoint', arg_group='Batch Account', |
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 assumed you added these three arguments to the Azure{Batch}DataPlaneCommand object to avoid having to do this. Why do you do both?
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 AzureDataPlaneCommand is automatic create the parameter, and here is manually added to the only 5 custom commands.
5d85241
to
824eefb
Compare
The latest result show the loading performance has been improved a lot DEBUG: Loaded module 'batch' in 0.056 seconds Consider the batch service has around 90 commands, it is quite good result. |
Also, one thing we require of all New Command PRs is a dump of the -h help text. This helps us spot things like missing or illogical parameter help. Not every command is necessary--generally just the create commands. Please add those as a comment on the PR (and omit the global arguments please). |
@tjprescott, |
@annatisch is the help text updated? I started looking at it and immediately saw:
This is not consistent with the rest of the CLI. It would be called I just want to make sure before I start commenting on the help text that it's the latest. |
@tjprescott, FYI @xingwu1 |
@tjprescott, |
* node-user -> node user * Review feedback * More feedback fixes * pep fixes * fixed underscore * More touch ups
* Command refinements * More command feedback * Test fixes * More tests
9a3f088
to
d619798
Compare
We've just finished an internal review of the commands and help documentation and I have updated the outputs here: The commands have reduced quite significantly - though still quite numerous. Let us know what you think. We've addressed the enum options (and removed thumbprint-algorithm), and prefixed more commands for clarification. We've reworded quite a lot of the help text to be more informative (e.g. correct use of Boolean flags). FYI @itowlson |
* streamlined commands * Updated tests * Fixed arg loading test
@tjprescott |
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. Additional tweaks can still be make in follow-up PRs if needed.
@troydai Could you review the change and sign off it if you feel good with the change? |
* support silent args * Fixed pool error message * Further command editing * last fixes
@troydai - would be great if you could take a look at the changes so we can get this merged today :) |
I'm taking another look. |
except DeserializationError: | ||
message = "Argument {} is not a valid ISO-8601 datetime format" | ||
raise ValueError(message.format(value)) | ||
else: |
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.
not sure if this else
is necessary here.
def validate_pool_resize_parameters(namespace): | ||
"""Validate pool resize parameters correct""" | ||
if not namespace.abort: | ||
if not namespace.target_dedicated: |
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 not namespace.abort and not namespace.target_dedicated:
Thanks everybody! Great to see this merged! |
Thanks guys!! |
Thanks!!! |
No description provided.