From de3d188cdd3f9f96e5067eb1b017be70542a7d0e Mon Sep 17 00:00:00 2001 From: Zim Kalinowski Date: Mon, 17 Dec 2018 13:21:25 +0800 Subject: [PATCH] support lro in azure_rm_resource (#49919) --- .../module_utils/azure_rm_common_rest.py | 33 +++++++++++++++-- .../modules/cloud/azure/azure_rm_resource.py | 33 +++++++++++++++-- .../cloud/azure/azure_rm_resource_facts.py | 2 +- .../targets/azure_rm_resource/tasks/main.yml | 35 ++++++++++++++++--- 4 files changed, 93 insertions(+), 10 deletions(-) diff --git a/lib/ansible/module_utils/azure_rm_common_rest.py b/lib/ansible/module_utils/azure_rm_common_rest.py index b1caa07f76c..4fd7eaa3b45 100644 --- a/lib/ansible/module_utils/azure_rm_common_rest.py +++ b/lib/ansible/module_utils/azure_rm_common_rest.py @@ -2,15 +2,23 @@ # # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +from ansible.module_utils.ansible_release import __version__ as ANSIBLE_VERSION + try: from msrestazure.azure_exceptions import CloudError from msrestazure.azure_configuration import AzureConfiguration from msrest.service_client import ServiceClient + from msrest.pipeline import ClientRawResponse + from msrest.polling import LROPoller + from msrestazure.polling.arm_polling import ARMPolling + import uuid import json except ImportError: # This is handled in azure_rm_common AzureConfiguration = object +ANSIBLE_USER_AGENT = 'Ansible/{0}'.format(ANSIBLE_VERSION) + class GenericRestClientConfiguration(AzureConfiguration): @@ -25,8 +33,7 @@ class GenericRestClientConfiguration(AzureConfiguration): super(GenericRestClientConfiguration, self).__init__(base_url) - self.add_user_agent('genericrestclient/1.0') - self.add_user_agent('Azure-SDK-For-Python') + self.add_user_agent(ANSIBLE_USER_AGENT) self.credentials = credentials self.subscription_id = subscription_id @@ -39,12 +46,17 @@ class GenericRestClient(object): self._client = ServiceClient(self.config.credentials, self.config) self.models = None - def query(self, url, method, query_parameters, header_parameters, body, expected_status_codes): + def query(self, url, method, query_parameters, header_parameters, body, expected_status_codes, polling_timeout, polling_interval): # Construct and send request operation_config = {} request = None + if header_parameters is None: + header_parameters = {} + + header_parameters['x-ms-client-request-id'] = str(uuid.uuid1()) + if method == 'GET': request = self._client.get(url, query_parameters) elif method == 'PUT': @@ -66,5 +78,20 @@ class GenericRestClient(object): exp = CloudError(response) exp.request_id = response.headers.get('x-ms-request-id') raise exp + elif response.status_code == 202 and polling_timeout > 0: + def get_long_running_output(response): + return response + poller = LROPoller(self._client, + ClientRawResponse(None, response), + get_long_running_output, + ARMPolling(polling_interval, **operation_config)) + response = self.get_poller_result(poller, polling_timeout) return response + + def get_poller_result(self, poller, timeout): + try: + poller.wait(timeout=timeout) + return poller.result() + except Exception as exc: + raise diff --git a/lib/ansible/modules/cloud/azure/azure_rm_resource.py b/lib/ansible/modules/cloud/azure/azure_rm_resource.py index e9c3ea2f663..954a35beb06 100644 --- a/lib/ansible/modules/cloud/azure/azure_rm_resource.py +++ b/lib/ansible/modules/cloud/azure/azure_rm_resource.py @@ -78,6 +78,18 @@ options: - If enabled, idempotency check will be done by using GET method first and then comparing with I(body) default: no type: bool + polling_timeout: + description: + - If enabled, idempotency check will be done by using GET method first and then comparing with I(body) + default: 0 + type: int + version_added: "2.8" + polling_interval: + description: + - If enabled, idempotency check will be done by using GET method first and then comparing with I(body) + default: 60 + type: int + version_added: "2.8" state: description: - Assert the state of the resource. Use C(present) to create or update resource or C(absent) to delete resource. @@ -171,6 +183,14 @@ class AzureRMResource(AzureRMModuleBase): type='bool', default=False ), + polling_timeout=dict( + type='int', + default=0 + ), + polling_interval=dict( + type='int', + default=60 + ), state=dict( type='str', default='present', @@ -195,6 +215,8 @@ class AzureRMResource(AzureRMModuleBase): self.method = None self.status_code = [] self.idempotency = False + self.polling_timeout = None + self.polling_interval = None self.state = None self.body = None super(AzureRMResource, self).__init__(self.module_arg_spec, supports_tags=False) @@ -249,7 +271,7 @@ class AzureRMResource(AzureRMModuleBase): response = None if self.idempotency: - original = self.mgmt_client.query(self.url, "GET", query_parameters, None, None, [200, 404]) + original = self.mgmt_client.query(self.url, "GET", query_parameters, None, None, [200, 404], 0, 0) if original.status_code == 404: if self.state == 'absent': @@ -262,7 +284,14 @@ class AzureRMResource(AzureRMModuleBase): pass if needs_update: - response = self.mgmt_client.query(self.url, self.method, query_parameters, header_parameters, self.body, self.status_code) + response = self.mgmt_client.query(self.url, + self.method, + query_parameters, + header_parameters, + self.body, + self.status_code, + self.polling_timeout, + self.polling_interval) if self.state == 'present': try: response = json.loads(response.text) diff --git a/lib/ansible/modules/cloud/azure/azure_rm_resource_facts.py b/lib/ansible/modules/cloud/azure/azure_rm_resource_facts.py index ddad963f774..eebce579791 100644 --- a/lib/ansible/modules/cloud/azure/azure_rm_resource_facts.py +++ b/lib/ansible/modules/cloud/azure/azure_rm_resource_facts.py @@ -190,7 +190,7 @@ class AzureRMResourceFacts(AzureRMModuleBase): header_parameters = {} header_parameters['Content-Type'] = 'application/json; charset=utf-8' - response = self.mgmt_client.query(self.url, "GET", query_parameters, header_parameters, None, [200, 404]) + response = self.mgmt_client.query(self.url, "GET", query_parameters, header_parameters, None, [200, 404], 0, 0) try: response = json.loads(response.text) diff --git a/test/integration/targets/azure_rm_resource/tasks/main.yml b/test/integration/targets/azure_rm_resource/tasks/main.yml index d068c8057a4..6eecfc10dec 100644 --- a/test/integration/targets/azure_rm_resource/tasks/main.yml +++ b/test/integration/targets/azure_rm_resource/tasks/main.yml @@ -2,6 +2,7 @@ set_fact: nsgname: "{{ resource_group | hash('md5') | truncate(7, True, '') }}{{ 1000 | random }}" storageaccountname: "stacc{{ resource_group | hash('md5') | truncate(7, True, '') }}{{ 1000 | random }}" + dbname: "mdb{{ resource_group | hash('md5') | truncate(7, True, '') }}{{ 1000 | random }}" run_once: yes - name: Call REST API @@ -64,11 +65,26 @@ resource_name: "{{ nsgname }}" register: output -- name: Create storage account for Registry - azure_rm_storageaccount: +- name: Create storage account that requires LRO polling + azure_rm_resource: + polling_timeout: 600 + polling_interval: 60 + api_version: '2018-07-01' resource_group: "{{ resource_group }}" - name: "{{ storageaccountname }}" - type: Standard_LRS + provider: Storage + resource_type: storageAccounts + resource_name: "{{ storageaccountname }}" + body: + sku: + name: Standard_GRS + kind: Storage + location: eastus + register: output + +- name: Assert that storage was successfully created + assert: + that: "output['response']['name'] == '{{ storageaccountname }}'" + - name: Try to storage keys -- special case when subresource part has no name azure_rm_resource: @@ -85,3 +101,14 @@ - name: Assert that key was returned assert: that: keys['response']['keys'][0]['value'] | length > 0 + +- name: Delete storage + azure_rm_resource: + polling_timeout: 600 + polling_interval: 60 + method: DELETE + api_version: '2018-07-01' + resource_group: "{{ resource_group }}" + provider: Storage + resource_type: storageAccounts + resource_name: "{{ storageaccountname }}"