1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4# Copyright: (c) 2016, Thomas Stringer <tomstr@microsoft.com>
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10
11ANSIBLE_METADATA = {'metadata_version': '1.1',
12                    'status': ['preview'],
13                    'supported_by': 'community'}
14
15
16DOCUMENTATION = '''
17---
18module: azure_rm_loadbalancer
19
20version_added: "2.4"
21
22short_description: Manage Azure load balancers
23
24description:
25    - Create, update and delete Azure load balancers.
26
27options:
28    resource_group:
29        description:
30            - Name of a resource group where the load balancer exists or will be created.
31        required: true
32    name:
33        description:
34            - Name of the load balancer.
35        required: true
36    state:
37        description:
38            - Assert the state of the load balancer. Use C(present) to create/update a load balancer, or C(absent) to delete one.
39        default: present
40        choices:
41            - absent
42            - present
43    location:
44        description:
45            - Valid Azure location. Defaults to location of the resource group.
46    sku:
47        description:
48            - The load balancer SKU.
49        choices:
50            - Basic
51            - Standard
52        version_added: '2.6'
53    frontend_ip_configurations:
54        description:
55            - List of frontend IPs to be used.
56        suboptions:
57            name:
58                description:
59                    - Name of the frontend ip configuration.
60                required: True
61            public_ip_address:
62                description:
63                    - Name of an existing public IP address object in the current resource group to associate with the security group.
64            private_ip_address:
65                description:
66                    - The reference of the Public IP resource.
67                version_added: '2.6'
68            private_ip_allocation_method:
69                description:
70                    - The Private IP allocation method.
71                choices:
72                    - Static
73                    - Dynamic
74                version_added: '2.6'
75            subnet:
76                description:
77                    - The reference of the subnet resource.
78                    - Should be an existing subnet's resource id.
79                version_added: '2.6'
80        version_added: '2.5'
81    backend_address_pools:
82        description:
83            - List of backend address pools.
84        suboptions:
85            name:
86                description:
87                    - Name of the backend address pool.
88                required: True
89        version_added: '2.5'
90    probes:
91        description:
92            - List of probe definitions used to check endpoint health.
93        suboptions:
94            name:
95                description:
96                    - Name of the probe.
97                required: True
98            port:
99                description:
100                    - Probe port for communicating the probe. Possible values range from 1 to 65535, inclusive.
101                required: True
102            protocol:
103                description:
104                    - The protocol of the end point to be probed.
105                    - If C(Tcp) is specified, a received ACK is required for the probe to be successful.
106                    - If C(Http) or C(Https) is specified, a 200 OK response from the specified URL is required for the probe to be successful.
107                choices:
108                    - Tcp
109                    - Http
110                    - Https
111            interval:
112                description:
113                    - The interval, in seconds, for how frequently to probe the endpoint for health status.
114                    - Slightly less than half the allocated timeout period, which allows two full probes before taking the instance out of rotation.
115                    - The default value is C(15), the minimum value is C(5).
116                default: 15
117            fail_count:
118                description:
119                    - The number of probes where if no response, will result in stopping further traffic from being delivered to the endpoint.
120                    - This values allows endpoints to be taken out of rotation faster or slower than the typical times used in Azure.
121                default: 3
122                aliases:
123                    - number_of_probes
124            request_path:
125                description:
126                    - The URI used for requesting health status from the VM.
127                    - Path is required if I(protocol=Http) or I(protocol=Https). Otherwise, it is not allowed.
128        version_added: '2.5'
129    inbound_nat_pools:
130        description:
131            - Defines an external port range for inbound NAT to a single backend port on NICs associated with a load balancer.
132            - Inbound NAT rules are created automatically for each NIC associated with the Load Balancer using an external port from this range.
133            - Defining an Inbound NAT pool on your Load Balancer is mutually exclusive with defining inbound Nat rules.
134            - Inbound NAT pools are referenced from virtual machine scale sets.
135            - NICs that are associated with individual virtual machines cannot reference an inbound NAT pool.
136            - They have to reference individual inbound NAT rules.
137        suboptions:
138            name:
139                description:
140                    - Name of the inbound NAT pool.
141                required: True
142            frontend_ip_configuration_name:
143                description:
144                    - A reference to frontend IP addresses.
145                required: True
146            protocol:
147                description:
148                    - IP protocol for the NAT pool.
149                choices:
150                    - Tcp
151                    - Udp
152                    - All
153            frontend_port_range_start:
154                description:
155                    - The first port in the range of external ports that will be used to provide inbound NAT to NICs associated with the load balancer.
156                    - Acceptable values range between 1 and 65534.
157                required: True
158            frontend_port_range_end:
159                description:
160                    - The last port in the range of external ports that will be used to provide inbound NAT to NICs associated with the load balancer.
161                    - Acceptable values range between 1 and 65535.
162                required: True
163            backend_port:
164                description:
165                    - The port used for internal connections on the endpoint.
166                    - Acceptable values are between 1 and 65535.
167        version_added: '2.5'
168    load_balancing_rules:
169        description:
170            - Object collection representing the load balancing rules Gets the provisioning.
171        suboptions:
172            name:
173                description:
174                    - Name of the load balancing rule.
175                required: True
176            frontend_ip_configuration:
177                description:
178                    - A reference to frontend IP addresses.
179                required: True
180            backend_address_pool:
181                description:
182                    - A reference to a pool of DIPs. Inbound traffic is randomly load balanced across IPs in the backend IPs.
183                required: True
184            probe:
185                description:
186                    - The name of the load balancer probe this rule should use for health checks.
187                required: True
188            protocol:
189                description:
190                    - IP protocol for the load balancing rule.
191                choices:
192                    - Tcp
193                    - Udp
194                    - All
195            load_distribution:
196                description:
197                    - The session persistence policy for this rule; C(Default) is no persistence.
198                choices:
199                    - Default
200                    - SourceIP
201                    - SourceIPProtocol
202                default: Default
203            frontend_port:
204                description:
205                    - The port for the external endpoint.
206                    - Frontend port numbers must be unique across all rules within the load balancer.
207                    - Acceptable values are between 0 and 65534.
208                    - Note that value 0 enables "Any Port".
209            backend_port:
210                description:
211                    - The port used for internal connections on the endpoint.
212                    - Acceptable values are between 0 and 65535.
213                    - Note that value 0 enables "Any Port".
214            idle_timeout:
215                description:
216                    - The timeout for the TCP idle connection.
217                    - The value can be set between 4 and 30 minutes.
218                    - The default value is C(4) minutes.
219                    - This element is only used when the protocol is set to TCP.
220            enable_floating_ip:
221                description:
222                    - Configures SNAT for the VMs in the backend pool to use the publicIP address specified in the frontend of the load balancing rule.
223        version_added: '2.5'
224    inbound_nat_rules:
225        description:
226            - Collection of inbound NAT Rules used by a load balancer.
227            - Defining inbound NAT rules on your load balancer is mutually exclusive with defining an inbound NAT pool.
228            - Inbound NAT pools are referenced from virtual machine scale sets.
229            - NICs that are associated with individual virtual machines cannot reference an Inbound NAT pool.
230            - They have to reference individual inbound NAT rules.
231        suboptions:
232            name:
233                description:
234                    - name of the inbound nat rule.
235                required: True
236            frontend_ip_configuration:
237                description:
238                    - A reference to frontend IP addresses.
239                required: True
240            protocol:
241                description:
242                    - IP protocol for the inbound nat rule.
243                choices:
244                    - Tcp
245                    - Udp
246                    - All
247            frontend_port:
248                description:
249                    - The port for the external endpoint.
250                    - Frontend port numbers must be unique across all rules within the load balancer.
251                    - Acceptable values are between 0 and 65534.
252                    - Note that value 0 enables "Any Port".
253            backend_port:
254                description:
255                    - The port used for internal connections on the endpoint.
256                    - Acceptable values are between 0 and 65535.
257                    - Note that value 0 enables "Any Port".
258            idle_timeout:
259                description:
260                    - The timeout for the TCP idle connection.
261                    - The value can be set between 4 and 30 minutes.
262                    - The default value is C(4) minutes.
263                    - This element is only used when I(protocol=Tcp).
264            enable_floating_ip:
265                description:
266                    - Configures a virtual machine's endpoint for the floating IP capability required to configure a SQL AlwaysOn Availability Group.
267                    - This setting is required when using the SQL AlwaysOn Availability Groups in SQL server.
268                    - This setting can't be changed after you create the endpoint.
269            enable_tcp_reset:
270                description:
271                    - Receive bidirectional TCP Reset on TCP flow idle timeout or unexpected connection termination.
272                    - This element is only used when I(protocol=Tcp).
273        version_added: '2.8'
274    public_ip_address_name:
275        description:
276            - (deprecated) Name of an existing public IP address object to associate with the security group.
277            - This option has been deprecated, and will be removed in 2.9. Use I(frontend_ip_configurations) instead.
278        aliases:
279            - public_ip_address
280            - public_ip_name
281            - public_ip
282    probe_port:
283        description:
284            - (deprecated) The port that the health probe will use.
285            - This option has been deprecated, and will be removed in 2.9. Use I(probes) instead.
286    probe_protocol:
287        description:
288            - (deprecated) The protocol to use for the health probe.
289            - This option has been deprecated, and will be removed in 2.9. Use I(probes) instead.
290        choices:
291            - Tcp
292            - Http
293            - Https
294    probe_interval:
295        description:
296            - (deprecated) Time (in seconds) between endpoint health probes.
297            - This option has been deprecated, and will be removed in 2.9. Use I(probes) instead.
298        default: 15
299    probe_fail_count:
300        description:
301            - (deprecated) The amount of probe failures for the load balancer to make a health determination.
302            - This option has been deprecated, and will be removed in 2.9. Use I(probes) instead.
303        default: 3
304    probe_request_path:
305        description:
306            - (deprecated) The URL that an HTTP probe or HTTPS probe will use (only relevant if I(probe_protocol=Http) or I(probe_protocol=Https)).
307            - This option has been deprecated, and will be removed in 2.9. Use I(probes) instead.
308    protocol:
309        description:
310            - (deprecated) The protocol (TCP or UDP) that the load balancer will use.
311            - This option has been deprecated, and will be removed in 2.9. Use I(load_balancing_rules) instead.
312        choices:
313            - Tcp
314            - Udp
315    load_distribution:
316        description:
317            - (deprecated) The type of load distribution that the load balancer will employ.
318            - This option has been deprecated, and will be removed in 2.9. Use I(load_balancing_rules) instead.
319        choices:
320            - Default
321            - SourceIP
322            - SourceIPProtocol
323    frontend_port:
324        description:
325            - (deprecated) Frontend port that will be exposed for the load balancer.
326            - This option has been deprecated, and will be removed in 2.9. Use I(load_balancing_rules) instead.
327    backend_port:
328        description:
329            - (deprecated) Backend port that will be exposed for the load balancer.
330            - This option has been deprecated, and will be removed in 2.9. Use I(load_balancing_rules) instead.
331    idle_timeout:
332        description:
333            - (deprecated) Timeout for TCP idle connection in minutes.
334            - This option has been deprecated, and will be removed in 2.9. Use I(load_balancing_rules) instead.
335        default: 4
336    natpool_frontend_port_start:
337        description:
338            - (deprecated) Start of the port range for a NAT pool.
339            - This option has been deprecated, and will be removed in 2.9. Use I(inbound_nat_pools) instead.
340    natpool_frontend_port_end:
341        description:
342            - (deprecated) End of the port range for a NAT pool.
343            - This option has been deprecated, and will be removed in 2.9. Use I(inbound_nat_pools) instead.
344    natpool_backend_port:
345        description:
346            - (deprecated) Backend port used by the NAT pool.
347            - This option has been deprecated, and will be removed in 2.9. Use I(inbound_nat_pools) instead.
348    natpool_protocol:
349        description:
350            - (deprecated) The protocol for the NAT pool.
351            - This option has been deprecated, and will be removed in 2.9. Use I(inbound_nat_pools) instead.
352extends_documentation_fragment:
353    - azure
354    - azure_tags
355
356author:
357    - Thomas Stringer (@trstringer)
358    - Yuwei Zhou (@yuwzho)
359'''
360
361EXAMPLES = '''
362- name: create load balancer
363  azure_rm_loadbalancer:
364    resource_group: myResourceGroup
365    name: testloadbalancer1
366    frontend_ip_configurations:
367      - name: frontendipconf0
368        public_ip_address: testpip
369    backend_address_pools:
370      - name: backendaddrpool0
371    probes:
372      - name: prob0
373        port: 80
374    inbound_nat_pools:
375      - name: inboundnatpool0
376        frontend_ip_configuration_name: frontendipconf0
377        protocol: Tcp
378        frontend_port_range_start: 80
379        frontend_port_range_end: 81
380        backend_port: 8080
381    load_balancing_rules:
382      - name: lbrbalancingrule0
383        frontend_ip_configuration: frontendipconf0
384        backend_address_pool: backendaddrpool0
385        frontend_port: 80
386        backend_port: 80
387        probe: prob0
388    inbound_nat_rules:
389      - name: inboundnatrule0
390        backend_port: 8080
391        protocol: Tcp
392        frontend_port: 8080
393        frontend_ip_configuration: frontendipconf0
394'''
395
396RETURN = '''
397state:
398    description:
399        - Current state of the load balancer.
400    returned: always
401    type: dict
402changed:
403    description:
404        - Whether or not the resource has changed.
405    returned: always
406    type: bool
407'''
408
409import random
410from ansible.module_utils.azure_rm_common import AzureRMModuleBase, format_resource_id
411from ansible.module_utils._text import to_native
412try:
413    from msrestazure.tools import parse_resource_id
414    from msrestazure.azure_exceptions import CloudError
415except ImportError:
416    # This is handled in azure_rm_common
417    pass
418
419
420frontend_ip_configuration_spec = dict(
421    name=dict(
422        type='str',
423        required=True
424    ),
425    public_ip_address=dict(
426        type='str'
427    ),
428    private_ip_address=dict(
429        type='str'
430    ),
431    private_ip_allocation_method=dict(
432        type='str'
433    ),
434    subnet=dict(
435        type='str'
436    )
437)
438
439
440backend_address_pool_spec = dict(
441    name=dict(
442        type='str',
443        required=True
444    )
445)
446
447
448probes_spec = dict(
449    name=dict(
450        type='str',
451        required=True
452    ),
453    port=dict(
454        type='int',
455        required=True
456    ),
457    protocol=dict(
458        type='str',
459        choices=['Tcp', 'Http', 'Https']
460    ),
461    interval=dict(
462        type='int',
463        default=15
464    ),
465    fail_count=dict(
466        type='int',
467        default=3,
468        aliases=['number_of_probes']
469    ),
470    request_path=dict(
471        type='str'
472    )
473)
474
475
476inbound_nat_pool_spec = dict(
477    name=dict(
478        type='str',
479        required=True
480    ),
481    frontend_ip_configuration_name=dict(
482        type='str',
483        required=True
484    ),
485    protocol=dict(
486        type='str',
487        choices=['Tcp', 'Udp', 'All']
488    ),
489    frontend_port_range_start=dict(
490        type='int',
491        required=True
492    ),
493    frontend_port_range_end=dict(
494        type='int',
495        required=True
496    ),
497    backend_port=dict(
498        type='int',
499        required=True
500    )
501)
502
503
504inbound_nat_rule_spec = dict(
505    name=dict(
506        type='str',
507        required=True
508    ),
509    frontend_ip_configuration=dict(
510        type='str',
511        required=True
512    ),
513    protocol=dict(
514        type='str',
515        choices=['Tcp', 'Udp', 'All']
516    ),
517    frontend_port=dict(
518        type='int',
519        required=True
520    ),
521    idle_timeout=dict(
522        type='int'
523    ),
524    backend_port=dict(
525        type='int',
526        required=True
527    ),
528    enable_floating_ip=dict(
529        type='bool'
530    ),
531    enable_tcp_reset=dict(
532        type='bool'
533    )
534)
535
536
537load_balancing_rule_spec = dict(
538    name=dict(
539        type='str',
540        required=True
541    ),
542    frontend_ip_configuration=dict(
543        type='str',
544        required=True
545    ),
546    backend_address_pool=dict(
547        type='str',
548        required=True
549    ),
550    probe=dict(
551        type='str',
552        required=True
553    ),
554    protocol=dict(
555        type='str',
556        choices=['Tcp', 'Udp', 'All']
557    ),
558    load_distribution=dict(
559        type='str',
560        choices=['Default', 'SourceIP', 'SourceIPProtocol'],
561        default='Default'
562    ),
563    frontend_port=dict(
564        type='int',
565        required=True
566    ),
567    backend_port=dict(
568        type='int'
569    ),
570    idle_timeout=dict(
571        type='int',
572        default=4
573    ),
574    enable_floating_ip=dict(
575        type='bool'
576    )
577)
578
579
580class AzureRMLoadBalancer(AzureRMModuleBase):
581    """Configuration class for an Azure RM load balancer resource"""
582
583    def __init__(self):
584        self.module_args = dict(
585            resource_group=dict(
586                type='str',
587                required=True
588            ),
589            name=dict(
590                type='str',
591                required=True
592            ),
593            state=dict(
594                type='str',
595                default='present',
596                choices=['present', 'absent']
597            ),
598            location=dict(
599                type='str'
600            ),
601            sku=dict(
602                type='str',
603                choices=['Basic', 'Standard']
604            ),
605            frontend_ip_configurations=dict(
606                type='list',
607                elements='dict',
608                options=frontend_ip_configuration_spec
609            ),
610            backend_address_pools=dict(
611                type='list',
612                elements='dict',
613                options=backend_address_pool_spec
614            ),
615            probes=dict(
616                type='list',
617                elements='dict',
618                options=probes_spec
619            ),
620            inbound_nat_rules=dict(
621                type='list',
622                elements='dict',
623                options=inbound_nat_rule_spec
624            ),
625            inbound_nat_pools=dict(
626                type='list',
627                elements='dict',
628                options=inbound_nat_pool_spec
629            ),
630            load_balancing_rules=dict(
631                type='list',
632                elements='dict',
633                options=load_balancing_rule_spec
634            ),
635            public_ip_address_name=dict(
636                type='str',
637                aliases=['public_ip_address', 'public_ip_name', 'public_ip']
638            ),
639            probe_port=dict(
640                type='int'
641            ),
642            probe_protocol=dict(
643                type='str',
644                choices=['Tcp', 'Http', 'Https']
645            ),
646            probe_interval=dict(
647                type='int',
648                default=15
649            ),
650            probe_fail_count=dict(
651                type='int',
652                default=3
653            ),
654            probe_request_path=dict(
655                type='str'
656            ),
657            protocol=dict(
658                type='str',
659                choices=['Tcp', 'Udp']
660            ),
661            load_distribution=dict(
662                type='str',
663                choices=['Default', 'SourceIP', 'SourceIPProtocol']
664            ),
665            frontend_port=dict(
666                type='int'
667            ),
668            backend_port=dict(
669                type='int'
670            ),
671            idle_timeout=dict(
672                type='int',
673                default=4
674            ),
675            natpool_frontend_port_start=dict(
676                type='int'
677            ),
678            natpool_frontend_port_end=dict(
679                type='int'
680            ),
681            natpool_backend_port=dict(
682                type='int'
683            ),
684            natpool_protocol=dict(
685                type='str'
686            )
687        )
688
689        self.resource_group = None
690        self.name = None
691        self.location = None
692        self.sku = None
693        self.frontend_ip_configurations = None
694        self.backend_address_pools = None
695        self.probes = None
696        self.inbound_nat_rules = None
697        self.inbound_nat_pools = None
698        self.load_balancing_rules = None
699        self.public_ip_address_name = None
700        self.state = None
701        self.probe_port = None
702        self.probe_protocol = None
703        self.probe_interval = None
704        self.probe_fail_count = None
705        self.probe_request_path = None
706        self.protocol = None
707        self.load_distribution = None
708        self.frontend_port = None
709        self.backend_port = None
710        self.idle_timeout = None
711        self.natpool_frontend_port_start = None
712        self.natpool_frontend_port_end = None
713        self.natpool_backend_port = None
714        self.natpool_protocol = None
715        self.tags = None
716
717        self.results = dict(changed=False, state=dict())
718
719        super(AzureRMLoadBalancer, self).__init__(
720            derived_arg_spec=self.module_args,
721            supports_check_mode=True
722        )
723
724    def exec_module(self, **kwargs):
725        """Main module execution method"""
726        for key in list(self.module_args.keys()) + ['tags']:
727            setattr(self, key, kwargs[key])
728
729        changed = False
730
731        resource_group = self.get_resource_group(self.resource_group)
732        if not self.location:
733            self.location = resource_group.location
734
735        load_balancer = self.get_load_balancer()
736
737        if self.state == 'present':
738            # compatible parameters
739            is_compatible_param = not self.frontend_ip_configurations and not self.backend_address_pools and not self.probes and not self.inbound_nat_pools
740            is_compatible_param = is_compatible_param and not load_balancer  # the instance should not be exist
741            is_compatible_param = is_compatible_param or self.public_ip_address_name or self.probe_protocol or self.natpool_protocol or self.protocol
742            if is_compatible_param:
743                self.deprecate('Discrete load balancer config settings are deprecated and will be removed.'
744                               ' Use frontend_ip_configurations, backend_address_pools, probes, inbound_nat_pools lists instead.', version='2.9')
745                frontend_ip_name = 'frontendip0'
746                backend_address_pool_name = 'backendaddrp0'
747                prob_name = 'prob0'
748                inbound_nat_pool_name = 'inboundnatp0'
749                lb_rule_name = 'lbr'
750                self.frontend_ip_configurations = [dict(
751                    name=frontend_ip_name,
752                    public_ip_address=self.public_ip_address_name
753                )]
754                self.backend_address_pools = [dict(
755                    name=backend_address_pool_name
756                )]
757                self.probes = [dict(
758                    name=prob_name,
759                    port=self.probe_port,
760                    protocol=self.probe_protocol,
761                    interval=self.probe_interval,
762                    fail_count=self.probe_fail_count,
763                    request_path=self.probe_request_path
764                )] if self.probe_protocol else None
765                self.inbound_nat_pools = [dict(
766                    name=inbound_nat_pool_name,
767                    frontend_ip_configuration_name=frontend_ip_name,
768                    protocol=self.natpool_protocol,
769                    frontend_port_range_start=self.natpool_frontend_port_start,
770                    frontend_port_range_end=self.natpool_frontend_port_end,
771                    backend_port=self.natpool_backend_port
772                )] if self.natpool_protocol else None
773                self.load_balancing_rules = [dict(
774                    name=lb_rule_name,
775                    frontend_ip_configuration=frontend_ip_name,
776                    backend_address_pool=backend_address_pool_name,
777                    probe=prob_name,
778                    protocol=self.protocol,
779                    load_distribution=self.load_distribution,
780                    frontend_port=self.frontend_port,
781                    backend_port=self.backend_port,
782                    idle_timeout=self.idle_timeout,
783                    enable_floating_ip=False
784                )] if self.protocol else None
785
786            # create new load balancer structure early, so it can be easily compared
787            frontend_ip_configurations_param = [self.network_models.FrontendIPConfiguration(
788                name=item.get('name'),
789                public_ip_address=self.get_public_ip_address_instance(item.get('public_ip_address')) if item.get('public_ip_address') else None,
790                private_ip_address=item.get('private_ip_address'),
791                private_ip_allocation_method=item.get('private_ip_allocation_method'),
792                subnet=self.network_models.Subnet(id=item.get('subnet')) if item.get('subnet') else None
793            ) for item in self.frontend_ip_configurations] if self.frontend_ip_configurations else None
794
795            backend_address_pools_param = [self.network_models.BackendAddressPool(
796                name=item.get('name')
797            ) for item in self.backend_address_pools] if self.backend_address_pools else None
798
799            probes_param = [self.network_models.Probe(
800                name=item.get('name'),
801                port=item.get('port'),
802                protocol=item.get('protocol'),
803                interval_in_seconds=item.get('interval'),
804                request_path=item.get('request_path'),
805                number_of_probes=item.get('fail_count')
806            ) for item in self.probes] if self.probes else None
807
808            inbound_nat_pools_param = [self.network_models.InboundNatPool(
809                name=item.get('name'),
810                frontend_ip_configuration=self.network_models.SubResource(
811                    id=frontend_ip_configuration_id(
812                        self.subscription_id,
813                        self.resource_group,
814                        self.name,
815                        item.get('frontend_ip_configuration_name'))),
816                protocol=item.get('protocol'),
817                frontend_port_range_start=item.get('frontend_port_range_start'),
818                frontend_port_range_end=item.get('frontend_port_range_end'),
819                backend_port=item.get('backend_port')
820            ) for item in self.inbound_nat_pools] if self.inbound_nat_pools else None
821
822            load_balancing_rules_param = [self.network_models.LoadBalancingRule(
823                name=item.get('name'),
824                frontend_ip_configuration=self.network_models.SubResource(
825                    id=frontend_ip_configuration_id(
826                        self.subscription_id,
827                        self.resource_group,
828                        self.name,
829                        item.get('frontend_ip_configuration')
830                    )
831                ),
832                backend_address_pool=self.network_models.SubResource(
833                    id=backend_address_pool_id(
834                        self.subscription_id,
835                        self.resource_group,
836                        self.name,
837                        item.get('backend_address_pool')
838                    )
839                ),
840                probe=self.network_models.SubResource(
841                    id=probe_id(
842                        self.subscription_id,
843                        self.resource_group,
844                        self.name,
845                        item.get('probe')
846                    )
847                ),
848                protocol=item.get('protocol'),
849                load_distribution=item.get('load_distribution'),
850                frontend_port=item.get('frontend_port'),
851                backend_port=item.get('backend_port'),
852                idle_timeout_in_minutes=item.get('idle_timeout'),
853                enable_floating_ip=item.get('enable_floating_ip')
854            ) for item in self.load_balancing_rules] if self.load_balancing_rules else None
855
856            inbound_nat_rules_param = [self.network_models.InboundNatRule(
857                name=item.get('name'),
858                frontend_ip_configuration=self.network_models.SubResource(
859                    id=frontend_ip_configuration_id(
860                        self.subscription_id,
861                        self.resource_group,
862                        self.name,
863                        item.get('frontend_ip_configuration')
864                    )
865                ) if item.get('frontend_ip_configuration') else None,
866                protocol=item.get('protocol'),
867                frontend_port=item.get('frontend_port'),
868                backend_port=item.get('backend_port'),
869                idle_timeout_in_minutes=item.get('idle_timeout'),
870                enable_tcp_reset=item.get('enable_tcp_reset'),
871                enable_floating_ip=item.get('enable_floating_ip')
872            ) for item in self.inbound_nat_rules] if self.inbound_nat_rules else None
873
874            # construct the new instance, if the parameter is none, keep remote one
875            self.new_load_balancer = self.network_models.LoadBalancer(
876                sku=self.network_models.LoadBalancerSku(name=self.sku) if self.sku else None,
877                location=self.location,
878                tags=self.tags,
879                frontend_ip_configurations=frontend_ip_configurations_param,
880                backend_address_pools=backend_address_pools_param,
881                probes=probes_param,
882                inbound_nat_pools=inbound_nat_pools_param,
883                load_balancing_rules=load_balancing_rules_param,
884                inbound_nat_rules=inbound_nat_rules_param
885            )
886
887            self.new_load_balancer = self.assign_protocol(self.new_load_balancer, load_balancer)
888
889            if load_balancer:
890                self.new_load_balancer = self.object_assign(self.new_load_balancer, load_balancer)
891                load_balancer_dict = load_balancer.as_dict()
892                new_dict = self.new_load_balancer.as_dict()
893                if not default_compare(new_dict, load_balancer_dict, ''):
894                    changed = True
895                else:
896                    changed = False
897            else:
898                changed = True
899        elif self.state == 'absent' and load_balancer:
900            changed = True
901
902        self.results['state'] = load_balancer.as_dict() if load_balancer else {}
903        if 'tags' in self.results['state']:
904            update_tags, self.results['state']['tags'] = self.update_tags(self.results['state']['tags'])
905            if update_tags:
906                changed = True
907        else:
908            if self.tags:
909                changed = True
910        self.results['changed'] = changed
911
912        if self.state == 'present' and changed:
913            self.results['state'] = self.create_or_update_load_balancer(self.new_load_balancer).as_dict()
914        elif self.state == 'absent' and changed:
915            self.delete_load_balancer()
916            self.results['state'] = None
917
918        return self.results
919
920    def get_public_ip_address_instance(self, id):
921        """Get a reference to the public ip address resource"""
922        self.log('Fetching public ip address {0}'.format(id))
923        resource_id = format_resource_id(id, self.subscription_id, 'Microsoft.Network', 'publicIPAddresses', self.resource_group)
924        return self.network_models.PublicIPAddress(id=resource_id)
925
926    def get_load_balancer(self):
927        """Get a load balancer"""
928        self.log('Fetching loadbalancer {0}'.format(self.name))
929        try:
930            return self.network_client.load_balancers.get(self.resource_group, self.name)
931        except CloudError:
932            return None
933
934    def delete_load_balancer(self):
935        """Delete a load balancer"""
936        self.log('Deleting loadbalancer {0}'.format(self.name))
937        try:
938            poller = self.network_client.load_balancers.delete(self.resource_group, self.name)
939            return self.get_poller_result(poller)
940        except CloudError as exc:
941            self.fail("Error deleting loadbalancer {0} - {1}".format(self.name, str(exc)))
942
943    def create_or_update_load_balancer(self, param):
944        try:
945            poller = self.network_client.load_balancers.create_or_update(self.resource_group, self.name, param)
946            new_lb = self.get_poller_result(poller)
947            return new_lb
948        except CloudError as exc:
949            self.fail("Error creating or updating load balancer {0} - {1}".format(self.name, str(exc)))
950
951    def object_assign(self, patch, origin):
952        attribute_map = set(self.network_models.LoadBalancer._attribute_map.keys()) - set(self.network_models.LoadBalancer._validation.keys())
953        for key in attribute_map:
954            if not getattr(patch, key):
955                setattr(patch, key, getattr(origin, key))
956        return patch
957
958    def assign_protocol(self, patch, origin):
959        attribute_map = ['probes', 'inbound_nat_rules', 'inbound_nat_pools', 'load_balancing_rules']
960        for attribute in attribute_map:
961            properties = getattr(patch, attribute)
962            if not properties:
963                continue
964            references = getattr(origin, attribute) if origin else []
965            for item in properties:
966                if item.protocol:
967                    continue
968                refs = [x for x in references if to_native(x.name) == item.name]
969                ref = refs[0] if len(refs) > 0 else None
970                item.protocol = ref.protocol if ref else 'Tcp'
971        return patch
972
973
974def default_compare(new, old, path):
975    if isinstance(new, dict):
976        if not isinstance(old, dict):
977            return False
978        for k in new.keys():
979            if not default_compare(new.get(k), old.get(k, None), path + '/' + k):
980                return False
981        return True
982    elif isinstance(new, list):
983        if not isinstance(old, list) or len(new) != len(old):
984            return False
985        if len(old) == 0:
986            return True
987        if isinstance(old[0], dict):
988            key = None
989            if 'id' in old[0] and 'id' in new[0]:
990                key = 'id'
991            elif 'name' in old[0] and 'name' in new[0]:
992                key = 'name'
993            new = sorted(new, key=lambda x: x.get(key, None))
994            old = sorted(old, key=lambda x: x.get(key, None))
995        else:
996            new = sorted(new)
997            old = sorted(old)
998        for i in range(len(new)):
999            if not default_compare(new[i], old[i], path + '/*'):
1000                return False
1001        return True
1002    else:
1003        return new == old
1004
1005
1006def frontend_ip_configuration_id(subscription_id, resource_group_name, load_balancer_name, name):
1007    """Generate the id for a frontend ip configuration"""
1008    return '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Network/loadBalancers/{2}/frontendIPConfigurations/{3}'.format(
1009        subscription_id,
1010        resource_group_name,
1011        load_balancer_name,
1012        name
1013    )
1014
1015
1016def backend_address_pool_id(subscription_id, resource_group_name, load_balancer_name, name):
1017    """Generate the id for a backend address pool"""
1018    return '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Network/loadBalancers/{2}/backendAddressPools/{3}'.format(
1019        subscription_id,
1020        resource_group_name,
1021        load_balancer_name,
1022        name
1023    )
1024
1025
1026def probe_id(subscription_id, resource_group_name, load_balancer_name, name):
1027    """Generate the id for a probe"""
1028    return '/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Network/loadBalancers/{2}/probes/{3}'.format(
1029        subscription_id,
1030        resource_group_name,
1031        load_balancer_name,
1032        name
1033    )
1034
1035
1036def main():
1037    """Main execution"""
1038    AzureRMLoadBalancer()
1039
1040
1041if __name__ == '__main__':
1042    main()
1043