1
0
Fork 0
mirror of https://github.com/ansible-collections/hetzner.hcloud.git synced 2026-02-04 08:01:49 +00:00

Placement groups (#102)

* Add placement_groups

* Create server with placement_group

* Add/remove server to/from placement_group

* Remove deprecated tmage test

* Add changelogs

* Add placement groups to hcloud_server_info

* Deprecate force_upgrade flag
This commit is contained in:
Adrian Huber 2021-08-16 12:31:02 +02:00 committed by GitHub
parent 8cd7b9f997
commit 7d3828837c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 520 additions and 31 deletions

View file

@ -0,0 +1,2 @@
major_changes:
- Introduction of placement groups

View file

@ -0,0 +1,230 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# 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
DOCUMENTATION = """
---
module: hcloud_placement_group
short_description: Create and manage placement groups on the Hetzner Cloud.
description:
- Create, update and manage placement groups on the Hetzner Cloud.
author:
- Adrian Huber (@Adi146)
options:
id:
description:
- The ID of the Hetzner Cloud placement group to manage.
- Only required if no placement group I(name) is given
type: int
name:
description:
- The Name of the Hetzner Cloud placement group to manage.
- Only required if no placement group I(id) is given, or a placement group does not exists.
type: str
labels:
description:
- User-defined labels (key-value pairs)
type: dict
type:
description:
- The Type of the Hetzner Cloud placement group.
type: str
state:
description:
- State of the placement group.
default: present
choices: [ absent, present ]
type: str
requirements:
- hcloud-python >= 1.15.0
extends_documentation_fragment:
- hetzner.hcloud.hcloud
"""
EXAMPLES = """
- name: Create a basic placement group
hcloud_placement_group:
name: my-placement-group
state: present
type: spread
- name: Create a placement group with labels
hcloud_placement_group:
name: my-placement-group
type: spread
labels:
key: value
mylabel: 123
state: present
- name: Ensure the placement group is absent (remove if needed)
hcloud_placement_group:
name: my-placement-group
state: absent
"""
RETURN = """
hcloud_placement_group:
description: The placement group instance
returned: Always
type: complex
contains:
id:
description: Numeric identifier of the placement group
returned: always
type: int
sample: 1937415
name:
description: Name of the placement group
returned: always
type: str
sample: my placement group
labels:
description: User-defined labels (key-value pairs)
returned: always
type: dict
type:
description: Type of the placement group
returned: always
type: str
sample: spread
servers:
description: Server IDs of the placement group
returned: always
type: list
elements: int
sample:
- 4711
- 4712
"""
from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
class AnsibleHcloudPlacementGroup(Hcloud):
def __init__(self, module):
Hcloud.__init__(self, module, "hcloud_placement_group")
self.hcloud_placement_group = None
def _prepare_result(self):
return {
"id": to_native(self.hcloud_placement_group.id),
"name": to_native(self.hcloud_placement_group.name),
"labels": self.hcloud_placement_group.labels,
"type": to_native(self.hcloud_placement_group.type),
"servers": self.hcloud_placement_group.servers,
}
def _get_placement_group(self):
try:
if self.module.params.get("id") is not None:
self.hcloud_placement_group = self.client.placement_groups.get_by_id(
self.module.params.get("id")
)
elif self.module.params.get("name") is not None:
self.hcloud_placement_group = self.client.placement_groups.get_by_name(
self.module.params.get("name")
)
except Exception as e:
self.module.fail_json(msg=e.message)
def _create_placement_group(self):
self.module.fail_on_missing_params(
required_params=["name"]
)
params = {
"name": self.module.params.get("name"),
"type": self.module.params.get("type"),
"labels": self.module.params.get("labels"),
}
if not self.module.check_mode:
try:
self.client.placement_groups.create(**params)
except Exception as e:
self.module.fail_json(msg=e.message, **params)
self._mark_as_changed()
self._get_placement_group()
def _update_placement_group(self):
name = self.module.params.get("name")
if name is not None and self.hcloud_placement_group.name != name:
self.module.fail_on_missing_params(
required_params=["id"]
)
if not self.module.check_mode:
self.hcloud_placement_group.update(name=name)
self._mark_as_changed()
labels = self.module.params.get("labels")
if labels is not None and self.hcloud_placement_group.labels != labels:
if not self.module.check_mode:
self.hcloud_placement_group.update(labels=labels)
self._mark_as_changed()
self._get_placement_group()
def present_placement_group(self):
self._get_placement_group()
if self.hcloud_placement_group is None:
self._create_placement_group()
else:
self._update_placement_group()
def delete_placement_group(self):
self._get_placement_group()
if self.hcloud_placement_group is not None:
if not self.module.check_mode:
self.client.placement_groups.delete(self.hcloud_placement_group)
self._mark_as_changed()
self.hcloud_placement_group = None
@staticmethod
def define_module():
return AnsibleModule(
argument_spec=dict(
id={"type": "int"},
name={"type": "str"},
labels={"type": "dict"},
type={"type": "str"},
state={
"choices": ["absent", "present"],
"default": "present",
},
**Hcloud.base_module_arguments()
),
required_one_of=[['id', 'name']],
required_if=[['state', 'present', ['name']]],
supports_check_mode=True,
)
def main():
module = AnsibleHcloudPlacementGroup.define_module()
hcloud = AnsibleHcloudPlacementGroup(module)
state = module.params.get("state")
if state == "absent":
hcloud.delete_placement_group()
elif state == "present":
hcloud.present_placement_group()
module.exit_json(**hcloud.get_result())
if __name__ == "__main__":
main()

View file

@ -81,10 +81,17 @@ options:
default: no
force_upgrade:
description:
- Deprecated
- Force the upgrade of the server.
- Power off the server if it is running on upgrade.
type: bool
default: no
force:
description:
- Force the update of the server.
- May power off the server if update.
type: bool
default: no
allow_deprecated_image:
description:
- Allows the creation of servers with deprecated images.
@ -113,6 +120,10 @@ options:
- Protect the Server for rebuild.
- Needs to be the same as I(delete_protection).
type: bool
placement_group:
description:
- Placement Group of the server.
type: str
state:
description:
- State of the server.
@ -180,6 +191,19 @@ EXAMPLES = """
name: my-server
image: ubuntu-18.04
state: rebuild
- name: Add server to placement group
hcloud_server:
name: my-server
placement_group: my-placement-group
force: True
state: present
- name: Remove server from placement group
hcloud_server:
name: my-server
placement_group: null
state: present
"""
RETURN = """
@ -223,6 +247,12 @@ hcloud_server:
returned: always
type: str
sample: fsn1
placement_group:
description: Placement Group of the server
type: str
returned: always
sample: 4711
version_added: "1.5.0"
datacenter:
description: Name of the datacenter of the server
returned: always
@ -283,6 +313,7 @@ class AnsibleHcloudServer(Hcloud):
def _prepare_result(self):
image = None if self.hcloud_server.image is None else to_native(self.hcloud_server.image.name)
placement_group = None if self.hcloud_server.placement_group is None else to_native(self.hcloud_server.placement_group.name)
return {
"id": to_native(self.hcloud_server.id),
"name": to_native(self.hcloud_server.name),
@ -292,6 +323,7 @@ class AnsibleHcloudServer(Hcloud):
"server_type": to_native(self.hcloud_server.server_type.name),
"datacenter": to_native(self.hcloud_server.datacenter.name),
"location": to_native(self.hcloud_server.datacenter.location.name),
"placement_group": placement_group,
"rescue_enabled": self.hcloud_server.rescue_enabled,
"backup_window": to_native(self.hcloud_server.backup_window),
"labels": self.hcloud_server.labels,
@ -323,7 +355,8 @@ class AnsibleHcloudServer(Hcloud):
"server_type": self._get_server_type(),
"user_data": self.module.params.get("user_data"),
"labels": self.module.params.get("labels"),
"image": self._get_image()
"image": self._get_image(),
"placement_group": self._get_placement_group(),
}
if self.module.params.get("ssh_keys") is not None:
@ -424,8 +457,27 @@ class AnsibleHcloudServer(Hcloud):
return server_type
def _get_placement_group(self):
if self.module.params.get("placement_group") is None:
return None
placement_group = self.client.placement_groups.get_by_name(
self.module.params.get("placement_group")
)
if placement_group is None:
try:
placement_group = self.client.placement_groups.get_by_id(self.module.params.get("placement_group"))
except Exception:
self.module.fail_json(msg="placement_group %s was not found" % self.module.params.get("placement_group"))
return placement_group
def _update_server(self):
self.module.warn("force_upgrade is deprecated, use force instead")
try:
previous_server_status = self.hcloud_server.status
rescue_mode = self.module.params.get("rescue_mode")
if rescue_mode and self.hcloud_server.rescue_enabled is False:
if not self.module.check_mode:
@ -482,19 +534,26 @@ class AnsibleHcloudServer(Hcloud):
for a in actions:
a.wait_until_finished()
if "placement_group" in self.module.params:
if self.module.params["placement_group"] is None and self.hcloud_server.placement_group is not None:
if not self.module.check_mode:
self.hcloud_server.remove_from_placement_group().wait_until_finished()
self._mark_as_changed()
else:
placement_group = self._get_placement_group()
if (
placement_group is not None and
(self.hcloud_server.placement_group is None or self.hcloud_server.placement_group.id != placement_group.id)
):
self.stop_server_if_forced()
if not self.module.check_mode:
self.hcloud_server.add_to_placement_group(placement_group)
self._mark_as_changed()
server_type = self.module.params.get("server_type")
if server_type is not None and self.hcloud_server.server_type.name != server_type:
previous_server_status = self.hcloud_server.status
state = self.module.params.get("state")
if previous_server_status == Server.STATUS_RUNNING:
if not self.module.check_mode:
if self.module.params.get("force_upgrade") or state == "stopped":
self.stop_server() # Only stopped server can be upgraded
else:
self.module.warn(
"You can not upgrade a running instance %s. You need to stop the instance or use force_upgrade=yes."
% self.hcloud_server.name
)
self.stop_server_if_forced()
timeout = 100
if self.module.params.get("upgrade_disk"):
timeout = (
@ -505,11 +564,17 @@ class AnsibleHcloudServer(Hcloud):
server_type=self._get_server_type(),
upgrade_disk=self.module.params.get("upgrade_disk"),
).wait_until_finished(timeout)
if state == "present" and previous_server_status == Server.STATUS_RUNNING or state == "started":
self.start_server()
self._mark_as_changed()
if (
not self.module.check_mode and
(
self.module.params.get("state") == "present" and previous_server_status == Server.STATUS_RUNNING or
self.module.params.get("state") == "started"
)
):
self.start_server()
delete_protection = self.module.params.get("delete_protection")
rebuild_protection = self.module.params.get("rebuild_protection")
if (delete_protection is not None and rebuild_protection is not None) and (
@ -557,6 +622,24 @@ class AnsibleHcloudServer(Hcloud):
except Exception as e:
self.module.fail_json(msg=e.message)
def stop_server_if_forced(self):
previous_server_status = self.hcloud_server.status
if previous_server_status == Server.STATUS_RUNNING and not self.module.check_mode:
if (
self.module.params.get("force_upgrade") or
self.module.params.get("force") or
self.module.params.get("state") == "stopped"
):
self.stop_server() # Only stopped server can be upgraded
return previous_server_status
else:
self.module.warn(
"You can not upgrade a running instance %s. You need to stop the instance or use force=yes."
% self.hcloud_server.name
)
return None
def rebuild_server(self):
self.module.fail_on_missing_params(
required_params=["image"]
@ -606,11 +689,13 @@ class AnsibleHcloudServer(Hcloud):
labels={"type": "dict"},
backups={"type": "bool"},
upgrade_disk={"type": "bool", "default": False},
force={"type": "bool", "default": False},
force_upgrade={"type": "bool", "default": False},
allow_deprecated_image={"type": "bool", "default": False},
rescue_mode={"type": "str"},
delete_protection={"type": "bool"},
rebuild_protection={"type": "bool"},
placement_group={"type": "str"},
state={
"choices": ["absent", "present", "restarted", "started", "stopped", "rebuild"],
"default": "present",

View file

@ -92,6 +92,12 @@ hcloud_server_info:
returned: always
type: str
sample: fsn1
placement_group:
description: Placement Group of the server
type: str
returned: always
sample: 4711
version_added: "1.5.0"
datacenter:
description: Name of the datacenter of the server
returned: always
@ -146,6 +152,7 @@ class AnsibleHcloudServerInfo(Hcloud):
for server in self.hcloud_server_info:
if server is not None:
image = None if server.image is None else to_native(server.image.name)
placement_group = None if server.placement_group is None else to_native(server.placement_group.name)
tmp.append({
"id": to_native(server.id),
"name": to_native(server.name),
@ -155,6 +162,7 @@ class AnsibleHcloudServerInfo(Hcloud):
"server_type": to_native(server.server_type.name),
"datacenter": to_native(server.datacenter.name),
"location": to_native(server.datacenter.location.name),
"placement_group": placement_group,
"rescue_enabled": server.rescue_enabled,
"backup_window": to_native(server.backup_window),
"labels": server.labels,

View file

@ -0,0 +1,2 @@
cloud/hcloud
shippable/hcloud/group2

View file

@ -0,0 +1,6 @@
# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
---
hcloud_prefix: "tests"
hcloud_placement_group_name: "{{hcloud_prefix}}-i"
hcloud_server_name: "{{hcloud_prefix}}-i"

View file

@ -0,0 +1,3 @@
collections:
- community.general.ipfilter
- hetzner.cloud

View file

@ -0,0 +1,169 @@
# Copyright: (c) 2020, Hetzner Cloud GmbH <info@hetzner-cloud.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
---
- name: setup placement group to be absent
hcloud_placement_group:
name: "{{ hcloud_placement_group_name }}"
state: absent
- name: setup server to be absent
hcloud_server:
name: "{{ hcloud_server_name }}"
state: absent
- name: test missing required parameters on create placement group
hcloud_placement_group:
register: result
ignore_errors: yes
- name: verify fail test missing required parameters on create placement group
assert:
that:
- result is failed
- 'result.msg == "one of the following is required: id, name"'
- name: test create placement group with check mode
hcloud_placement_group:
name: "{{ hcloud_placement_group_name }}"
type: spread
register: result
check_mode: yes
- name: test create placement group with check mode
assert:
that:
- result is changed
- name: test create placement group
hcloud_placement_group:
name: "{{ hcloud_placement_group_name }}"
type: spread
labels:
key: value
my-label: label
register: placement_group
- name: verify create placement group
assert:
that:
- placement_group is changed
- placement_group.hcloud_placement_group.name == "{{ hcloud_placement_group_name }}"
- placement_group.hcloud_placement_group.type == "spread"
- placement_group.hcloud_placement_group.servers | list | count == 0
- name: test create placement group idempotence
hcloud_placement_group:
name: "{{ hcloud_placement_group_name }}"
type: spread
labels:
key: value
my-label: label
register: result
- name: verify create placement group idempotence
assert:
that:
- result is not changed
- name: test create server with placement group
hcloud_server:
name: "{{ hcloud_server_name }}"
server_type: cpx11
placement_group: "{{ hcloud_placement_group_name }}"
image: "ubuntu-20.04"
ssh_keys:
- ci@ansible.hetzner.cloud
state: present
register: server
- name: verify create server with placement group
assert:
that:
- server is changed
- server.hcloud_server.placement_group == "{{ hcloud_placement_group_name }}"
- name: test remove server from placement group
hcloud_server:
name: "{{ hcloud_server_name }}"
placement_group: null
state: present
register: result
- name: verify remove server from placement group
assert:
that:
- result is changed
- result.hcloud_server.placement_group == None
- name: test add server to placement group
hcloud_server:
name: "{{ hcloud_server_name }}"
placement_group: "{{ hcloud_server_name }}"
force: True
state: present
register: result
- name: verify add server to placement group
assert:
that:
- result is changed
- result.hcloud_server.placement_group == "{{ hcloud_placement_group_name }}"
- result.hcloud_server.status == "running"
- name: test add server to placement group idempotence
hcloud_server:
name: "{{ hcloud_server_name }}"
placement_group: "{{ hcloud_server_name }}"
force: True
state: present
register: result
- name: verify add server to placement group idempotence
assert:
that:
- result is not changed
- result.hcloud_server.placement_group == "{{ hcloud_placement_group_name }}"
- result.hcloud_server.status == "running"
- name: test update placement group with check mode
hcloud_placement_group:
id: "{{ placement_group.hcloud_placement_group.id }}"
name: "changed-{{ hcloud_placement_group_name }}"
register: result
check_mode: yes
- name: verify update placement group with check mode
assert:
that:
- result is changed
- name: test update placement group
hcloud_placement_group:
id: "{{ placement_group.hcloud_placement_group.id }}"
name: "changed-{{ hcloud_placement_group_name }}"
labels:
key: value
register: result
- name: verify update placement group
assert:
that:
- result is changed
- result.hcloud_placement_group.name == "changed-{{ hcloud_placement_group_name }}"
- name: test update placement group idempotence
hcloud_placement_group:
id: "{{ placement_group.hcloud_placement_group.id }}"
name: "changed-{{ hcloud_placement_group_name }}"
labels:
key: value
register: result
- name: verify update placement group idempotence
assert:
that:
- result is not changed
- name: absent server
hcloud_server:
id: "{{ server.hcloud_server.id }}"
state: absent
- name: absent placement group
hcloud_placement_group:
id: "{{ placement_group.hcloud_placement_group.id }}"
state: absent
register: result
- name: verify absent placement group
assert:
that:
- result is success

View file

@ -49,22 +49,6 @@
- result is failed
- 'result.msg == "Image my-not-existing-image-20.04 was not found"'
# Temporary test case to test deprecated images. This test will fail when the ubuntu-16.04 image was removed
# feel free to remove this test then.
- name: test create server with deprecated image
hcloud_server:
name: "{{ hcloud_server_name }}"
server_type: cx11
image: ubuntu-16.04
state: present
register: result
ignore_errors: yes
- name: verify fail test create server deprecated image
assert:
that:
- result is failed
- 'result.msg == "You try to use a deprecated image. The image ubuntu-16.04 will continue to be available until 2021-06-22. If you want to use this image use allow_deprecated_image=yes."'
- name: test create server with check mode
hcloud_server:
name: "{{ hcloud_server_name }}"