From 7db4352f6e16fe3f28f8d5ffcdfdade8450b87ab Mon Sep 17 00:00:00 2001 From: Zim Kalinowski Date: Wed, 14 Nov 2018 16:57:03 +0800 Subject: [PATCH] Adding missing idempotence support in load balancer (#45548) idempotence --- .../cloud/azure/azure_rm_loadbalancer.py | 199 +++++------------- .../azure_rm_loadbalancer/tasks/main.yml | 54 ++++- 2 files changed, 110 insertions(+), 143 deletions(-) diff --git a/lib/ansible/modules/cloud/azure/azure_rm_loadbalancer.py b/lib/ansible/modules/cloud/azure/azure_rm_loadbalancer.py index a3af9f647c2..3039cf52320 100644 --- a/lib/ansible/modules/cloud/azure/azure_rm_loadbalancer.py +++ b/lib/ansible/modules/cloud/azure/azure_rm_loadbalancer.py @@ -667,26 +667,8 @@ class AzureRMLoadBalancer(AzureRMModuleBase): idle_timeout=self.idle_timeout, enable_floating_ip=False )] if self.protocol else None - if load_balancer: - # check update, NIE - changed = False - else: - changed = True - elif self.state == 'absent' and load_balancer: - changed = True - self.results['state'] = load_balancer_to_dict(load_balancer) - if 'tags' in self.results['state']: - update_tags, self.results['state']['tags'] = self.update_tags(self.results['state']['tags']) - if update_tags: - changed = True - else: - if self.tags: - changed = True - self.results['changed'] = changed - - if self.state == 'present' and changed: - # create or update + # create new load balancer structure early, so it can be easily compared frontend_ip_configurations_param = [self.network_models.FrontendIPConfiguration( name=item.get('name'), public_ip_address=self.get_public_ip_address_instance(item.get('public_ip_address')) if item.get('public_ip_address') else None, @@ -756,7 +738,7 @@ class AzureRMLoadBalancer(AzureRMModuleBase): enable_floating_ip=item.get('enable_floating_ip') ) for item in self.load_balancing_rules] if self.load_balancing_rules else None - param = self.network_models.LoadBalancer( + self.new_load_balancer = self.network_models.LoadBalancer( sku=self.network_models.LoadBalancerSku(self.sku) if self.sku else None, location=self.location, tags=self.tags, @@ -767,7 +749,31 @@ class AzureRMLoadBalancer(AzureRMModuleBase): load_balancing_rules=load_balancing_rules_param ) - self.results['state'] = self.create_or_update_load_balancer(param) + if load_balancer: + new_dict = self.new_load_balancer.as_dict() + if (self.location != load_balancer['location'] or + self.sku != load_balancer['sku']['name'] or + not default_compare(new_dict, load_balancer, '')): + changed = True + else: + changed = False + else: + changed = True + elif self.state == 'absent' and load_balancer: + changed = True + + self.results['state'] = load_balancer if load_balancer else {} + if 'tags' in self.results['state']: + update_tags, self.results['state']['tags'] = self.update_tags(self.results['state']['tags']) + if update_tags: + changed = True + else: + if self.tags: + changed = True + self.results['changed'] = changed + + if self.state == 'present' and changed: + self.results['state'] = self.create_or_update_load_balancer(self.new_load_balancer) elif self.state == 'absent' and changed: self.delete_load_balancer() self.results['state'] = None @@ -784,7 +790,7 @@ class AzureRMLoadBalancer(AzureRMModuleBase): """Get a load balancer""" self.log('Fetching loadbalancer {0}'.format(self.name)) try: - return self.network_client.load_balancers.get(self.resource_group, self.name) + return self.network_client.load_balancers.get(self.resource_group, self.name).as_dict() except CloudError: return None @@ -801,130 +807,39 @@ class AzureRMLoadBalancer(AzureRMModuleBase): try: poller = self.network_client.load_balancers.create_or_update(self.resource_group, self.name, param) new_lb = self.get_poller_result(poller) - return load_balancer_to_dict(new_lb) + return new_lb.as_dict() except CloudError as exc: self.fail("Error creating or updating load balancer {0} - {1}".format(self.name, str(exc))) -def load_balancer_to_dict(load_balancer): - """Seralialize a LoadBalancer object to a dict""" - if not load_balancer: - return dict() - - result = dict( - id=load_balancer.id, - name=load_balancer.name, - location=load_balancer.location, - sku=load_balancer.sku.name, - tags=load_balancer.tags, - provisioning_state=load_balancer.provisioning_state, - etag=load_balancer.etag, - frontend_ip_configurations=[], - backend_address_pools=[], - load_balancing_rules=[], - probes=[], - inbound_nat_rules=[], - inbound_nat_pools=[], - outbound_nat_rules=[] - ) - - if load_balancer.frontend_ip_configurations: - result['frontend_ip_configurations'] = [dict( - id=_.id, - name=_.name, - etag=_.etag, - provisioning_state=_.provisioning_state, - private_ip_address=_.private_ip_address, - private_ip_allocation_method=_.private_ip_allocation_method, - subnet=dict( - id=_.subnet.id, - name=_.subnet.name, - address_prefix=_.subnet.address_prefix - ) if _.subnet else None, - public_ip_address=dict( - id=_.public_ip_address.id, - location=_.public_ip_address.location, - public_ip_allocation_method=_.public_ip_address.public_ip_allocation_method, - ip_address=_.public_ip_address.ip_address - ) if _.public_ip_address else None - ) for _ in load_balancer.frontend_ip_configurations] - - if load_balancer.backend_address_pools: - result['backend_address_pools'] = [dict( - id=_.id, - name=_.name, - provisioning_state=_.provisioning_state, - etag=_.etag - ) for _ in load_balancer.backend_address_pools] - - if load_balancer.load_balancing_rules: - result['load_balancing_rules'] = [dict( - id=_.id, - name=_.name, - protocol=_.protocol, - frontend_ip_configuration_id=_.frontend_ip_configuration.id, - backend_address_pool_id=_.backend_address_pool.id, - probe_id=_.probe.id, - load_distribution=_.load_distribution, - frontend_port=_.frontend_port, - backend_port=_.backend_port, - idle_timeout_in_minutes=_.idle_timeout_in_minutes, - enable_floating_ip=_.enable_floating_ip, - provisioning_state=_.provisioning_state, - etag=_.etag - ) for _ in load_balancer.load_balancing_rules] - - if load_balancer.probes: - result['probes'] = [dict( - id=_.id, - name=_.name, - protocol=_.protocol, - port=_.port, - interval_in_seconds=_.interval_in_seconds, - number_of_probes=_.number_of_probes, - request_path=_.request_path, - provisioning_state=_.provisioning_state - ) for _ in load_balancer.probes] - - if load_balancer.inbound_nat_rules: - result['inbound_nat_rules'] = [dict( - id=_.id, - name=_.name, - frontend_ip_configuration_id=_.frontend_ip_configuration.id, - protocol=_.protocol, - frontend_port=_.frontend_port, - backend_port=_.backend_port, - idle_timeout_in_minutes=_.idle_timeout_in_minutes, - enable_floating_point_ip=_.enable_floating_point_ip if hasattr(_, 'enable_floating_point_ip') else False, - provisioning_state=_.provisioning_state, - etag=_.etag - ) for _ in load_balancer.inbound_nat_rules] - - if load_balancer.inbound_nat_pools: - result['inbound_nat_pools'] = [dict( - id=_.id, - name=_.name, - frontend_ip_configuration_id=_.frontend_ip_configuration.id, - protocol=_.protocol, - frontend_port_range_start=_.frontend_port_range_start, - frontend_port_range_end=_.frontend_port_range_end, - backend_port=_.backend_port, - provisioning_state=_.provisioning_state, - etag=_.etag - ) for _ in load_balancer.inbound_nat_pools] - - if load_balancer.outbound_nat_rules: - result['outbound_nat_rules'] = [dict( - id=_.id, - name=_.name, - allocated_outbound_ports=_.allocated_outbound_ports, - frontend_ip_configuration_id=_.frontend_ip_configuration.id, - backend_address_pool=_.backend_address_pool.id, - provisioning_state=_.provisioning_state, - etag=_.etag - ) for _ in load_balancer.outbound_nat_rules] - - return result +def default_compare(new, old, path): + if isinstance(new, dict): + if not isinstance(old, dict): + return False + for k in new.keys(): + if not default_compare(new.get(k), old.get(k, None), path + '/' + k): + return False + return True + elif isinstance(new, list): + if not isinstance(old, list) or len(new) != len(old): + return False + if isinstance(old[0], dict): + key = None + if 'id' in old[0] and 'id' in new[0]: + key = 'id' + elif 'name' in old[0] and 'name' in new[0]: + key = 'name' + new = sorted(new, key=lambda x: x.get(key, None)) + old = sorted(old, key=lambda x: x.get(key, None)) + else: + new = sorted(new) + old = sorted(old) + for i in range(len(new)): + if not default_compare(new[i], old[i], path + '/*'): + return False + return True + else: + return new == old def frontend_ip_configuration_id(subscription_id, resource_group_name, load_balancer_name, name): diff --git a/test/integration/targets/azure_rm_loadbalancer/tasks/main.yml b/test/integration/targets/azure_rm_loadbalancer/tasks/main.yml index b55f4b39e51..40babf4f4fe 100644 --- a/test/integration/targets/azure_rm_loadbalancer/tasks/main.yml +++ b/test/integration/targets/azure_rm_loadbalancer/tasks/main.yml @@ -85,7 +85,59 @@ assert: that: - output.changed - - output.state.sku == 'Standard' + - output.state.sku.name == 'Standard' + +- name: create load balancer again to check idempotency + azure_rm_loadbalancer: + resource_group: '{{ resource_group }}' + name: "{{ lbname_b }}" + sku: Standard + public_ip_address: "{{ pipbname }}" + probe_protocol: Tcp + probe_port: 80 + probe_interval: 10 + probe_fail_count: 3 + protocol: Tcp + load_distribution: Default + frontend_port: 80 + backend_port: 8080 + idle_timeout: 4 + natpool_frontend_port_start: 30 + natpool_frontend_port_end: 40 + natpool_backend_port: 80 + natpool_protocol: Tcp + register: output + +- name: assert that output has not changed + assert: + that: + - not output.changed + +- name: create load balancer again to check idempotency - change something + azure_rm_loadbalancer: + resource_group: '{{ resource_group }}' + name: "{{ lbname_b }}" + sku: Standard + public_ip_address: "{{ pipbname }}" + probe_protocol: Tcp + probe_port: 80 + probe_interval: 10 + probe_fail_count: 3 + protocol: Tcp + load_distribution: Default + frontend_port: 81 + backend_port: 8080 + idle_timeout: 4 + natpool_frontend_port_start: 30 + natpool_frontend_port_end: 40 + natpool_backend_port: 80 + natpool_protocol: Tcp + register: output + +- name: assert that output has changed + assert: + that: + - output.changed - name: delete load balancer azure_rm_loadbalancer: