From 858f0fc00006e15d33b63a505c80b0a1412a4a7e Mon Sep 17 00:00:00 2001 From: Rob Date: Fri, 25 May 2018 06:38:34 +1000 Subject: [PATCH] New module: AWS Network load balancer (#33808) * New module - elb_network_lb * Fix creating a load balancer without tags * Linter Fix purging tags Remove extra imports * add support for cross zone lb, doc update and fix tagging * pep8 fixes * Add integration tests for elb_network_lb module * more pep8 * Remove non-applicable option for NLBs * fix target protocol * pep8 --- lib/ansible/module_utils/aws/elbv2.py | 19 +- .../modules/cloud/amazon/elb_network_lb.py | 419 ++++++++++++++++++ .../targets/elb_network_lb/aliases | 2 + .../targets/elb_network_lb/defaults/main.yml | 6 + .../targets/elb_network_lb/meta/main.yml | 3 + .../targets/elb_network_lb/tasks/main.yml | 205 +++++++++ .../tasks/test_creating_nlb.yml | 48 ++ .../tasks/test_deleting_nlb.yml | 50 +++ .../tasks/test_modifying_nlb_listeners.yml | 88 ++++ .../tasks/test_nlb_bad_listener_options.yml | 72 +++ .../elb_network_lb/tasks/test_nlb_tags.yml | 101 +++++ .../tasks/test_nlb_with_asg.yml | 90 ++++ 12 files changed, 1095 insertions(+), 8 deletions(-) create mode 100644 lib/ansible/modules/cloud/amazon/elb_network_lb.py create mode 100644 test/integration/targets/elb_network_lb/aliases create mode 100644 test/integration/targets/elb_network_lb/defaults/main.yml create mode 100644 test/integration/targets/elb_network_lb/meta/main.yml create mode 100644 test/integration/targets/elb_network_lb/tasks/main.yml create mode 100644 test/integration/targets/elb_network_lb/tasks/test_creating_nlb.yml create mode 100644 test/integration/targets/elb_network_lb/tasks/test_deleting_nlb.yml create mode 100644 test/integration/targets/elb_network_lb/tasks/test_modifying_nlb_listeners.yml create mode 100644 test/integration/targets/elb_network_lb/tasks/test_nlb_bad_listener_options.yml create mode 100644 test/integration/targets/elb_network_lb/tasks/test_nlb_tags.yml create mode 100644 test/integration/targets/elb_network_lb/tasks/test_nlb_with_asg.yml diff --git a/lib/ansible/module_utils/aws/elbv2.py b/lib/ansible/module_utils/aws/elbv2.py index 62f4dc4d683..1be37280770 100644 --- a/lib/ansible/module_utils/aws/elbv2.py +++ b/lib/ansible/module_utils/aws/elbv2.py @@ -13,7 +13,6 @@ try: except ImportError: pass import traceback -import time from copy import deepcopy @@ -262,7 +261,8 @@ class ApplicationLoadBalancer(ElasticLoadBalancerV2): def modify_elb_attributes(self): """ - Update ELB attributes if required + Update Application ELB attributes if required + :return: """ @@ -338,6 +338,7 @@ class NetworkLoadBalancer(ElasticLoadBalancerV2): # Ansible module parameters specific to NLBs self.type = 'network' + self.cross_zone_load_balancing = module.params.get('cross_zone_load_balancing') def create_elb(self): """ @@ -354,7 +355,7 @@ class NetworkLoadBalancer(ElasticLoadBalancerV2): if self.subnets is not None: params['Subnets'] = self.subnets params['Scheme'] = self.scheme - if self.tags is not None: + if self.tags: params['Tags'] = self.tags try: @@ -369,16 +370,18 @@ class NetworkLoadBalancer(ElasticLoadBalancerV2): def modify_elb_attributes(self): """ - Update ELB attributes if required + Update Network ELB attributes if required + :return: """ update_attributes = [] - if self.deletion_protection and self.elb_attributes['deletion_protection_enabled'] != "true": - update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "true"}) - if self.deletion_protection is not None and not self.deletion_protection and self.elb_attributes['deletion_protection_enabled'] != "false": - update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "false"}) + if self.cross_zone_load_balancing is not None and str(self.cross_zone_load_balancing).lower() != \ + self.elb_attributes['load_balancing_cross_zone_enabled']: + update_attributes.append({'Key': 'load_balancing.cross_zone.enabled', 'Value': str(self.cross_zone_load_balancing).lower()}) + if self.deletion_protection is not None and str(self.deletion_protection).lower() != self.elb_attributes['deletion_protection_enabled']: + update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': str(self.deletion_protection).lower()}) if update_attributes: try: diff --git a/lib/ansible/modules/cloud/amazon/elb_network_lb.py b/lib/ansible/modules/cloud/amazon/elb_network_lb.py new file mode 100644 index 00000000000..d33fe571fe3 --- /dev/null +++ b/lib/ansible/modules/cloud/amazon/elb_network_lb.py @@ -0,0 +1,419 @@ +#!/usr/bin/python + +# Copyright: (c) 2018, Rob White (@wimnat) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +--- +module: elb_network_lb +short_description: Manage a Network Load Balancer +description: + - Manage an AWS Network Elastic Load Balancer. See + U(https://aws.amazon.com/blogs/aws/new-network-load-balancer-effortless-scaling-to-millions-of-requests-per-second/) for details. +version_added: "2.6" +requirements: [ boto3 ] +author: "Rob White (@wimnat)" +options: + cross_zone_load_balancing: + description: + - Indicates whether cross-zone load balancing is enabled. + required: false + default: no + type: bool + deletion_protection: + description: + - Indicates whether deletion protection for the ELB is enabled. + required: false + default: no + type: bool + listeners: + description: + - A list of dicts containing listeners to attach to the ELB. See examples for detail of the dict required. Note that listener keys + are CamelCased. + required: false + name: + description: + - The name of the load balancer. This name must be unique within your AWS account, can have a maximum of 32 characters, must contain only alphanumeric + characters or hyphens, and must not begin or end with a hyphen. + required: true + purge_listeners: + description: + - If yes, existing listeners will be purged from the ELB to match exactly what is defined by I(listeners) parameter. If the I(listeners) parameter is + not set then listeners will not be modified + default: yes + type: bool + purge_tags: + description: + - If yes, existing tags will be purged from the resource to match exactly what is defined by I(tags) parameter. If the I(tags) parameter is not set then + tags will not be modified. + required: false + default: yes + type: bool + subnet_mappings: + description: + - A list of dicts containing the IDs of the subnets to attach to the load balancer. You can also specify the allocation ID of an Elastic IP + to attach to the load balancer. You can specify one Elastic IP address per subnet. This parameter is mutually exclusive with I(subnets) + required: false + subnets: + description: + - A list of the IDs of the subnets to attach to the load balancer. You can specify only one subnet per Availability Zone. You must specify subnets from + at least two Availability Zones. Required if state=present. This parameter is mutually exclusive with I(subnet_mappings) + required: false + scheme: + description: + - Internet-facing or internal load balancer. An ELB scheme can not be modified after creation. + required: false + default: internet-facing + choices: [ 'internet-facing', 'internal' ] + state: + description: + - Create or destroy the load balancer. + required: true + choices: [ 'present', 'absent' ] + tags: + description: + - A dictionary of one or more tags to assign to the load balancer. + required: false + wait: + description: + - Whether or not to wait for the network load balancer to reach the desired state. + type: bool + wait_timeout: + description: + - The duration in seconds to wait, used in conjunction with I(wait). +extends_documentation_fragment: + - aws + - ec2 +notes: + - Listeners are matched based on port. If a listener's port is changed then a new listener will be created. + - Listener rules are matched based on priority. If a rule's priority is changed then a new rule will be created. +''' + +EXAMPLES = ''' +# Note: These examples do not set authentication details, see the AWS Guide for details. + +# Create an ELB and attach a listener +- elb_network_lb: + name: myelb + subnets: + - subnet-012345678 + - subnet-abcdef000 + listeners: + - Protocol: TCP # Required. The protocol for connections from clients to the load balancer (Only TCP is available) (case-sensitive). + Port: 80 # Required. The port on which the load balancer is listening. + DefaultActions: + - Type: forward # Required. Only 'forward' is accepted at this time + TargetGroupName: mytargetgroup # Required. The name of the target group + state: present + +# Create an ELB with an attached Elastic IP address +- elb_network_lb: + name: myelb + subnet_mappings: + - SubnetId: subnet-012345678 + AllocationId: eipalloc-aabbccdd + listeners: + - Protocol: TCP # Required. The protocol for connections from clients to the load balancer (Only TCP is available) (case-sensitive). + Port: 80 # Required. The port on which the load balancer is listening. + DefaultActions: + - Type: forward # Required. Only 'forward' is accepted at this time + TargetGroupName: mytargetgroup # Required. The name of the target group + state: present + +# Remove an ELB +- elb_network_lb: + name: myelb + state: absent + +''' + +RETURN = ''' +availability_zones: + description: The Availability Zones for the load balancer. + returned: when state is present + type: list + sample: "[{'subnet_id': 'subnet-aabbccddff', 'zone_name': 'ap-southeast-2a', 'load_balancer_addresses': []}]" +canonical_hosted_zone_id: + description: The ID of the Amazon Route 53 hosted zone associated with the load balancer. + returned: when state is present + type: string + sample: ABCDEF12345678 +created_time: + description: The date and time the load balancer was created. + returned: when state is present + type: string + sample: "2015-02-12T02:14:02+00:00" +deletion_protection_enabled: + description: Indicates whether deletion protection is enabled. + returned: when state is present + type: string + sample: true +dns_name: + description: The public DNS name of the load balancer. + returned: when state is present + type: string + sample: internal-my-elb-123456789.ap-southeast-2.elb.amazonaws.com +idle_timeout_timeout_seconds: + description: The idle timeout value, in seconds. + returned: when state is present + type: string + sample: 60 +ip_address_type: + description: The type of IP addresses used by the subnets for the load balancer. + returned: when state is present + type: string + sample: ipv4 +listeners: + description: Information about the listeners. + returned: when state is present + type: complex + contains: + listener_arn: + description: The Amazon Resource Name (ARN) of the listener. + returned: when state is present + type: string + sample: "" + load_balancer_arn: + description: The Amazon Resource Name (ARN) of the load balancer. + returned: when state is present + type: string + sample: "" + port: + description: The port on which the load balancer is listening. + returned: when state is present + type: int + sample: 80 + protocol: + description: The protocol for connections from clients to the load balancer. + returned: when state is present + type: string + sample: HTTPS + certificates: + description: The SSL server certificate. + returned: when state is present + type: complex + contains: + certificate_arn: + description: The Amazon Resource Name (ARN) of the certificate. + returned: when state is present + type: string + sample: "" + ssl_policy: + description: The security policy that defines which ciphers and protocols are supported. + returned: when state is present + type: string + sample: "" + default_actions: + description: The default actions for the listener. + returned: when state is present + type: string + contains: + type: + description: The type of action. + returned: when state is present + type: string + sample: "" + target_group_arn: + description: The Amazon Resource Name (ARN) of the target group. + returned: when state is present + type: string + sample: "" +load_balancer_arn: + description: The Amazon Resource Name (ARN) of the load balancer. + returned: when state is present + type: string + sample: arn:aws:elasticloadbalancing:ap-southeast-2:0123456789:loadbalancer/app/my-elb/001122334455 +load_balancer_name: + description: The name of the load balancer. + returned: when state is present + type: string + sample: my-elb +load_balancing_cross_zone_enabled: + description: Indicates whether cross-zone load balancing is enabled. + returned: when state is present + type: string + sample: true +scheme: + description: Internet-facing or internal load balancer. + returned: when state is present + type: string + sample: internal +state: + description: The state of the load balancer. + returned: when state is present + type: dict + sample: "{'code': 'active'}" +tags: + description: The tags attached to the load balancer. + returned: when state is present + type: dict + sample: "{ + 'Tag': 'Example' + }" +type: + description: The type of load balancer. + returned: when state is present + type: string + sample: network +vpc_id: + description: The ID of the VPC for the load balancer. + returned: when state is present + type: string + sample: vpc-0011223344 +''' + +from ansible.module_utils.aws.core import AnsibleAWSModule +from ansible.module_utils.ec2 import camel_dict_to_snake_dict, boto3_tag_list_to_ansible_dict, compare_aws_tags +from ansible.module_utils.aws.elbv2 import NetworkLoadBalancer, ELBListeners, ELBListener + + +def create_or_update_elb(elb_obj): + """Create ELB or modify main attributes. json_exit here""" + + if elb_obj.elb: + # ELB exists so check subnets, security groups and tags match what has been passed + + # Subnets + if not elb_obj.compare_subnets(): + elb_obj.modify_subnets() + + # Tags - only need to play with tags if tags parameter has been set to something + if elb_obj.tags is not None: + + # Delete necessary tags + tags_need_modify, tags_to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(elb_obj.elb['tags']), + boto3_tag_list_to_ansible_dict(elb_obj.tags), elb_obj.purge_tags) + if tags_to_delete: + elb_obj.delete_tags(tags_to_delete) + + # Add/update tags + if tags_need_modify: + elb_obj.modify_tags() + + else: + # Create load balancer + elb_obj.create_elb() + + # ELB attributes + elb_obj.update_elb_attributes() + elb_obj.modify_elb_attributes() + + # Listeners + listeners_obj = ELBListeners(elb_obj.connection, elb_obj.module, elb_obj.elb['LoadBalancerArn']) + + listeners_to_add, listeners_to_modify, listeners_to_delete = listeners_obj.compare_listeners() + + # Delete listeners + for listener_to_delete in listeners_to_delete: + listener_obj = ELBListener(elb_obj.connection, elb_obj.module, listener_to_delete, elb_obj.elb['LoadBalancerArn']) + listener_obj.delete() + listeners_obj.changed = True + + # Add listeners + for listener_to_add in listeners_to_add: + listener_obj = ELBListener(elb_obj.connection, elb_obj.module, listener_to_add, elb_obj.elb['LoadBalancerArn']) + listener_obj.add() + listeners_obj.changed = True + + # Modify listeners + for listener_to_modify in listeners_to_modify: + listener_obj = ELBListener(elb_obj.connection, elb_obj.module, listener_to_modify, elb_obj.elb['LoadBalancerArn']) + listener_obj.modify() + listeners_obj.changed = True + + # If listeners changed, mark ELB as changed + if listeners_obj.changed: + elb_obj.changed = True + + # Get the ELB again + elb_obj.update() + + # Get the ELB listeners again + listeners_obj.update() + + # Update the ELB attributes + elb_obj.update_elb_attributes() + + # Convert to snake_case and merge in everything we want to return to the user + snaked_elb = camel_dict_to_snake_dict(elb_obj.elb) + snaked_elb.update(camel_dict_to_snake_dict(elb_obj.elb_attributes)) + snaked_elb['listeners'] = [] + for listener in listeners_obj.current_listeners: + snaked_elb['listeners'].append(camel_dict_to_snake_dict(listener)) + + # Change tags to ansible friendly dict + snaked_elb['tags'] = boto3_tag_list_to_ansible_dict(snaked_elb['tags']) + + elb_obj.module.exit_json(changed=elb_obj.changed, **snaked_elb) + + +def delete_elb(elb_obj): + + if elb_obj.elb: + elb_obj.delete() + + elb_obj.module.exit_json(changed=elb_obj.changed) + + +def main(): + + argument_spec = ( + dict( + cross_zone_load_balancing=dict(type='bool'), + deletion_protection=dict(type='bool'), + listeners=dict(type='list', + elements='dict', + options=dict( + Protocol=dict(type='str', required=True), + Port=dict(type='int', required=True), + SslPolicy=dict(type='str'), + Certificates=dict(type='list'), + DefaultActions=dict(type='list', required=True) + ) + ), + name=dict(required=True, type='str'), + purge_listeners=dict(default=True, type='bool'), + purge_tags=dict(default=True, type='bool'), + subnets=dict(type='list'), + subnet_mappings=dict(type='list'), + scheme=dict(default='internet-facing', choices=['internet-facing', 'internal']), + state=dict(choices=['present', 'absent'], type='str'), + tags=dict(type='dict'), + wait_timeout=dict(type='int'), + wait=dict(type='bool') + ) + ) + + module = AnsibleAWSModule(argument_spec=argument_spec, + mutually_exclusive=[['subnets', 'subnet_mappings']]) + + # Check for subnets or subnet_mappings if state is present + state = module.params.get("state") + if state == 'present': + if module.params.get("subnets") is None and module.params.get("subnet_mappings") is None: + module.fail_json(msg="'subnets' or 'subnet_mappings' is required when state=present") + + # Quick check of listeners parameters + listeners = module.params.get("listeners") + if listeners is not None: + for listener in listeners: + for key in listener.keys(): + if key == 'Protocol' and listener[key] != 'TCP': + module.fail_json(msg="'Protocol' must be 'TCP'") + + connection = module.client('elbv2') + connection_ec2 = module.client('ec2') + + elb = NetworkLoadBalancer(connection, connection_ec2, module) + + if state == 'present': + create_or_update_elb(elb) + else: + delete_elb(elb) + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/elb_network_lb/aliases b/test/integration/targets/elb_network_lb/aliases new file mode 100644 index 00000000000..56927195182 --- /dev/null +++ b/test/integration/targets/elb_network_lb/aliases @@ -0,0 +1,2 @@ +cloud/aws +unsupported diff --git a/test/integration/targets/elb_network_lb/defaults/main.yml b/test/integration/targets/elb_network_lb/defaults/main.yml new file mode 100644 index 00000000000..ad31bf839d9 --- /dev/null +++ b/test/integration/targets/elb_network_lb/defaults/main.yml @@ -0,0 +1,6 @@ +--- +# load balancer and target group names have to be less than 32 characters +# the 8 digit identifier at the end of resource_prefix helps determine during which test something +# was created and allows tests to be run in parallel +nlb_name: "my-nlb-{{ resource_prefix | regex_search('([0-9]+)$') }}" +tg_name: "my-tg-{{ resource_prefix | regex_search('([0-9]+)$') }}" diff --git a/test/integration/targets/elb_network_lb/meta/main.yml b/test/integration/targets/elb_network_lb/meta/main.yml new file mode 100644 index 00000000000..1f64f1169a9 --- /dev/null +++ b/test/integration/targets/elb_network_lb/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - prepare_tests + - setup_ec2 diff --git a/test/integration/targets/elb_network_lb/tasks/main.yml b/test/integration/targets/elb_network_lb/tasks/main.yml new file mode 100644 index 00000000000..626107165f6 --- /dev/null +++ b/test/integration/targets/elb_network_lb/tasks/main.yml @@ -0,0 +1,205 @@ +- block: + + - name: set connection information for all tasks + set_fact: + aws_connection_info: &aws_connection_info + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token }}" + region: "{{ aws_region }}" + no_log: yes + + - name: create VPC + ec2_vpc_net: + cidr_block: 10.228.228.0/22 + name: "{{ resource_prefix }}_vpc" + state: present + <<: *aws_connection_info + register: vpc + + - name: create internet gateway + ec2_vpc_igw: + vpc_id: "{{ vpc.vpc.id }}" + state: present + tags: + Name: "{{ resource_prefix }}" + <<: *aws_connection_info + register: igw + + - name: create subnets + ec2_vpc_subnet: + cidr: "{{ item.cidr }}" + az: "{{ aws_region}}{{ item.az }}" + vpc_id: "{{ vpc.vpc.id }}" + state: present + tags: + Created_By: "{{ resource_prefix }}" + Public: "{{ item.public }}" + <<: *aws_connection_info + with_items: + - cidr: 10.228.228.0/24 + az: "a" + public: True + - cidr: 10.228.229.0/24 + az: "b" + public: True + - cidr: 10.228.230.0/24 + az: "a" + public: False + - cidr: 10.228.231.0/24 + az: "b" + public: False + register: subnets + + - ec2_vpc_subnet_facts: + filters: + vpc-id: "{{ vpc.vpc.id }}" + <<: *aws_connection_info + register: vpc_subnets + + - name: create list of subnet ids + set_fact: + nlb_subnets: "{{ vpc_subnets|json_query('subnets[?tags.Public == `True`].id') }}" + private_subnets: "{{ vpc_subnets|json_query('subnets[?tags.Public != `True`].id') }}" + + - name: create a route table + ec2_vpc_route_table: + vpc_id: "{{ vpc.vpc.id }}" + <<: *aws_connection_info + tags: + Name: igw-route + Created: "{{ resource_prefix }}" + subnets: "{{ nlb_subnets + private_subnets }}" + routes: + - dest: 0.0.0.0/0 + gateway_id: "{{ igw.gateway_id }}" + register: route_table + + - ec2_group: + name: "{{ resource_prefix }}" + description: "security group for Ansible NLB integration tests" + state: present + vpc_id: "{{ vpc.vpc.id }}" + rules: + - proto: tcp + from_port: 1 + to_port: 65535 + cidr_ip: 0.0.0.0/0 + - proto: all + ports: 80 + cidr_ip: 10.228.228.0/22 + <<: *aws_connection_info + register: sec_group + + - name: create a target group for testing + elb_target_group: + name: "{{ tg_name }}" + protocol: tcp + port: 80 + vpc_id: "{{ vpc.vpc.id }}" + state: present + <<: *aws_connection_info + register: tg + + - include_tasks: test_nlb_bad_listener_options.yml + - include_tasks: test_nlb_tags.yml + - include_tasks: test_creating_nlb.yml + - include_tasks: test_nlb_with_asg.yml + - include_tasks: test_modifying_nlb_listeners.yml + - include_tasks: test_deleting_nlb.yml + + always: + + - name: destroy NLB + elb_network_lb: + name: "{{ nlb_name }}" + state: absent + wait: yes + wait_timeout: 600 + <<: *aws_connection_info + ignore_errors: yes + + - name: destroy target group if it was created + elb_target_group: + name: "{{ tg_name }}" + protocol: tcp + port: 80 + vpc_id: "{{ vpc.vpc.id }}" + state: absent + wait: yes + wait_timeout: 600 + <<: *aws_connection_info + register: remove_tg + retries: 5 + delay: 3 + until: remove_tg is success + when: tg is defined + ignore_errors: yes + + - name: destroy sec group + ec2_group: + name: "{{ sec_group.group_name }}" + description: "security group for Ansible NLB integration tests" + state: absent + vpc_id: "{{ vpc.vpc.id }}" + <<: *aws_connection_info + register: remove_sg + retries: 10 + delay: 5 + until: remove_sg is success + ignore_errors: yes + + - name: remove route table + ec2_vpc_route_table: + vpc_id: "{{ vpc.vpc.id }}" + route_table_id: "{{ route_table.route_table.route_table_id }}" + lookup: id + state: absent + <<: *aws_connection_info + register: remove_rt + retries: 10 + delay: 5 + until: remove_rt is success + ignore_errors: yes + + - name: destroy subnets + ec2_vpc_subnet: + cidr: "{{ item.cidr }}" + vpc_id: "{{ vpc.vpc.id }}" + state: absent + <<: *aws_connection_info + register: remove_subnet + retries: 10 + delay: 5 + until: remove_subnet is success + with_items: + - cidr: 10.228.228.0/24 + - cidr: 10.228.229.0/24 + - cidr: 10.228.230.0/24 + - cidr: 10.228.231.0/24 + ignore_errors: yes + + - name: destroy internet gateway + ec2_vpc_igw: + vpc_id: "{{ vpc.vpc.id }}" + tags: + Name: "{{ resource_prefix }}" + state: absent + <<: *aws_connection_info + register: remove_igw + retries: 10 + delay: 5 + until: remove_igw is success + ignore_errors: yes + + - name: destroy VPC + ec2_vpc_net: + cidr_block: 10.228.228.0/22 + name: "{{ resource_prefix }}_vpc" + state: absent + <<: *aws_connection_info + register: remove_vpc + retries: 10 + delay: 5 + until: remove_vpc is success + ignore_errors: yes diff --git a/test/integration/targets/elb_network_lb/tasks/test_creating_nlb.yml b/test/integration/targets/elb_network_lb/tasks/test_creating_nlb.yml new file mode 100644 index 00000000000..9b0d204e480 --- /dev/null +++ b/test/integration/targets/elb_network_lb/tasks/test_creating_nlb.yml @@ -0,0 +1,48 @@ +- block: + + - name: set connection information for all tasks + set_fact: + aws_connection_info: &aws_connection_info + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token }}" + region: "{{ aws_region }}" + no_log: yes + + - name: create NLB with a listener + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + state: present + listeners: + - Protocol: TCP + Port: 80 + DefaultActions: + - Type: forward + TargetGroupName: "{{ tg_name }}" + <<: *aws_connection_info + register: nlb + + - assert: + that: + - nlb.changed + - nlb.listeners|length == 1 + + - name: test idempotence creating NLB with a listener + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + state: present + listeners: + - Protocol: TCP + Port: 80 + DefaultActions: + - Type: forward + TargetGroupName: "{{ tg_name }}" + <<: *aws_connection_info + register: nlb + + - assert: + that: + - not nlb.changed + - nlb.listeners|length == 1 diff --git a/test/integration/targets/elb_network_lb/tasks/test_deleting_nlb.yml b/test/integration/targets/elb_network_lb/tasks/test_deleting_nlb.yml new file mode 100644 index 00000000000..23d1d535881 --- /dev/null +++ b/test/integration/targets/elb_network_lb/tasks/test_deleting_nlb.yml @@ -0,0 +1,50 @@ +- block: + + - name: set connection information for all tasks + set_fact: + aws_connection_info: &aws_connection_info + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token }}" + region: "{{ aws_region }}" + no_log: yes + + - name: destroy NLB with listener + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + state: absent + listeners: + - Protocol: TCP + Port: 80 + DefaultActions: + - Type: forward + TargetGroupName: "{{ tg_name }}" + <<: *aws_connection_info + wait: yes + wait_timeout: 300 + register: nlb + + - assert: + that: + - nlb.changed + + - name: test idempotence + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + state: absent + listeners: + - Protocol: TCP + Port: 80 + DefaultActions: + - Type: forward + TargetGroupName: "{{ tg_name }}" + <<: *aws_connection_info + wait: yes + wait_timeout: 300 + register: nlb + + - assert: + that: + - not nlb.changed diff --git a/test/integration/targets/elb_network_lb/tasks/test_modifying_nlb_listeners.yml b/test/integration/targets/elb_network_lb/tasks/test_modifying_nlb_listeners.yml new file mode 100644 index 00000000000..67ab99e8707 --- /dev/null +++ b/test/integration/targets/elb_network_lb/tasks/test_modifying_nlb_listeners.yml @@ -0,0 +1,88 @@ +- block: + + - name: set connection information for all tasks + set_fact: + aws_connection_info: &aws_connection_info + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token }}" + region: "{{ aws_region }}" + no_log: yes + + - name: add a listener + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + state: present + listeners: + - Protocol: TCP + Port: 80 + DefaultActions: + - Type: forward + TargetGroupName: "{{ tg_name }}" + - Protocol: TCP + Port: 443 + DefaultActions: + - Type: forward + TargetGroupName: "{{ tg_name }}" + <<: *aws_connection_info + register: nlb + + - assert: + that: + - nlb.changed + - nlb.listeners|length == 2 + + - name: test an omitted listener will not be removed without purge_listeners + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + state: present + purge_listeners: false + listeners: + - Protocol: TCP + Port: 80 + DefaultActions: + - Type: forward + TargetGroupName: "{{ tg_name }}" + <<: *aws_connection_info + register: nlb + + - assert: + that: + - not nlb.changed + - nlb.listeners|length == 2 + + - name: remove the rule + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + state: present + purge_listeners: true + listeners: + - Protocol: TCP + Port: 80 + DefaultActions: + - Type: forward + TargetGroupName: "{{ tg_name }}" + <<: *aws_connection_info + register: nlb + + - assert: + that: + - nlb.changed + - nlb.listeners|length == 1 + + - name: remove listener from NLB + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + state: present + listeners: [] + <<: *aws_connection_info + register: nlb + + - assert: + that: + - nlb.changed + - not nlb.listeners diff --git a/test/integration/targets/elb_network_lb/tasks/test_nlb_bad_listener_options.yml b/test/integration/targets/elb_network_lb/tasks/test_nlb_bad_listener_options.yml new file mode 100644 index 00000000000..5372cae37c7 --- /dev/null +++ b/test/integration/targets/elb_network_lb/tasks/test_nlb_bad_listener_options.yml @@ -0,0 +1,72 @@ +- block: + + - name: set connection information for all tasks + set_fact: + aws_connection_info: &aws_connection_info + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token }}" + region: "{{ aws_region }}" + no_log: yes + + - name: test creating an NLB with invalid listener options + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + #security_groups: "{{ sec_group.group_id }}" + state: present + listeners: + - Protocol: TCP + Port: 80 + Certificates: {'CertificateArn': 'test', 'IsDefault': 'True'} + DefaultActions: + - Type: forward + TargetGroupName: "{{ tg_name }}" + <<: *aws_connection_info + ignore_errors: yes + register: nlb + + - assert: + that: + - nlb is failed + - "'unable to convert to list' in nlb.msg" + + - name: test creating an NLB without providing required listener options + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + #security_groups: "{{ sec_group.group_id }}" + state: present + listeners: + - Port: 80 + <<: *aws_connection_info + ignore_errors: yes + register: nlb + + - assert: + that: + - nlb is failed + - '"missing required arguments" in nlb.msg' + - '"Protocol" in nlb.msg' + - '"DefaultActions" in nlb.msg' + + - name: test creating an NLB providing an invalid listener option type + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + #security_groups: "{{ sec_group.group_id }}" + state: present + listeners: + - Protocol: TCP + Port: "bad type" + DefaultActions: + - Type: forward + TargetGroupName: "{{ tg_name }}" + <<: *aws_connection_info + ignore_errors: yes + register: nlb + + - assert: + that: + - nlb is failed + - "'unable to convert to int' in nlb.msg" diff --git a/test/integration/targets/elb_network_lb/tasks/test_nlb_tags.yml b/test/integration/targets/elb_network_lb/tasks/test_nlb_tags.yml new file mode 100644 index 00000000000..6b81e90c53c --- /dev/null +++ b/test/integration/targets/elb_network_lb/tasks/test_nlb_tags.yml @@ -0,0 +1,101 @@ +- block: + + - name: set connection information for all tasks + set_fact: + aws_connection_info: &aws_connection_info + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token }}" + region: "{{ aws_region }}" + no_log: yes + + - name: create NLB with no listeners + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + state: present + <<: *aws_connection_info + register: nlb + + - assert: + that: + - nlb.changed + + - name: re-create NLB with no listeners + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + state: present + <<: *aws_connection_info + register: nlb + + - assert: + that: + - not nlb.changed + + - name: add tags to NLB + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + state: present + tags: + created_by: "NLB test {{ resource_prefix }}" + <<: *aws_connection_info + register: nlb + + - assert: + that: + - nlb.changed + - 'nlb.tags.created_by == "NLB test {{ resource_prefix }}"' + + - name: test tags are not removed if unspecified + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + state: present + <<: *aws_connection_info + register: nlb + + - assert: + that: + - not nlb.changed + - 'nlb.tags.created_by == "NLB test {{ resource_prefix }}"' + + - name: remove tags from NLB + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + state: present + tags: {} + <<: *aws_connection_info + register: nlb + + - assert: + that: + - nlb.changed + - not nlb.tags + + - name: test idempotence + elb_network_lb: + name: "{{ nlb_name }}" + subnets: "{{ nlb_subnets }}" + state: present + tags: {} + <<: *aws_connection_info + register: nlb + + - assert: + that: + - not nlb.changed + - not nlb.tags + + - name: destroy NLB with no listeners + elb_network_lb: + name: "{{ nlb_name }}" + state: absent + <<: *aws_connection_info + register: nlb + + - assert: + that: + - nlb.changed diff --git a/test/integration/targets/elb_network_lb/tasks/test_nlb_with_asg.yml b/test/integration/targets/elb_network_lb/tasks/test_nlb_with_asg.yml new file mode 100644 index 00000000000..e686b43252b --- /dev/null +++ b/test/integration/targets/elb_network_lb/tasks/test_nlb_with_asg.yml @@ -0,0 +1,90 @@ +- block: + + # create instances + + - name: set connection information for all tasks + set_fact: + aws_connection_info: &aws_connection_info + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token }}" + region: "{{ aws_region }}" + no_log: yes + + - ec2_ami_facts: + <<: *aws_connection_info + filters: + architecture: x86_64 + virtualization-type: hvm + root-device-type: ebs + name: "amzn-ami-hvm*" + register: amis + + - set_fact: + latest_amazon_linux: "{{ amis.images | sort(attribute='creation_date') | last }}" + + - ec2_asg: + <<: *aws_connection_info + state: absent + name: "{{ resource_prefix }}-webservers" + wait_timeout: 900 + + - ec2_lc: + <<: *aws_connection_info + name: "{{ resource_prefix }}-web-lcfg" + state: absent + + - name: Create launch config for testing + ec2_lc: + <<: *aws_connection_info + name: "{{ resource_prefix }}-web-lcfg" + assign_public_ip: true + image_id: "{{ latest_amazon_linux.image_id }}" + security_groups: "{{ sec_group.group_id }}" + instance_type: t2.micro + user_data: | + #!/bin/bash + set -x + yum update -y --nogpgcheck + yum install -y --nogpgcheck httpd + echo "Hello Ansiblings!" >> /var/www/html/index.html + service httpd start + volumes: + - device_name: /dev/xvda + volume_size: 10 + volume_type: gp2 + delete_on_termination: true + + - name: Create autoscaling group for app server fleet + ec2_asg: + <<: *aws_connection_info + name: "{{ resource_prefix }}-webservers" + vpc_zone_identifier: "{{ nlb_subnets }}" + launch_config_name: "{{ resource_prefix }}-web-lcfg" + termination_policies: + - OldestLaunchConfiguration + - Default + health_check_period: 600 + health_check_type: EC2 + replace_all_instances: true + min_size: 0 + max_size: 2 + desired_capacity: 1 + wait_for_instances: true + target_group_arns: + - "{{ tg.target_group_arn }}" + + always: + + - ec2_asg: + <<: *aws_connection_info + state: absent + name: "{{ resource_prefix }}-webservers" + wait_timeout: 900 + ignore_errors: yes + + - ec2_lc: + <<: *aws_connection_info + name: "{{ resource_prefix }}-web-lcfg" + state: absent + ignore_errors: yes