From bc37ea96d59d2345ce2c5112cbceb6986d25d34a Mon Sep 17 00:00:00 2001 From: haiyuan_zhang Date: Thu, 16 Jan 2020 09:24:39 +0800 Subject: [PATCH] Vmss scale in policy (#66512) * azure_rm_virtualmachinescaleset: add scale_in_policy and terminate_event_notification * azure_rm_virtualmachinescalesetinstance: add vmss instance protection policy support --- lib/ansible/module_utils/azure_rm_common.py | 6 +- .../azure/azure_rm_virtualmachinescaleset.py | 102 +++++++++++++++--- ...azure_rm_virtualmachinescalesetinstance.py | 65 ++++++++++- packaging/requirements/requirements-azure.txt | 8 +- shippable.yml | 3 + .../targets/azure_rm_postgresqlserver/aliases | 2 +- .../tasks/main.yml | 1 + .../requirements/integration.cloud.azure.txt | 8 +- 8 files changed, 161 insertions(+), 34 deletions(-) diff --git a/lib/ansible/module_utils/azure_rm_common.py b/lib/ansible/module_utils/azure_rm_common.py index 129faba37ae..7e02dc686cf 100644 --- a/lib/ansible/module_utils/azure_rm_common.py +++ b/lib/ansible/module_utils/azure_rm_common.py @@ -936,7 +936,7 @@ class AzureRMModuleBase(object): if not self._network_client: self._network_client = self.get_mgmt_svc_client(NetworkManagementClient, base_url=self._cloud_environment.endpoints.resource_manager, - api_version='2018-08-01') + api_version='2019-06-01') return self._network_client @property @@ -964,13 +964,13 @@ class AzureRMModuleBase(object): if not self._compute_client: self._compute_client = self.get_mgmt_svc_client(ComputeManagementClient, base_url=self._cloud_environment.endpoints.resource_manager, - api_version='2018-06-01') + api_version='2019-07-01') return self._compute_client @property def compute_models(self): self.log("Getting compute models") - return ComputeManagementClient.models("2018-06-01") + return ComputeManagementClient.models("2019-07-01") @property def dns_client(self): diff --git a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachinescaleset.py b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachinescaleset.py index 7dfcf806160..acdf49b2257 100644 --- a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachinescaleset.py +++ b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachinescaleset.py @@ -241,6 +241,19 @@ options: U(https://docs.microsoft.com/en-us/azure/virtual-machines/linux/using-cloud-init#cloud-init-overview), follow these steps U(https://docs.microsoft.com/en-us/azure/virtual-machines/linux/cloudinit-prepare-custom-image). version_added: "2.8" + scale_in_policy: + description: + - define the order in which vmss instances are scaled-in + choices: + - Default + - NewestVM + - OldestVM + version_added: "2.10" + terminate_event_timeout_minutes: + description: + - timeout time for termination notification event + - in range between 5 and 15 + version_added: "2.10" extends_documentation_fragment: - azure @@ -261,6 +274,8 @@ EXAMPLES = ''' virtual_network_name: testvnet upgrade_policy: Manual subnet_name: testsubnet + terminate_event_timeout_minutes: 10 + scale_in_policy: NewestVM admin_username: adminUser ssh_password_enabled: false ssh_public_keys: @@ -364,6 +379,11 @@ azure_vmss: sample: { "properties": { "overprovision": true, + "scaleInPolicy": { + "rules": [ + "NewestVM" + ] + }, "singlePlacementGroup": true, "upgradePolicy": { "mode": "Manual" @@ -410,6 +430,12 @@ azure_vmss: }, "secrets": [] }, + "scheduledEventsProfile": { + "terminateNotificationProfile": { + "enable": true, + "notBeforeTimeout": "PT10M" + } + }, "storageProfile": { "dataDisks": [ { @@ -448,8 +474,6 @@ azure_vmss: } ''' # NOQA -import random -import re import base64 try: @@ -508,6 +532,8 @@ class AzureRMVirtualMachineScaleSet(AzureRMModuleBase): plan=dict(type='dict', options=dict(publisher=dict(type='str', required=True), product=dict(type='str', required=True), name=dict(type='str', required=True), promotion_code=dict(type='str'))), + scale_in_policy=dict(type='str', choices=['Default', 'OldestVM', 'NewestVM']), + terminate_event_timeout_minutes=dict(type='int') ) self.resource_group = None @@ -542,11 +568,8 @@ class AzureRMVirtualMachineScaleSet(AzureRMModuleBase): self.zones = None self.custom_data = None self.plan = None - - required_if = [ - ('state', 'present', [ - 'vm_size']) - ] + self.scale_in_policy = None + self.terminate_event_timeout_minutes = None mutually_exclusive = [('load_balancer', 'application_gateway')] self.results = dict( @@ -558,13 +581,10 @@ class AzureRMVirtualMachineScaleSet(AzureRMModuleBase): super(AzureRMVirtualMachineScaleSet, self).__init__( derived_arg_spec=self.module_arg_spec, supports_check_mode=True, - required_if=required_if, mutually_exclusive=mutually_exclusive) def exec_module(self, **kwargs): - nsg = None - for key in list(self.module_arg_spec.keys()) + ['tags']: setattr(self, key, kwargs[key]) @@ -585,11 +605,8 @@ class AzureRMVirtualMachineScaleSet(AzureRMModuleBase): results = dict() vmss = None disable_ssh_password = None - vmss_dict = None - virtual_network = None subnet = None image_reference = None - custom_image = False load_balancer_backend_address_pools = None load_balancer_inbound_nat_pools = None load_balancer = None @@ -737,6 +754,27 @@ class AzureRMVirtualMachineScaleSet(AzureRMModuleBase): changed = True vmss_dict['zones'] = self.zones + if self.terminate_event_timeout_minutes: + timeout = self.terminate_event_timeout_minutes + if timeout < 5 or timeout > 15: + self.fail("terminate_event_timeout_minutes should >= 5 and <= 15") + iso_8601_format = "PT" + str(timeout) + "M" + old = vmss_dict['properties']['virtualMachineProfile'].get('scheduledEventsProfile', {}).\ + get('terminateNotificationProfile', {}).get('notBeforeTimeout', "") + if old != iso_8601_format: + differences.append('terminateNotification') + changed = True + vmss_dict['properties']['virtualMachineProfile'].setdefault('scheduledEventsProfile', {})['terminateNotificationProfile'] = { + 'notBeforeTimeout': iso_8601_format, + "enable": 'true' + } + + if self.scale_in_policy and self.scale_in_policy != vmss_dict['properties'].get('scaleInPolicy', {}).get('rules', [""])[0]: + self.log("CHANGED: virtual machine sale sets {0} scale in policy".format(self.name)) + differences.append('scaleInPolicy') + changed = True + vmss_dict['properties'].setdefault('scaleInPolicy', {})['rules'] = [self.scale_in_policy] + nicConfigs = vmss_dict['properties']['virtualMachineProfile']['networkProfile']['networkInterfaceConfigurations'] backend_address_pool = nicConfigs[0]['properties']['ipConfigurations'][0]['properties'].get('loadBalancerBackendAddressPools', []) @@ -752,7 +790,7 @@ class AzureRMVirtualMachineScaleSet(AzureRMModuleBase): lb_or_ag_id = "{0}/".format(application_gateway.id) backend_address_pool_id = backend_address_pool[0].get('id') - if bool(lb_or_ag_id) != bool(backend_address_pool_id) or not backend_address_pool_id.startswith(lb_or_ag_id): + if lb_or_ag_id is not None and (bool(lb_or_ag_id) != bool(backend_address_pool_id) or not backend_address_pool_id.startswith(lb_or_ag_id)): differences.append('load_balancer') changed = True @@ -785,6 +823,9 @@ class AzureRMVirtualMachineScaleSet(AzureRMModuleBase): if self.state == 'present': if not vmss: # Create the VMSS + if self.vm_size is None: + self.fail("vm size must be set") + self.log("Create virtual machine scale set {0}".format(self.name)) self.results['actions'].append('Created VMSS {0}'.format(self.name)) @@ -793,9 +834,7 @@ class AzureRMVirtualMachineScaleSet(AzureRMModuleBase): self.fail("Parameter error: ssh_public_keys required when disabling SSH password.") if not self.virtual_network_name: - default_vnet = self.create_default_vnet() - virtual_network = default_vnet.id - self.virtual_network_name = default_vnet.name + self.fail("virtual network name is required") if self.subnet_name: subnet = self.get_subnet(self.virtual_network_name, self.subnet_name) @@ -877,6 +916,12 @@ class AzureRMVirtualMachineScaleSet(AzureRMModuleBase): zones=self.zones ) + if self.scale_in_policy: + vmss_resource.scale_in_policy = self.gen_scale_in_policy() + + if self.terminate_event_timeout_minutes: + vmss_resource.virtual_machine_profile.scheduled_events_profile = self.gen_scheduled_event_profile() + if self.admin_password: vmss_resource.virtual_machine_profile.os_profile.admin_password = self.admin_password @@ -972,6 +1017,12 @@ class AzureRMVirtualMachineScaleSet(AzureRMModuleBase): )) vmss_resource.virtual_machine_profile.storage_profile.data_disks = data_disks + if self.scale_in_policy: + vmss_resource.scale_in_policy = self.gen_scale_in_policy() + + if self.terminate_event_timeout_minutes: + vmss_resource.virtual_machine_profile.scheduled_events_profile = self.gen_scheduled_event_profile() + if image_reference is not None: vmss_resource.virtual_machine_profile.storage_profile.image_reference = image_reference self.log("Update virtual machine with parameters:") @@ -1138,6 +1189,23 @@ class AzureRMVirtualMachineScaleSet(AzureRMModuleBase): name = azure_id_to_dict(id).get('name') return dict(id=id, name=name) + def gen_scheduled_event_profile(self): + if self.terminate_event_timeout_minutes is None: + return None + + scheduledEventProfile = self.compute_models.ScheduledEventsProfile() + terminationProfile = self.compute_models.TerminateNotificationProfile() + terminationProfile.not_before_timeout = "PT" + str(self.terminate_event_timeout_minutes) + "M" + terminationProfile.enable = True + scheduledEventProfile.terminate_notification_profile = terminationProfile + return scheduledEventProfile + + def gen_scale_in_policy(self): + if self.scale_in_policy is None: + return None + + return self.compute_models.ScaleInPolicy(rules=[self.scale_in_policy]) + def main(): AzureRMVirtualMachineScaleSet() diff --git a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachinescalesetinstance.py b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachinescalesetinstance.py index 966a88f83c1..6f4a6f5c39b 100644 --- a/lib/ansible/modules/cloud/azure/azure_rm_virtualmachinescalesetinstance.py +++ b/lib/ansible/modules/cloud/azure/azure_rm_virtualmachinescalesetinstance.py @@ -41,11 +41,20 @@ options: power_state: description: - Use this option to change power state of the instance. - required: True choices: - 'running' - 'stopped' - 'deallocated' + protect_from_scale_in: + type: bool + description: + - turn on/off instance protection from scale in + version_added: "2.10" + protect_from_scale_set_actions: + type: bool + description: + - tun on/off instance protection from scale set actions + version_added: "2.10" state: description: - State of the VMSS instance. Use C(present) to update an instance and C(absent) to delete an instance. @@ -69,6 +78,13 @@ EXAMPLES = ''' vmss_name: myVMSS instance_id: "2" latest_model: yes + + - name: Turn on protect from scale in + azure_rm_virtualmachinescalesetinstance: + resource_group: myResourceGroup + vmss_name: myVMSS + instance_id: "2" + protect_from_scale_in: true ''' RETURN = ''' @@ -119,6 +135,12 @@ class AzureRMVirtualMachineScaleSetInstance(AzureRMModuleBase): type='str', choices=['running', 'stopped', 'deallocated'] ), + protect_from_scale_in=dict( + type='bool' + ), + protect_from_scale_set_actions=dict( + type='bool' + ), state=dict( type='str', default='present', @@ -136,13 +158,16 @@ class AzureRMVirtualMachineScaleSetInstance(AzureRMModuleBase): self.latest_model = None self.power_state = None self.state = None + self.protect_from_scale_in = None + self.protect_from_scale_set_actions = None super(AzureRMVirtualMachineScaleSetInstance, self).__init__(self.module_arg_spec, supports_tags=False) def exec_module(self, **kwargs): for key in self.module_arg_spec: setattr(self, key, kwargs[key]) self.mgmt_client = self.get_mgmt_svc_client(ComputeManagementClient, - base_url=self._cloud_environment.endpoints.resource_manager) + base_url=self._cloud_environment.endpoints.resource_manager, + api_version='2019-07-01') instances = self.get() @@ -175,6 +200,14 @@ class AzureRMVirtualMachineScaleSetInstance(AzureRMModuleBase): if not self.check_mode: self.start(item['instance_id']) self.results['changed'] = True + if self.protect_from_scale_in is not None or self.protect_from_scale_set_actions is not None: + for item in instances: + protection_policy = item['protection_policy'] + if protection_policy is None or self.protect_from_scale_in != protection_policy['protect_from_scale_in'] or \ + self.protect_from_scale_set_actions != protection_policy['protect_from_scale_set_actions']: + if not self.check_mode: + self.update_protection_policy(self.instance_id, self.protect_from_scale_in, self.protect_from_scale_set_actions) + self.results['changed'] = True self.results['instances'] = [{'id': item['id']} for item in instances] return self.results @@ -202,8 +235,8 @@ class AzureRMVirtualMachineScaleSetInstance(AzureRMModuleBase): instance_ids=[instance_id]) self.get_poller_result(poller) except CloudError as exc: - self.log("Error applying latest model {0} - {1}".format(self.name, str(exc))) - self.fail("Error applying latest model {0} - {1}".format(self.name, str(exc))) + self.log("Error applying latest model {0} - {1}".format(self.vmss_name, str(exc))) + self.fail("Error applying latest model {0} - {1}".format(self.vmss_name, str(exc))) def delete(self, instance_id): try: @@ -241,6 +274,27 @@ class AzureRMVirtualMachineScaleSetInstance(AzureRMModuleBase): self.log('Could not deallocate instance of Virtual Machine Scale Set VM.') self.fail('Could not deallocate instance of Virtual Machine Scale Set VM.') + def update_protection_policy(self, instance_id, protect_from_scale_in, protect_from_scale_set_actions): + try: + d = {} + if protect_from_scale_in is not None: + d['protect_from_scale_in'] = protect_from_scale_in + if protect_from_scale_set_actions is not None: + d['protect_from_scale_set_actions'] = protect_from_scale_set_actions + protection_policy = self.compute_models.VirtualMachineScaleSetVMProtectionPolicy(**d) + instance = self.mgmt_client.virtual_machine_scale_set_vms.get(resource_group_name=self.resource_group, + vm_scale_set_name=self.vmss_name, + instance_id=instance_id) + instance.protection_policy = protection_policy + poller = self.mgmt_client.virtual_machine_scale_set_vms.update(resource_group_name=self.resource_group, + vm_scale_set_name=self.vmss_name, + instance_id=instance_id, + parameters=instance) + self.get_poller_result(poller) + except CloudError as e: + self.log('Could not update instance protection policy.') + self.fail('Could not update instance protection policy.') + def format_response(self, item): d = item.as_dict() iv = self.mgmt_client.virtual_machine_scale_set_vms.get_instance_view(resource_group_name=self.resource_group, @@ -257,7 +311,8 @@ class AzureRMVirtualMachineScaleSetInstance(AzureRMModuleBase): 'tags': d.get('tags'), 'instance_id': d.get('instance_id'), 'latest_model': d.get('latest_model_applied'), - 'power_state': power_state + 'power_state': power_state, + 'protection_policy': d.get('protection_policy') } return d diff --git a/packaging/requirements/requirements-azure.txt b/packaging/requirements/requirements-azure.txt index 37f112f0258..6df1a4e8277 100644 --- a/packaging/requirements/requirements-azure.txt +++ b/packaging/requirements/requirements-azure.txt @@ -7,7 +7,7 @@ azure-common==1.1.11 azure-mgmt-authorization==0.51.1 azure-mgmt-batch==5.0.1 azure-mgmt-cdn==3.0.0 -azure-mgmt-compute==4.4.0 +azure-mgmt-compute==10.0.0 azure-mgmt-containerinstance==1.4.0 azure-mgmt-containerregistry==2.0.0 azure-mgmt-containerservice==4.4.0 @@ -15,7 +15,7 @@ azure-mgmt-dns==2.1.0 azure-mgmt-keyvault==1.1.0 azure-mgmt-marketplaceordering==0.1.0 azure-mgmt-monitor==0.5.2 -azure-mgmt-network==2.3.0 +azure-mgmt-network==4.0.0 azure-mgmt-nspkg==2.0.0 azure-mgmt-redis==5.0.0 azure-mgmt-resource==2.1.0 @@ -27,8 +27,8 @@ azure-mgmt-trafficmanager==0.50.0 azure-mgmt-web==0.41.0 azure-nspkg==2.0.0 azure-storage==0.35.1 -msrest==0.6.1 -msrestazure==0.5.0 +msrest==0.6.10 +msrestazure==0.6.2 azure-keyvault==1.0.0a1 azure-graphrbac==0.40.0 azure-mgmt-cosmosdb==0.5.2 diff --git a/shippable.yml b/shippable.yml index cfd25f0051f..bba21c80b0e 100644 --- a/shippable.yml +++ b/shippable.yml @@ -161,6 +161,9 @@ matrix: - env: T=azure/2.7/10 - env: T=azure/3.6/10 + - env: T=azure/2.7/11 + - env: T=azure/3.6/11 + - env: T=vcenter/2.7/1 - env: T=vcenter/3.6/1 diff --git a/test/integration/targets/azure_rm_postgresqlserver/aliases b/test/integration/targets/azure_rm_postgresqlserver/aliases index 573a6a6748b..ad065181b35 100644 --- a/test/integration/targets/azure_rm_postgresqlserver/aliases +++ b/test/integration/targets/azure_rm_postgresqlserver/aliases @@ -1,6 +1,6 @@ cloud/azure destructive -shippable/azure/group8 +shippable/azure/group11 azure_rm_postgresqlserver_facts azure_rm_postgresqldatabase azure_rm_postgresqldatabase_facts diff --git a/test/integration/targets/azure_rm_virtualmachinescaleset/tasks/main.yml b/test/integration/targets/azure_rm_virtualmachinescaleset/tasks/main.yml index 84842023f46..67a0b938020 100644 --- a/test/integration/targets/azure_rm_virtualmachinescaleset/tasks/main.yml +++ b/test/integration/targets/azure_rm_virtualmachinescaleset/tasks/main.yml @@ -163,6 +163,7 @@ disk_size_gb: 64 caching: ReadWrite managed_disk_type: Standard_LRS + scale_in_policy: "NewestVM" register: results - name: Assert that VMSS was created diff --git a/test/lib/ansible_test/_data/requirements/integration.cloud.azure.txt b/test/lib/ansible_test/_data/requirements/integration.cloud.azure.txt index 37f112f0258..6df1a4e8277 100644 --- a/test/lib/ansible_test/_data/requirements/integration.cloud.azure.txt +++ b/test/lib/ansible_test/_data/requirements/integration.cloud.azure.txt @@ -7,7 +7,7 @@ azure-common==1.1.11 azure-mgmt-authorization==0.51.1 azure-mgmt-batch==5.0.1 azure-mgmt-cdn==3.0.0 -azure-mgmt-compute==4.4.0 +azure-mgmt-compute==10.0.0 azure-mgmt-containerinstance==1.4.0 azure-mgmt-containerregistry==2.0.0 azure-mgmt-containerservice==4.4.0 @@ -15,7 +15,7 @@ azure-mgmt-dns==2.1.0 azure-mgmt-keyvault==1.1.0 azure-mgmt-marketplaceordering==0.1.0 azure-mgmt-monitor==0.5.2 -azure-mgmt-network==2.3.0 +azure-mgmt-network==4.0.0 azure-mgmt-nspkg==2.0.0 azure-mgmt-redis==5.0.0 azure-mgmt-resource==2.1.0 @@ -27,8 +27,8 @@ azure-mgmt-trafficmanager==0.50.0 azure-mgmt-web==0.41.0 azure-nspkg==2.0.0 azure-storage==0.35.1 -msrest==0.6.1 -msrestazure==0.5.0 +msrest==0.6.10 +msrestazure==0.6.2 azure-keyvault==1.0.0a1 azure-graphrbac==0.40.0 azure-mgmt-cosmosdb==0.5.2