-
Notifications
You must be signed in to change notification settings - Fork 3k
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
[VM] Allow VM creation with specialized VHD #2256
Conversation
@yugangw-msft the tests all pass on my machine, so that's mysterious. The logic is in place though if you could take a look. I'm not looking to merge this until sometime next week. |
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 this change looks very good to me. I suggest add a few unit tests on throwing exceptions when we detect invalid/unnecessary/conflicting arguments
@@ -254,7 +255,7 @@ def build_vm_resource( # pylint: disable=too-many-locals | |||
image_reference=None, os_disk_name=None, custom_image_os_type=None, | |||
storage_caching=None, storage_sku=None, | |||
os_publisher=None, os_offer=None, os_sku=None, os_version=None, os_vhd_uri=None, | |||
managed_os_disk=None, data_disk_sizes_gb=None, image_data_disks=None, | |||
attach_os_disk=None, data_disk_sizes_gb=None, image_data_disks=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.
The current name looks like a bit like a boolean flag. Maybe os_disk_to_attach
? Up to you though.
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 just used the same name as the command's parameter.
|
||
def _get_storage_profile_description(profile): | ||
if profile == StorageProfile.SACustomImage: | ||
return 'unmanaged disk from custom image' |
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.
suggest to tweak the word a bit. This option means 'use unmanaged disk when create the VM from unmanaged custom image'
if profile == StorageProfile.SACustomImage: | ||
return 'unmanaged disk from custom image' | ||
elif profile == StorageProfile.SAPirImage: | ||
return 'unmanaged disk from PIR image' |
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.
PIR
probably is an internal term, I am thinking we should just call Azure Marketplace images
. I will get it clarified the first time on Monday
elif profile == StorageProfile.SAPirImage: | ||
return 'unmanaged disk from PIR image' | ||
elif profile == StorageProfile.SASpecializedOSDisk: | ||
return 'unmanaged VHD OS disk' |
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.
suggest attach to existing unmanaged os disk
elif profile == StorageProfile.SASpecializedOSDisk: | ||
return 'unmanaged VHD OS disk' | ||
elif profile == StorageProfile.ManagedCustomImage: | ||
return 'managed disk from custom image' |
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.
suggest create VM from managed custom image
if namespace.storage_profile == StorageProfile.ManagedPirImage: | ||
required = ['image'] | ||
forbidden = ['os_type', 'attach_os_disk', 'storage_account', | ||
'storage_container_name', 'use_unmanaged_disk'] |
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.
adding use_unmanaged_disk
to the list will be a no-op since it is a valid argument we used to figure out the profile type. I assume you put here still to get the code simpler. If yes, then it is fine
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.
Right. It's here more for documentation purposes. If in the future the logic to quickly guess the profile is removed, this would still remain accurate.
elif namespace.storage_profile == StorageProfile.ManagedSpecializedOSDisk: | ||
required = ['os_type', 'attach_os_disk'] | ||
forbidden = ['os_disk_name', 'storage_caching', 'storage_account', | ||
'storage_container_name', 'use_unmanaged_disk'] |
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 question on use_unmanaged_disk
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 think storage_sku
should be forbidden as well, same for the SASpecializedOSDisk
option below
@@ -1424,9 +1424,9 @@ def create_vm(vm_name, resource_group_name, image=None, | |||
public_ip_address=None, public_ip_address_allocation='dynamic', | |||
public_ip_address_dns_name=None, | |||
os_disk_name=None, os_type=None, storage_account=None, | |||
storage_caching='ReadWrite', storage_container_name='vhds', | |||
storage_caching=None, storage_container_name=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.
Any reason we like to change this? I thought it is recommended by putting default value in a function's signature rather in param.py.
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, it is. The reason is because since these are in the list of "forbidden" parameters for certain profiles, then having a default means that these always throw. I toyed with using the "MarkSpecifiedAction" on these, but that always seemed like a hacky workaround (and it has demonstrably introduced bugs) and this was a simpler solution. Previously, we silently ignored these parameters when they were defaulted or specified, but we have said that we do not want to do 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.
Agreed.
@@ -1546,6 +1551,9 @@ def create_vm(vm_name, resource_group_name, image=None, | |||
client = get_mgmt_service_client(ResourceManagementClient).deployments | |||
properties = DeploymentProperties(template=template, parameters={}, mode='incremental') | |||
if validate: | |||
from pprint import pprint | |||
import sys | |||
pprint(template, sys.stderr) |
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.
Guess it is temp change you will remove later?
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, I actually put this in here just for you. 😄 You mentioned how pulling the template out of the --debug output was frustrating so I figured this would be more helpful. Thoughts?
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! Then definitely keep. I suggest we use logger.warning
for anything go to stderr.
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'll give it a try--just wasn't sure if pprint would play nicely with a logger warning.
@@ -1701,6 +1711,9 @@ def create_vmss(vmss_name, resource_group_name, image, | |||
client = get_mgmt_service_client(ResourceManagementClient).deployments | |||
properties = DeploymentProperties(template=template, parameters={}, mode='incremental') | |||
if validate: | |||
from pprint import pprint | |||
import sys | |||
pprint(template, sys.stderr) |
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
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.
Super intentional 💯
register_cli_argument(scope, 'storage_sku', help='The sku of storage account to persist VM. By default, only Standard_LRS and Premium_LRS are allowed. Using with --use-unmanaged-disk, all are available.', arg_group='Storage', **enum_choice_list(SkuName)) | ||
register_cli_argument(scope, 'storage_container_name', help="Only applicable when use with '--use-unmanaged-disk'. Name of the storage container for the VM OS disk.", arg_group='Storage') | ||
register_cli_argument(scope, 'storage_container_name', help="Only applicable when use with '--use-unmanaged-disk'. Name of the storage container for the VM OS disk. Default: vhds", arg_group='Storage') |
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.
Why not default='vhds'
?
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.
Because placing it here (or even as default in the register_cli_argument) results in the validator always failing because for N/A storage profiles, this has a value. There are a few ways to handle this:
-
have the validators inspect the namespace as it was provided and before defaults were applied. This is what I have always preferred but it would require infrastructure changes and got shot down.
-
Apply the "MarkSpecifiedAction" to these parameters. This appends "SET" to the value when it is supplied by the user but not when it is defaulted, which permits you to distinguish between the a default value and the user specifying the same value as the default. However, this has proven to introduce bugs and is basically a hacky workaround, so I don't want to do that. (I tried the implementation, it was messy, I removed it).
-
Make these None and then apply the default in-code and in the help text. While not ideal, it's the simplest fix. This is also the official guidance if a complex type is the default (for example a default dictionary or list) so it's not totally contrary to guidance.
-
Keep the defaults and don't validate. This means that if the user specifies a value that is N/A, it will be silently ignored. We've gone to great lengths to throw errors when users specify invalid parameter combinations, so, especially for our most complex command, this didn't seem like something to let slide.
Given those 4 options, I really prefer 1, but I' okay with (and chose) 3.
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 agree we can go with 3. This is the trade-off which might only be limited on VM/VMSS create
which have such large number of arguments
register_cli_argument('vm create', 'availability_set', help='Name or ID of an existing availability set to add the VM to. None by default.') | ||
register_cli_argument('vm create', 'managed_os_disk', options_list=('--attach-os-disk',), help='create VM by attaching to an existing managed OS disk', arg_group='Storage') | ||
register_cli_argument('vm create', 'storage_caching', help='Storage caching type for the VM OS disk. Default: ReadWrite.', arg_group='Storage', **enum_choice_list(['ReadWrite', 'ReadOnly'])) |
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. Why is default hard-coded in the help string?
@yugangw-msft, @derekbekoe comments addressed. Let me know what you think. |
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
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.
2 more feedback, the rest LGTM
# set default storage SKU if not provided and using an image based OS | ||
if not namespace.storage_sku and namespace.storage_profile \ | ||
not in [StorageProfile.ManagedSpecializedOSDisk, StorageProfile.SASpecializedOSDisk]: | ||
namespace.storage_sku = 'Standard_LRS' |
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 default one should be Premium_LRS
for VM and Standard_LRS
for VMSS. Yep, it was per discussion with CRP team 2 weeks ago
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.
Updated
logger = azlogging.get_az_logger(__name__) | ||
io = StringIO() | ||
pprint(template, io) | ||
logger.warning(io.getvalue()) |
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 a question, can we do json.dumps( indent=2)
and then give it to logger? Or `pprint' can format the text better?
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'll check it out and pick the better of the 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.
Yep, json.dumps looks better. Also, I changed the logging level to info so you have to use --verbose to see this. I thought that was more appropriate than displaying it by default or requiring --debug.
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.
There is a minor style check error you need to address before merge
Codecov Report
@@ Coverage Diff @@
## master #2256 +/- ##
==========================================
+ Coverage 72.95% 72.99% +0.03%
==========================================
Files 458 458
Lines 19499 19568 +69
Branches 2578 2765 +187
==========================================
+ Hits 14226 14283 +57
+ Misses 4390 4350 -40
- Partials 883 935 +52
Continue to review full report at Codecov.
|
Fixes #1961. Adds a storage profile for unmanaged specialized VHD. Refactors existing logic to (hopefully) be easier to maintain.