Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Scaleway lb backend #56386

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/ansible/module_utils/scaleway.py
Expand Up @@ -179,3 +179,9 @@ def warn(self, x):
"fr-par-1",
"nl-ams-1",
]

# New convention about region naming
SCALEWAY_NEW_LOCATION = {
'fr-par': {'name': 'Paris 1', 'country': 'FR', "api_endpoint": 'https://api-world.scaleway.com'},

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://api.scaleway.com (without world)

'nl-ams': {'name': 'Amsterdam 1', 'country': 'NL', "api_endpoint": 'https://api-world.scaleway.com'}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://api.scaleway.com (without world)

}
4 changes: 3 additions & 1 deletion lib/ansible/modules/cloud/scaleway/scaleway_lb.py
Expand Up @@ -148,6 +148,7 @@
import time
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.scaleway import SCALEWAY_REGIONS, SCALEWAY_ENDPOINT, scaleway_argument_spec, Scaleway
from ansible.module_utils.scaleway import SCALEWAY_NEW_LOCATION, scaleway_argument_spec, Scaleway

STABLE_STATES = (
"ready",
Expand Down Expand Up @@ -319,6 +320,7 @@ def core(module):
"organization_id": module.params["organization_id"]
}
module.params['api_url'] = SCALEWAY_ENDPOINT
module.params['api_url'] = SCALEWAY_NEW_LOCATION[region]["api_endpoint"]
api = Scaleway(module=module)
api.api_path = "lbaas/v1beta1/regions/%s/lbs" % region

Expand All @@ -332,9 +334,9 @@ def main():
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=[]),
region=dict(required=True, choices=SCALEWAY_NEW_LOCATION.keys()),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove choices

organization_id=dict(required=True),
wait=dict(type="bool", default=False),
wait_timeout=dict(type="int", default=300),
Expand Down
361 changes: 361 additions & 0 deletions lib/ansible/modules/cloud/scaleway/scaleway_lb_backend.py
@@ -0,0 +1,361 @@
#!/usr/bin/python
#
# Scaleway Load-balancer backend 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_back
short_description: Scaleway load-balancer backend management module
version_added: "2.8"
author: Remy Leone (@sieben)
description:
- "This module manages load-balancers backend on Scaleway."
extends_documentation_fragment: scaleway

options:

name:
description:
- Name of the load-balancer backend

organization:
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

forward_port:
description:
- User sessions will be forwarded to this port of backend servers

forward_port_algorithm:
description:
- Load balancing algorithm:
- Round-Robin: Equal share
- leastconn: Least amount of TCP connection
choices:
- leastconn
- roundrobin

forward_protocol:
description:
- Backend protocol
choices:
- tcp
- http
default:
- tcp

on_marked_down_action:
description:
- Modify what occurs when a backend server is marked down
choices:
- on_marked_down_action_none
- shutdown_sessions

send_proxy_v2:
description:
- Enables PROXY protocol version 2 (must be supported by backend servers)
type: bool
default: no

server_ip:
description:
- Backend server IP addresses list (IPv4 or IPv6)

sticky_sessions:
description:
- Enables cookie-based session persistence
default: none
choices:
- cookie
- none
- table (Will match on IP)

sticky_sessions_cookie_name:
description:
- Cookie name for sticky sessions

timeout_connect:
description:
- Maximum initial server connection establishment time in milliseconds

timeout_server:
description:
- Maximum server connection inactivity time in milliseconds

timeout_tunnel:
description:
- Maximum tunnel inactivity time in milliseconds

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 = '''
'''

RETURNS = '''
'''

import datetime
import time
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.scaleway import SCALEWAY_NEW_LOCATION, 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"],
}


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_backend, 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_backend)
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_backend))
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_backend, wished_lb_backend):
diff = {
attr: wished_lb_backend[attr] for attr in MUTABLE_ATTRIBUTES if target_lb_backend[attr] != wished_lb_backend[attr]
}
if diff:
return {attr: wished_lb_backend[attr] for attr in MUTABLE_ATTRIBUTES}
else:
return diff


def present_strategy(api, wished_lb_backend):
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["backends"]
lb_lookup = dict((lb["name"], lb)
for lb in lbs_list)

if wished_lb_backend["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_backend))
creation_response = api.post(path=api.api_path,
data=payload_from_wished_lb(wished_lb_backend))

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_backend=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_backend["name"]]
patch_payload = lb_attributes_should_be_changed(target_lb_backend=target_lb,
wished_lb_backend=wished_lb_backend)

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_backend=target_lb)
return changed, lb_patch_response.json


def absent_strategy(api, wished_lb_backend):
response = api.get(path=api.api_path)
changed = False

status_code = response.status_code
lbs_json = response.json
lbs_list = lbs_json["backends"]

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_backend["name"] not in lb_lookup.keys():
return changed, {}

target_lb = lb_lookup[wished_lb_backend["name"]]
changed = True
if api.module.check_mode:
return changed, {"status": "Load-balancer would be destroyed"}

wait_to_complete_state_transition(api=api, lb_backend=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_backend=target_lb)
return changed, response.json


state_strategy = {
"present": present_strategy,
"absent": absent_strategy
}


def core(module):
region = module.params["region"]
lb_id = module.params["lb_id"]
wished_load_balancer_back = {
"name": module.params["name"],
"state": module.params["state"],
"lb_id": module.params["lb_id"],
"forward_port": module.params["forward_port"],
"forward_protocol": module.params["forward_protocol"],
"forward_port_algorithm": module.params["forward_port_algorithm"],
"sticky_sessions": module.params["sticky_sessions"],
"timeout_connect": module.params["timeout_connect"],
"timeout_server": module.params["timeout_server"],
"timeout_tunnel": module.params["timeout_tunnel"]
}
module.params['api_url'] = SCALEWAY_NEW_LOCATION[region]["api_endpoint"]
api = Scaleway(module=module)
api.api_path = "lbaas/v1beta1/regions/%s/lbs/%s/backends" % (region, lb_id)

changed, summary = state_strategy[wished_load_balancer_back["state"]](api=api,
wished_lb_backend=wished_load_balancer_back)
module.exit_json(changed=changed, scaleway_lb=summary)


def main():
argument_spec = scaleway_argument_spec()
argument_spec.update(dict(
name=dict(required=True),
state=dict(choices=state_strategy.keys(), default='present'),
lb_id=dict(required=True),
forward_port=dict(required=True, type='int'),
forward_protocol=dict(choices=["http", "tcp"], default="tcp"),
forward_port_algorithm=dict(choices=["roundrobin", "leastconn"], default="roundrobin"),
region=dict(required=True, choices=SCALEWAY_NEW_LOCATION.keys()),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove choice?

sticky_sessions=dict(choices=["cookie", "none", "table"], default="none"),
timeout_connect=dict(type="int"),
timeout_server=dict(type="int"),
timeout_tunnel=dict(type="int"),
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()