Add a Scaleway load-balancer module (#51741)
This commit is contained in:
parent
9dbb551528
commit
accbcdeccb
5 changed files with 585 additions and 0 deletions
|
@ -167,3 +167,15 @@ SCALEWAY_LOCATION = {
|
|||
'ams1': {'name': 'Amsterdam 1', 'country': 'NL', "api_endpoint": 'https://cp-ams1.scaleway.com'},
|
||||
'EMEA-NL-EVS': {'name': 'Amsterdam 1', 'country': 'NL', "api_endpoint": 'https://cp-ams1.scaleway.com'}
|
||||
}
|
||||
|
||||
SCALEWAY_ENDPOINT = "https://api-world.scaleway.com"
|
||||
|
||||
SCALEWAY_REGIONS = [
|
||||
"fr-par",
|
||||
"nl-ams",
|
||||
]
|
||||
|
||||
SCALEWAY_ZONES = [
|
||||
"fr-par-1",
|
||||
"nl-ams-1",
|
||||
]
|
||||
|
|
348
lib/ansible/modules/cloud/scaleway/scaleway_lb.py
Normal file
348
lib/ansible/modules/cloud/scaleway/scaleway_lb.py
Normal file
|
@ -0,0 +1,348 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# Scaleway Load-balancer management module
|
||||
#
|
||||
# Copyright (C) 2018 Online SAS.
|
||||
# https://www.scaleway.com
|
||||
#
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: scaleway_lb
|
||||
short_description: Scaleway load-balancer management module
|
||||
version_added: "2.8"
|
||||
author: Remy Leone (@sieben)
|
||||
description:
|
||||
- "This module manages load-balancers on Scaleway."
|
||||
extends_documentation_fragment: scaleway
|
||||
|
||||
options:
|
||||
|
||||
name:
|
||||
description:
|
||||
- Name of the load-balancer
|
||||
required: true
|
||||
|
||||
organization_id:
|
||||
description:
|
||||
- Organization identifier
|
||||
required: true
|
||||
|
||||
state:
|
||||
description:
|
||||
- Indicate desired state of the instance.
|
||||
default: present
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
|
||||
region:
|
||||
description:
|
||||
- Scaleway zone
|
||||
required: true
|
||||
choices:
|
||||
- nl-ams
|
||||
- fr-par
|
||||
|
||||
tags:
|
||||
description:
|
||||
- List of tags to apply to the load-balancer
|
||||
|
||||
wait:
|
||||
description:
|
||||
- Wait for the load-balancer to reach its desired state before returning.
|
||||
type: bool
|
||||
default: 'no'
|
||||
|
||||
wait_timeout:
|
||||
description:
|
||||
- Time to wait for the load-balancer to reach the expected state
|
||||
required: false
|
||||
default: 300
|
||||
|
||||
wait_sleep_time:
|
||||
description:
|
||||
- Time to wait before every attempt to check the state of the load-balancer
|
||||
required: false
|
||||
default: 3
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create a load-balancer
|
||||
scaleway_lb:
|
||||
name: foobar
|
||||
state: present
|
||||
organization_id: 951df375-e094-4d26-97c1-ba548eeb9c42
|
||||
region: fr-par
|
||||
tags:
|
||||
- hello
|
||||
|
||||
- name: Delete a load-balancer
|
||||
scaleway_lb:
|
||||
name: foobar
|
||||
state: absent
|
||||
organization_id: 951df375-e094-4d26-97c1-ba548eeb9c42
|
||||
region: fr-par
|
||||
'''
|
||||
|
||||
RETURNS = '''
|
||||
{
|
||||
"scaleway_lb": {
|
||||
"backend_count": 0,
|
||||
"frontend_count": 0,
|
||||
"description": "Description of my load-balancer",
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"instances": [
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"ip_address": "10.0.0.1",
|
||||
"region": "fr-par",
|
||||
"status": "ready"
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"ip_address": "10.0.0.2",
|
||||
"region": "fr-par",
|
||||
"status": "ready"
|
||||
}
|
||||
],
|
||||
"ip": [
|
||||
{
|
||||
"id": "00000000-0000-0000-0000-000000000000",
|
||||
"ip_address": "192.168.0.1",
|
||||
"lb_id": "00000000-0000-0000-0000-000000000000",
|
||||
"region": "fr-par",
|
||||
"organization_id": "00000000-0000-0000-0000-000000000000",
|
||||
"reverse": ""
|
||||
}
|
||||
],
|
||||
"name": "lb_ansible_test",
|
||||
"organization_id": "00000000-0000-0000-0000-000000000000",
|
||||
"region": "fr-par",
|
||||
"status": "ready",
|
||||
"tags": [
|
||||
"first_tag",
|
||||
"second_tag"
|
||||
]
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
import datetime
|
||||
import time
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.scaleway import SCALEWAY_REGIONS, SCALEWAY_ENDPOINT, scaleway_argument_spec, Scaleway
|
||||
|
||||
STABLE_STATES = (
|
||||
"ready",
|
||||
"absent"
|
||||
)
|
||||
|
||||
MUTABLE_ATTRIBUTES = (
|
||||
"name",
|
||||
"description"
|
||||
)
|
||||
|
||||
|
||||
def payload_from_wished_lb(wished_lb):
|
||||
return {
|
||||
"organization_id": wished_lb["organization_id"],
|
||||
"name": wished_lb["name"],
|
||||
"tags": wished_lb["tags"],
|
||||
"description": wished_lb["description"]
|
||||
}
|
||||
|
||||
|
||||
def fetch_state(api, lb):
|
||||
api.module.debug("fetch_state of load-balancer: %s" % lb["id"])
|
||||
response = api.get(path=api.api_path + "/%s" % lb["id"])
|
||||
|
||||
if response.status_code == 404:
|
||||
return "absent"
|
||||
|
||||
if not response.ok:
|
||||
msg = 'Error during state fetching: (%s) %s' % (response.status_code, response.json)
|
||||
api.module.fail_json(msg=msg)
|
||||
|
||||
try:
|
||||
api.module.debug("Load-balancer %s in state: %s" % (lb["id"], response.json["status"]))
|
||||
return response.json["status"]
|
||||
except KeyError:
|
||||
api.module.fail_json(msg="Could not fetch state in %s" % response.json)
|
||||
|
||||
|
||||
def wait_to_complete_state_transition(api, lb, force_wait=False):
|
||||
wait = api.module.params["wait"]
|
||||
if not (wait or force_wait):
|
||||
return
|
||||
wait_timeout = api.module.params["wait_timeout"]
|
||||
wait_sleep_time = api.module.params["wait_sleep_time"]
|
||||
|
||||
start = datetime.datetime.utcnow()
|
||||
end = start + datetime.timedelta(seconds=wait_timeout)
|
||||
while datetime.datetime.utcnow() < end:
|
||||
api.module.debug("We are going to wait for the load-balancer to finish its transition")
|
||||
state = fetch_state(api, lb)
|
||||
if state in STABLE_STATES:
|
||||
api.module.debug("It seems that the load-balancer is not in transition anymore.")
|
||||
api.module.debug("load-balancer in state: %s" % fetch_state(api, lb))
|
||||
break
|
||||
time.sleep(wait_sleep_time)
|
||||
else:
|
||||
api.module.fail_json(msg="Server takes too long to finish its transition")
|
||||
|
||||
|
||||
def lb_attributes_should_be_changed(target_lb, wished_lb):
|
||||
diff = {
|
||||
attr: wished_lb[attr] for attr in MUTABLE_ATTRIBUTES if target_lb[attr] != wished_lb[attr]
|
||||
}
|
||||
if diff:
|
||||
return {attr: wished_lb[attr] for attr in MUTABLE_ATTRIBUTES}
|
||||
else:
|
||||
return diff
|
||||
|
||||
|
||||
def present_strategy(api, wished_lb):
|
||||
changed = False
|
||||
|
||||
response = api.get(path=api.api_path)
|
||||
if not response.ok:
|
||||
api.module.fail_json(msg='Error getting load-balancers [{0}: {1}]'.format(
|
||||
response.status_code, response.json['message']))
|
||||
|
||||
lbs_list = response.json["lbs"]
|
||||
lb_lookup = dict((lb["name"], lb)
|
||||
for lb in lbs_list)
|
||||
|
||||
if wished_lb["name"] not in lb_lookup.keys():
|
||||
changed = True
|
||||
if api.module.check_mode:
|
||||
return changed, {"status": "A load-balancer would be created."}
|
||||
|
||||
# Create Load-balancer
|
||||
api.warn(payload_from_wished_lb(wished_lb))
|
||||
creation_response = api.post(path=api.api_path,
|
||||
data=payload_from_wished_lb(wished_lb))
|
||||
|
||||
if not creation_response.ok:
|
||||
msg = "Error during lb creation: %s: '%s' (%s)" % (creation_response.info['msg'],
|
||||
creation_response.json['message'],
|
||||
creation_response.json)
|
||||
api.module.fail_json(msg=msg)
|
||||
|
||||
wait_to_complete_state_transition(api=api, lb=creation_response.json)
|
||||
response = api.get(path=api.api_path + "/%s" % creation_response.json["id"])
|
||||
return changed, response.json
|
||||
|
||||
target_lb = lb_lookup[wished_lb["name"]]
|
||||
patch_payload = lb_attributes_should_be_changed(target_lb=target_lb,
|
||||
wished_lb=wished_lb)
|
||||
|
||||
if not patch_payload:
|
||||
return changed, target_lb
|
||||
|
||||
changed = True
|
||||
if api.module.check_mode:
|
||||
return changed, {"status": "Load-balancer attributes would be changed."}
|
||||
|
||||
lb_patch_response = api.put(path=api.api_path + "/%s" % target_lb["id"],
|
||||
data=patch_payload)
|
||||
|
||||
if not lb_patch_response.ok:
|
||||
api.module.fail_json(msg='Error during load-balancer attributes update: [{0}: {1}]'.format(
|
||||
lb_patch_response.status_code, lb_patch_response.json['message']))
|
||||
|
||||
wait_to_complete_state_transition(api=api, lb=target_lb)
|
||||
return changed, lb_patch_response.json
|
||||
|
||||
|
||||
def absent_strategy(api, wished_lb):
|
||||
response = api.get(path=api.api_path)
|
||||
changed = False
|
||||
|
||||
status_code = response.status_code
|
||||
lbs_json = response.json
|
||||
lbs_list = lbs_json["lbs"]
|
||||
|
||||
if not response.ok:
|
||||
api.module.fail_json(msg='Error getting load-balancers [{0}: {1}]'.format(
|
||||
status_code, response.json['message']))
|
||||
|
||||
lb_lookup = dict((lb["name"], lb)
|
||||
for lb in lbs_list)
|
||||
if wished_lb["name"] not in lb_lookup.keys():
|
||||
return changed, {}
|
||||
|
||||
target_lb = lb_lookup[wished_lb["name"]]
|
||||
changed = True
|
||||
if api.module.check_mode:
|
||||
return changed, {"status": "Load-balancer would be destroyed"}
|
||||
|
||||
wait_to_complete_state_transition(api=api, lb=target_lb, force_wait=True)
|
||||
response = api.delete(path=api.api_path + "/%s" % target_lb["id"])
|
||||
if not response.ok:
|
||||
api.module.fail_json(msg='Error deleting load-balancer [{0}: {1}]'.format(
|
||||
response.status_code, response.json))
|
||||
|
||||
wait_to_complete_state_transition(api=api, lb=target_lb)
|
||||
return changed, response.json
|
||||
|
||||
|
||||
state_strategy = {
|
||||
"present": present_strategy,
|
||||
"absent": absent_strategy
|
||||
}
|
||||
|
||||
|
||||
def core(module):
|
||||
region = module.params["region"]
|
||||
wished_load_balancer = {
|
||||
"state": module.params["state"],
|
||||
"name": module.params["name"],
|
||||
"description": module.params["description"],
|
||||
"tags": module.params["tags"],
|
||||
"organization_id": module.params["organization_id"]
|
||||
}
|
||||
module.params['api_url'] = SCALEWAY_ENDPOINT
|
||||
api = Scaleway(module=module)
|
||||
api.api_path = "lbaas/v1beta1/regions/%s/lbs" % region
|
||||
|
||||
changed, summary = state_strategy[wished_load_balancer["state"]](api=api,
|
||||
wished_lb=wished_load_balancer)
|
||||
module.exit_json(changed=changed, scaleway_lb=summary)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = scaleway_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
name=dict(required=True),
|
||||
description=dict(required=True),
|
||||
region=dict(required=True, choices=SCALEWAY_REGIONS),
|
||||
state=dict(choices=state_strategy.keys(), default='present'),
|
||||
tags=dict(type="list", default=[]),
|
||||
organization_id=dict(required=True),
|
||||
wait=dict(type="bool", default=False),
|
||||
wait_timeout=dict(type="int", default=300),
|
||||
wait_sleep_time=dict(type="int", default=3),
|
||||
))
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
core(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
8
test/legacy/roles/scaleway_lb/defaults/main.yml
Normal file
8
test/legacy/roles/scaleway_lb/defaults/main.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
scaleway_region: fr-par
|
||||
name: lb_ansible_test
|
||||
description: Load-balancer used for testing scaleway_lb ansible module
|
||||
updated_description: Load-balancer used for testing scaleway_lb ansible module (Updated description)
|
||||
tags:
|
||||
- first_tag
|
||||
- second_tag
|
216
test/legacy/roles/scaleway_lb/tasks/main.yml
Normal file
216
test/legacy/roles/scaleway_lb/tasks/main.yml
Normal file
|
@ -0,0 +1,216 @@
|
|||
# SCW_API_KEY='XXX' SCW_ORG='YYY' ansible-playbook ./test/legacy/scaleway.yml --tags test_scaleway_lb
|
||||
|
||||
- name: Create a load-balancer (Check)
|
||||
check_mode: yes
|
||||
scaleway_lb:
|
||||
state: present
|
||||
name: '{{ name }}'
|
||||
region: '{{ scaleway_region }}'
|
||||
organization_id: '{{ scw_org }}'
|
||||
description: '{{ description }}'
|
||||
tags: '{{ tags }}'
|
||||
register: lb_creation_check_task
|
||||
|
||||
- debug: var=lb_creation_check_task
|
||||
|
||||
- name: lb_creation_check_task is success
|
||||
assert:
|
||||
that:
|
||||
- lb_creation_check_task is success
|
||||
|
||||
- name: lb_creation_check_task is changed
|
||||
assert:
|
||||
that:
|
||||
- lb_creation_check_task is changed
|
||||
|
||||
- name: Create load-balancer
|
||||
scaleway_lb:
|
||||
state: present
|
||||
name: '{{ name }}'
|
||||
region: '{{ scaleway_region }}'
|
||||
organization_id: '{{ scw_org }}'
|
||||
description: '{{ description }}'
|
||||
tags: '{{ tags }}'
|
||||
wait: true
|
||||
register: lb_creation_task
|
||||
|
||||
- debug: var=lb_creation_task
|
||||
|
||||
- name: lb_creation_task is success
|
||||
assert:
|
||||
that:
|
||||
- lb_creation_task is success
|
||||
|
||||
- name: lb_creation_task is changed
|
||||
assert:
|
||||
that:
|
||||
- lb_creation_task is changed
|
||||
|
||||
- name: Assert that the load-balancer is in a valid state
|
||||
assert:
|
||||
that:
|
||||
- lb_creation_task.scaleway_lb.status == "ready"
|
||||
|
||||
- name: Create load-balancer (Confirmation)
|
||||
scaleway_lb:
|
||||
state: present
|
||||
name: '{{ name }}'
|
||||
region: '{{ scaleway_region }}'
|
||||
organization_id: '{{ scw_org }}'
|
||||
tags: '{{ tags }}'
|
||||
description: '{{ description }}'
|
||||
register: lb_creation_confirmation_task
|
||||
|
||||
- debug: var=lb_creation_confirmation_task
|
||||
|
||||
- name: lb_creation_confirmation_task is success
|
||||
assert:
|
||||
that:
|
||||
- lb_creation_confirmation_task is success
|
||||
|
||||
- name: lb_creation_confirmation_task is not changed
|
||||
assert:
|
||||
that:
|
||||
- lb_creation_confirmation_task is not changed
|
||||
|
||||
- name: Assert that the load-balancer is in a valid state
|
||||
assert:
|
||||
that:
|
||||
- lb_creation_confirmation_task.scaleway_lb.status == "ready"
|
||||
|
||||
- name: Update load-balancer (Check)
|
||||
check_mode: yes
|
||||
scaleway_lb:
|
||||
state: present
|
||||
name: '{{ name }}'
|
||||
region: '{{ scaleway_region }}'
|
||||
organization_id: '{{ scw_org }}'
|
||||
tags: '{{ tags }}'
|
||||
description: '{{ updated_description }}'
|
||||
register: lb_update_check_task
|
||||
|
||||
- debug: var=lb_update_check_task
|
||||
|
||||
- name: lb_update_check_task is success
|
||||
assert:
|
||||
that:
|
||||
- lb_update_check_task is success
|
||||
|
||||
- name: lb_update_check_task is changed
|
||||
assert:
|
||||
that:
|
||||
- lb_update_check_task is changed
|
||||
|
||||
- name: Update load-balancer
|
||||
scaleway_lb:
|
||||
state: present
|
||||
name: '{{ name }}'
|
||||
region: '{{ scaleway_region }}'
|
||||
tags: '{{ tags }}'
|
||||
organization_id: '{{ scw_org }}'
|
||||
description: '{{ updated_description }}'
|
||||
wait: true
|
||||
register: lb_update_task
|
||||
|
||||
- debug: var=lb_update_task
|
||||
|
||||
- name: lb_update_task is success
|
||||
assert:
|
||||
that:
|
||||
- lb_update_task is success
|
||||
|
||||
- name: lb_update_task is changed
|
||||
assert:
|
||||
that:
|
||||
- lb_update_task is changed
|
||||
|
||||
- name: Assert that the load-balancer is in a valid state
|
||||
assert:
|
||||
that:
|
||||
- lb_update_task.scaleway_lb.status == "ready"
|
||||
|
||||
- name: Update load-balancer (Confirmation)
|
||||
scaleway_lb:
|
||||
state: present
|
||||
name: '{{ name }}'
|
||||
region: '{{ scaleway_region }}'
|
||||
tags: '{{ tags }}'
|
||||
organization_id: '{{ scw_org }}'
|
||||
description: '{{ updated_description }}'
|
||||
register: lb_update_confirmation_task
|
||||
|
||||
- debug: var=lb_update_confirmation_task
|
||||
|
||||
- name: lb_update_confirmation_task is success
|
||||
assert:
|
||||
that:
|
||||
- lb_update_confirmation_task is success
|
||||
|
||||
- name: lb_update_confirmation_task is not changed
|
||||
assert:
|
||||
that:
|
||||
- lb_update_confirmation_task is not changed
|
||||
|
||||
- name: Assert that the load-balancer is in a valid state
|
||||
assert:
|
||||
that:
|
||||
- lb_update_confirmation_task.scaleway_lb.status == "ready"
|
||||
|
||||
- name: Delete load-balancer (Check)
|
||||
check_mode: yes
|
||||
scaleway_lb:
|
||||
state: absent
|
||||
name: '{{ name }}'
|
||||
region: '{{ scaleway_region }}'
|
||||
description: '{{ description }}'
|
||||
organization_id: '{{ scw_org }}'
|
||||
register: lb_deletion_check_task
|
||||
|
||||
- name: lb_deletion_check_task is success
|
||||
assert:
|
||||
that:
|
||||
- lb_deletion_check_task is success
|
||||
|
||||
- name: lb_deletion_check_task is changed
|
||||
assert:
|
||||
that:
|
||||
- lb_deletion_check_task is changed
|
||||
|
||||
- name: Delete load-balancer
|
||||
scaleway_lb:
|
||||
state: absent
|
||||
name: '{{ name }}'
|
||||
region: '{{ scaleway_region }}'
|
||||
description: '{{ description }}'
|
||||
organization_id: '{{ scw_org }}'
|
||||
wait: true
|
||||
register: lb_deletion_task
|
||||
|
||||
- name: lb_deletion_task is success
|
||||
assert:
|
||||
that:
|
||||
- lb_deletion_task is success
|
||||
|
||||
- name: lb_deletion_task is changed
|
||||
assert:
|
||||
that:
|
||||
- lb_deletion_task is changed
|
||||
|
||||
- name: Delete load-balancer (Confirmation)
|
||||
scaleway_lb:
|
||||
state: absent
|
||||
name: '{{ name }}'
|
||||
region: '{{ scaleway_region }}'
|
||||
description: '{{ description }}'
|
||||
organization_id: '{{ scw_org }}'
|
||||
register: lb_deletion_confirmation_task
|
||||
|
||||
- name: lb_deletion_confirmation_task is success
|
||||
assert:
|
||||
that:
|
||||
- lb_deletion_confirmation_task is success
|
||||
|
||||
- name: lb_deletion_confirmation_task is not changed
|
||||
assert:
|
||||
that:
|
||||
- lb_deletion_confirmation_task is not changed
|
|
@ -10,6 +10,7 @@
|
|||
- { role: scaleway_image_facts, tags: test_scaleway_image_facts }
|
||||
- { role: scaleway_ip, tags: test_scaleway_ip }
|
||||
- { role: scaleway_ip_facts, tags: test_scaleway_ip_facts }
|
||||
- { role: scaleway_lb, tags: test_scaleway_lb }
|
||||
- { role: scaleway_organization_facts, tags: test_scaleway_organization_facts }
|
||||
- { role: scaleway_s3, tags: test_scaleway_s3 }
|
||||
- { role: scaleway_security_group_facts, tags: test_scaleway_security_group_facts }
|
||||
|
|
Loading…
Reference in a new issue