Files
ansible-collections-openstack/plugins/modules/coe_cluster.py
Jakob Meng e4be201f20 Properly documented openstacksdk version requirements
With "extends_documentation_fragment: ['openstack.cloud.openstack']"
it is not necessary to list required Python libraries in section
'requirements' of DOCUMENTATION docstring in modules. Ansible will
merge requirements from doc fragments and DOCUMENTATION docstring
which previously resulted in duplicates such as in server module [0]:

* openstacksdk
* openstacksdk >= 0.36, < 0.99.0
* python >= 3.6

When removing the 'requirements' section from server module, then
Ansible will list openstacksdk once only:

* openstacksdk >= 0.36, < 0.99.0
* python >= 3.6

To see what documentation Ansible will produce for server module run:

  ansible-doc --type module openstack.cloud.server

[0] https://docs.ansible.com/ansible/latest/collections/openstack/\
    cloud/server_module.html

Change-Id: Ia53c2c34436c7a72080602f5699e82d20f677b8b
2023-01-16 13:52:45 +01:00

368 lines
12 KiB
Python

#!/usr/bin/python
# Copyright (c) 2018 Catalyst IT Ltd.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = r'''
---
module: coe_cluster
short_description: Manage COE cluster in OpenStack Cloud
author: OpenStack Ansible SIG
description:
- Add or remove a COE (Container Orchestration Engine) cluster
via OpenStack's Magnum aka Container Infrastructure Management API.
options:
cluster_template_id:
description:
- The template ID of cluster template.
- Required if I(state) is C(present).
type: str
discovery_url:
description:
- URL used for cluster node discovery.
type: str
docker_volume_size:
description:
- The size in GB of the docker volume.
type: int
flavor_id:
description:
- The flavor of the minion node for this cluster template.
type: str
floating_ip_enabled:
description:
- Indicates whether created cluster should have a floating ip.
- Whether enable or not using the floating IP of cloud provider. Some
cloud providers used floating IP, some used public IP, thus Magnum
provide this option for specifying the choice of using floating IP.
- If not set, the value of I(floating_ip_enabled) of the cluster template
specified with I(cluster_template_id) will be used.
- When I(floating_ip_enabled) is set to C(true), then
I(external_network_id) in cluster template must be defined.
type: bool
keypair:
description:
- Name of the keypair to use.
type: str
labels:
description:
- One or more key/value pairs.
type: raw
master_count:
description:
- The number of master nodes for this cluster.
- Magnum's default value for I(master_count) is 1.
type: int
master_flavor_id:
description:
- The flavor of the master node for this cluster template.
type: str
name:
description:
- Name that has to be given to the cluster template.
required: true
type: str
node_count:
description:
- The number of nodes for this cluster.
- Magnum's default value for I(node_count) is 1.
type: int
state:
description:
- Indicate desired state of the resource.
choices: [present, absent]
default: present
type: str
notes:
- Return values of this module are preliminary and will most likely change
when openstacksdk has finished its transition of cloud layer functions to
resource proxies.
extends_documentation_fragment:
- openstack.cloud.openstack
'''
RETURN = r'''
id:
description: The cluster UUID.
returned: On success when I(state) is C(present).
type: str
sample: "39007a7e-ee4f-4d13-8283-b4da2e037c69"
cluster:
description: Dictionary describing the cluster.
returned: On success when I(state) is C(present).
type: complex
contains:
cluster_template_id:
description: The cluster_template UUID
type: str
sample: '7b1418c8-cea8-48fc-995d-52b66af9a9aa'
create_timeout:
description: Timeout for creating the cluster in minutes.
Default to 60 if not set.
type: int
sample: 60
id:
description: Unique UUID for this cluster.
type: str
sample: '86246a4d-a16c-4a58-9e96ad7719fe0f9d'
keypair:
description: Name of the keypair to use.
type: str
sample: mykey
location:
description: The OpenStack location of this resource.
type: str
master_count:
description: The number of master nodes for this cluster.
type: int
sample: 1
name:
description: Name that has to be given to the cluster.
type: str
sample: k8scluster
node_count:
description: The number of master nodes for this cluster.
type: int
sample: 1
properties:
description: Additional properties of the cluster template.
type: dict
sample: |
{
'api_address': 'https://172.24.4.30:6443',
'coe_version': 'v1.11.1',
'container_version': '1.12.6',
'created_at': '2018-08-16T10:29:45+00:00',
'discovery_url': 'https://discovery.etcd.io/a42...aae5',
'faults': {'0': 'ResourceInError: resources[0].resources...'},
'flavor_id': 'c1.c1r1',
'floating_ip_enabled': true,
'labels': {'key1': 'value1', 'key2': 'value2'},
'master_addresses': ['172.24.4.5'],
'master_flavor_id': 'c1.c1r1',
'node_addresses': ['172.24.4.8'],
'status_reason': 'Stack CREATE completed successfully',
'updated_at': '2018-08-16T10:39:25+00:00',
}
stack_id:
description: Stack id of the Heat stack.
type: str
sample: '07767ec6-85f5-44cb-bd63-242a8e7f0d9d'
status:
description: Status of the cluster from the heat stack.
type: str
sample: 'CREATE_COMLETE'
uuid:
description: Unique UUID for this cluster.
type: str
sample: '86246a4d-a16c-4a58-9e96ad7719fe0f9d'
'''
EXAMPLES = r'''
- name: Create a new Kubernetes cluster
openstack.cloud.coe_cluster:
cloud: devstack
cluster_template_id: k8s-ha
keypair: mykey
master_count: 3
name: k8s
node_count: 5
'''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class COEClusterModule(OpenStackModule):
argument_spec = dict(
cluster_template_id=dict(),
discovery_url=dict(),
docker_volume_size=dict(type='int'),
flavor_id=dict(),
floating_ip_enabled=dict(type='bool'),
keypair=dict(no_log=False), # := noqa no-log-needed
labels=dict(type='raw'),
master_count=dict(type='int'),
master_flavor_id=dict(),
name=dict(required=True),
node_count=dict(type='int'),
state=dict(default='present', choices=['absent', 'present']),
)
module_kwargs = dict(
required_if=[
('state', 'present', ('cluster_template_id',))
],
supports_check_mode=True,
)
def run(self):
state = self.params['state']
cluster = self._find()
if self.ansible.check_mode:
self.exit_json(changed=self._will_change(state, cluster))
if state == 'present' and not cluster:
# Create cluster
cluster = self._create()
self.exit_json(changed=True,
# for backward compatibility
id=cluster['id'],
cluster=cluster)
elif state == 'present' and cluster:
# Update cluster
update = self._build_update(cluster)
if update:
cluster = self._update(cluster, update)
self.exit_json(changed=bool(update),
# for backward compatibility
id=cluster['id'],
cluster=cluster)
elif state == 'absent' and cluster:
# Delete cluster
self._delete(cluster)
self.exit_json(changed=True)
elif state == 'absent' and not cluster:
# Do nothing
self.exit_json(changed=False)
def _build_update(self, cluster):
update = {}
# TODO: Implement support for updates.
non_updateable_keys = [k for k in ['cluster_template_id',
'discovery_url',
'docker_volume_size', 'flavor_id',
'floating_ip_enabled', 'keypair',
'master_count', 'master_flavor_id',
'name', 'node_count']
if self.params[k] is not None
and self.params[k] != cluster[k]]
labels = self.params['labels']
if labels is not None:
if isinstance(labels, str):
labels = dict([tuple(kv.split(":"))
for kv in labels.split(",")])
if labels != cluster['labels']:
non_updateable_keys.append('labels')
if non_updateable_keys:
self.fail_json(msg='Cannot update parameters {0}'
.format(non_updateable_keys))
attributes = dict((k, self.params[k])
for k in []
if self.params[k] is not None
and self.params[k] != cluster[k])
if attributes:
update['attributes'] = attributes
return update
def _create(self):
# TODO: Complement *_id parameters with find_* functions to allow
# specifying names in addition to IDs.
kwargs = dict((k, self.params[k])
for k in ['cluster_template_id', 'discovery_url',
'docker_volume_size', 'flavor_id',
'floating_ip_enabled', 'keypair',
'master_count', 'master_flavor_id',
'name', 'node_count']
if self.params[k] is not None)
labels = self.params['labels']
if labels is not None:
if isinstance(labels, str):
labels = dict([tuple(kv.split(":"))
for kv in labels.split(",")])
kwargs['labels'] = labels
kwargs['create_timeout'] = self.params['timeout']
cluster = self.conn.create_coe_cluster(**kwargs)
if not self.params['wait']:
# openstacksdk's create_coe_cluster() returns a cluster's uuid only
# but we cannot use self.conn.get_coe_cluster(cluster_id) because
# it might return None as long as the cluster is being set up.
return cluster
cluster_id = cluster['id']
if self.params['wait']:
for count in self.sdk.utils.iterate_timeout(
timeout=self.params['timeout'],
message="Timeout waiting for cluster to be present"
):
# Fetch cluster again
cluster = self.conn.get_coe_cluster(cluster_id)
if cluster is None:
continue
elif cluster.status.lower() == 'active':
break
elif cluster.status.lower() in ['error']:
self.fail_json(msg="{0} transitioned to failure state {1}"
.format(cluster.name, 'error'))
return cluster
def _delete(self, cluster):
self.conn.delete_coe_cluster(cluster.name)
if self.params['wait']:
for count in self.sdk.utils.iterate_timeout(
timeout=self.params['timeout'],
message="Timeout waiting for cluster to be absent"
):
cluster = self.conn.get_coe_cluster(cluster.id)
if cluster is None:
break
elif cluster['status'].lower() == 'deleted':
break
def _find(self):
name = self.params['name']
filters = {}
cluster_template_id = self.params['cluster_template_id']
if cluster_template_id is not None:
filters['cluster_template_id'] = cluster_template_id
return self.conn.get_coe_cluster(name_or_id=name, filters=filters)
def _update(self, cluster, update):
attributes = update.get('attributes')
if attributes:
# TODO: Implement support for updates.
# cluster = self.conn.update_coe_cluster(...)
pass
return cluster
def _will_change(self, state, cluster):
if state == 'present' and not cluster:
return True
elif state == 'present' and cluster:
return bool(self._build_update(cluster))
elif state == 'absent' and cluster:
return True
else:
# state == 'absent' and not cluster:
return False
def main():
module = COEClusterModule()
module()
if __name__ == "__main__":
main()