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 Batch data plane commands. #1875

Merged
merged 27 commits into from Feb 7, 2017
Merged

Add Batch data plane commands. #1875

merged 27 commits into from Feb 7, 2017

Conversation

xingwu1
Copy link
Member

@xingwu1 xingwu1 commented Jan 26, 2017

No description provided.

@azurecla
Copy link

Hi @xingwu1, I'm your friendly neighborhood Azure Pull Request Bot (You can call me AZPRBOT). Thanks for your contribution!

In order for us to evaluate and accept your PR, we ask that you sign a contribution license agreement. It's all electronic and will take just minutes. I promise there's no faxing. https://cla.azure.com.

TTYL, AZPRBOT;

Copy link
Contributor

@troydai troydai left a 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",
Copy link
Contributor

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?

Copy link
Member Author

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__
Copy link
Contributor

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

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 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?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure. Here's an example

Python decorator is defined in PEP 318

raise CLIError(ex)


def resize_pool(client, pool_id, target_dedicated=None, #pylint:disable=too-many-arguments
Copy link
Contributor

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.

  1. two spaces between the last char and #.
  2. 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.

Copy link
Member Author

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
Copy link
Contributor

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.

Copy link
Member Author

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
Copy link
Contributor

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

investigating.

@troydai
Copy link
Contributor

troydai commented Jan 26, 2017

Note, if you want to check all PEP8 violation just run:

check_style --pep8 --module batch

@@ -95,8 +95,10 @@ def _wrapped_func(*args, **kwargs):
return _decorator


def transfer_doc(source_func):
def transfer_doc(source_func, *additional_source_funcs):
Copy link
Contributor

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.

Copy link
Member Author

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__
Copy link
Contributor

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?

Copy link
Member Author

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.

@annatisch
Copy link
Member

@troydai,
The complex logic is almost entirely executed only on the specific command that is being run. Only one regex is executed in loading the module - and that's a precompiled one.
Unless the arguments for every command are loaded when the module is loaded? I was assuming the argument loader function was only called one that specific command had been executed?

@troydai
Copy link
Contributor

troydai commented Jan 26, 2017

@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 batch command is two times slower than the second slowest command module in the list. It absolute time will only increase in the user environment.

@annatisch
Copy link
Member

@troydai,
That's for confirming that - yes I've been running it under the VS profiler, though having difficulties finding the culprit. Still investigating...

@annatisch
Copy link
Member

annatisch commented Jan 26, 2017

@troydai, additionally - do you know what cmd I can use to specifically run and display those loading stats?

@derekbekoe
Copy link
Member

@annatisch az --debug

@annatisch
Copy link
Member

@troydai - thanks, I just figured that out ;)

Copy link
Member

@derekbekoe derekbekoe left a 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
Copy link
Member

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

Copy link
Member

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')
Copy link
Member

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.

Copy link
Member

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 :)

Copy link
Member Author

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')
Copy link
Member

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?

Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

resolve.

Copy link
Member

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.

For example https://github.com/Azure/azure-cli/blob/master/src/command_modules/azure-cli-container/azure/cli/command_modules/container/custom.py#L23.

Another example https://github.com/Azure/azure-cli/blob/master/src/command_modules/azure-cli-storage/azure/cli/command_modules/storage/_validators.py#L51.

Copy link
Member Author

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
Copy link
Member

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

Copy link
Member Author

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
Copy link
Member

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):
Copy link
Member

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)
Copy link
Member

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.

Copy link
Member Author

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)
Copy link
Member

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
Copy link
Member

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.

Copy link
Member

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,
Copy link
Member

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)?

Copy link
Contributor

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).

Copy link
Member

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" :)

Copy link
Member

Choose a reason for hiding this comment

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

ahhh okay!

Copy link
Member

@tjprescott tjprescott left a 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)
Copy link
Member

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):
Copy link
Member

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
Copy link
Member

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())
Copy link
Member

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.

Copy link
Member Author

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())
Copy link
Member

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".

Copy link
Member Author

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',
Copy link
Member

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?

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 AzureDataPlaneCommand is automatic create the parameter, and here is manually added to the only 5 custom commands.

@xingwu1 xingwu1 force-pushed the master branch 3 times, most recently from 5d85241 to 824eefb Compare January 27, 2017 08:51
@xingwu1
Copy link
Member Author

xingwu1 commented Jan 27, 2017

@troydai @derekbekoe

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.

@tjprescott
Copy link
Member

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).

@annatisch
Copy link
Member

annatisch commented Jan 27, 2017

@tjprescott,
I've generated the help text into a separate branch on my fork:
Summary Commands (includes all create commands, both custom and 'auto parsed' and a couple of others, no global params)
Full Commands (includes all commands, group headings and globals parameters)

@tjprescott
Copy link
Member

@annatisch is the help text updated? I started looking at it and immediately saw:

    --storage-account-id          : The resource id of the storage account to be used for auto

                                    storage account.

    --storage-account-name        : The storage account name to be used for auto storage account.

This is not consistent with the rest of the CLI. It would be called storage_account which would accept a name or id. But in the custom command it only looks like storage_account_id.

I just want to make sure before I start commenting on the help text that it's the latest.

@annatisch
Copy link
Member

@tjprescott,
It was generated yesterday - so while it's not 100% latest, it's pretty recent.
Also - the command arguments you mentioned there are actually from the management module which has already been merged into master. But we can update it :)

FYI @xingwu1

@xingwu1
Copy link
Member Author

xingwu1 commented Jan 27, 2017

@tjprescott,
I change the code to use single storage account to accept both name and id scenario.

xingwu1 and others added 4 commits February 2, 2017 12:25
* 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
@xingwu1 xingwu1 force-pushed the master branch 3 times, most recently from 9a3f088 to d619798 Compare February 2, 2017 21:44
@annatisch
Copy link
Member

@tjprescott, @troydai

We've just finished an internal review of the commands and help documentation and I have updated the outputs here:
Summary Commands (includes all create commands, both custom and 'auto parsed' and a couple of others, no global params)
Full Commands (includes all commands, group headings and globals parameters)

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
@xingwu1
Copy link
Member Author

xingwu1 commented Feb 6, 2017

@tjprescott
We reduced more parameters and add the VM image completer.

Copy link
Member

@tjprescott tjprescott left a 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.

@xingwu1
Copy link
Member Author

xingwu1 commented Feb 6, 2017

@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
@annatisch
Copy link
Member

@troydai - would be great if you could take a look at the changes so we can get this merged today :)

@troydai
Copy link
Contributor

troydai commented Feb 7, 2017

I'm taking another look.

except DeserializationError:
message = "Argument {} is not a valid ISO-8601 datetime format"
raise ValueError(message.format(value))
else:
Copy link
Contributor

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:
Copy link
Contributor

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:

@derekbekoe derekbekoe merged commit ba47219 into Azure:master Feb 7, 2017
@johanste
Copy link
Member

johanste commented Feb 7, 2017

Thanks everybody! Great to see this merged!

@annatisch
Copy link
Member

Thanks guys!!

@xingwu1
Copy link
Member Author

xingwu1 commented Feb 7, 2017

Thanks!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants