
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: I727ed95ee480bb644b5a533f6a9526973677064c
368 lines
10 KiB
Python
368 lines
10 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: compute_flavor
|
|
short_description: Manage OpenStack compute flavors
|
|
author: OpenStack Ansible SIG
|
|
description:
|
|
- Add or remove compute flavors from OpenStack.
|
|
- Updating a flavor consists of deleting and (re)creating a flavor.
|
|
options:
|
|
description:
|
|
description:
|
|
- Description of the flavor.
|
|
type: str
|
|
disk:
|
|
description:
|
|
- Size of local disk, in GB.
|
|
- Required when I(state) is C(present).
|
|
type: int
|
|
ephemeral:
|
|
description:
|
|
- Ephemeral space size, in GB.
|
|
type: int
|
|
extra_specs:
|
|
description:
|
|
- Metadata dictionary
|
|
type: dict
|
|
id:
|
|
description:
|
|
- ID for the flavor. This is optional as a unique UUID will be
|
|
assigned if a value is not specified.
|
|
- Note that this ID will only be used when first creating the flavor.
|
|
- The ID of an existing flavor cannot be changed.
|
|
- When I(id) is set to C(auto), a new id will be autogenerated.
|
|
C(auto) is kept for backward compatibility and
|
|
will be dropped in the next major release.
|
|
type: str
|
|
aliases: ['flavorid']
|
|
is_public:
|
|
description:
|
|
- Make flavor accessible to the public.
|
|
type: bool
|
|
name:
|
|
description:
|
|
- Flavor name.
|
|
required: true
|
|
type: str
|
|
ram:
|
|
description:
|
|
- Amount of memory, in MB.
|
|
- Required when I(state) is C(present).
|
|
type: int
|
|
rxtx_factor:
|
|
description:
|
|
- RX/TX factor.
|
|
type: float
|
|
state:
|
|
description:
|
|
- Indicate desired state of the resource.
|
|
- When I(state) is C(present), then I(ram), I(vcpus), and I(disk) are
|
|
required. There are no default values for those parameters.
|
|
choices: ['present', 'absent']
|
|
default: present
|
|
type: str
|
|
swap:
|
|
description:
|
|
- Swap space size, in MB.
|
|
type: int
|
|
vcpus:
|
|
description:
|
|
- Number of virtual CPUs.
|
|
- Required when I(state) is C(present).
|
|
type: int
|
|
extends_documentation_fragment:
|
|
- openstack.cloud.openstack
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
- name: Create tiny flavor with 1024MB RAM, 1 vCPU, 10GB disk, 10GB ephemeral
|
|
openstack.cloud.compute_flavor:
|
|
cloud: mycloud
|
|
state: present
|
|
name: tiny
|
|
ram: 1024
|
|
vcpus: 1
|
|
disk: 10
|
|
ephemeral: 10
|
|
description: "I am flavor mycloud"
|
|
|
|
- name: Delete tiny flavor
|
|
openstack.cloud.compute_flavor:
|
|
cloud: mycloud
|
|
state: absent
|
|
name: tiny
|
|
|
|
- name: Create flavor with metadata
|
|
openstack.cloud.compute_flavor:
|
|
cloud: mycloud
|
|
state: present
|
|
name: tiny
|
|
ram: 1024
|
|
vcpus: 1
|
|
disk: 10
|
|
extra_specs:
|
|
"quota:disk_read_iops_sec": 5000
|
|
"aggregate_instance_extra_specs:pinned": false
|
|
'''
|
|
|
|
RETURN = '''
|
|
flavor:
|
|
description: Dictionary describing the flavor.
|
|
returned: On success when I(state) is 'present'
|
|
type: dict
|
|
contains:
|
|
description:
|
|
description: Description attached to flavor
|
|
returned: success
|
|
type: str
|
|
sample: Example description
|
|
disk:
|
|
description: Size of local disk, in GB.
|
|
returned: success
|
|
type: int
|
|
sample: 10
|
|
ephemeral:
|
|
description: Ephemeral space size, in GB.
|
|
returned: success
|
|
type: int
|
|
sample: 10
|
|
extra_specs:
|
|
description: Flavor metadata
|
|
returned: success
|
|
type: dict
|
|
sample:
|
|
"quota:disk_read_iops_sec": 5000
|
|
"aggregate_instance_extra_specs:pinned": false
|
|
id:
|
|
description: Flavor ID.
|
|
returned: success
|
|
type: str
|
|
sample: "515256b8-7027-4d73-aa54-4e30a4a4a339"
|
|
is_disabled:
|
|
description: Whether the flavor is disabled
|
|
returned: success
|
|
type: bool
|
|
sample: true
|
|
is_public:
|
|
description: Make flavor accessible to the public.
|
|
returned: success
|
|
type: bool
|
|
sample: true
|
|
name:
|
|
description: Flavor name.
|
|
returned: success
|
|
type: str
|
|
sample: "tiny"
|
|
original_name:
|
|
description: The name of this flavor when returned by server list/show
|
|
type: str
|
|
returned: success
|
|
ram:
|
|
description: Amount of memory, in MB.
|
|
returned: success
|
|
type: int
|
|
sample: 1024
|
|
rxtx_factor:
|
|
description: |
|
|
The bandwidth scaling factor this flavor receives on the network
|
|
returned: success
|
|
type: int
|
|
sample: 100
|
|
swap:
|
|
description: Swap space size, in MB.
|
|
returned: success
|
|
type: int
|
|
sample: 100
|
|
vcpus:
|
|
description: Number of virtual CPUs.
|
|
returned: success
|
|
type: int
|
|
sample: 2
|
|
'''
|
|
|
|
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
|
|
|
|
|
class ComputeFlavorModule(OpenStackModule):
|
|
argument_spec = dict(
|
|
description=dict(),
|
|
disk=dict(type='int'),
|
|
ephemeral=dict(type='int'),
|
|
extra_specs=dict(type='dict'),
|
|
id=dict(aliases=['flavorid']),
|
|
is_public=dict(type='bool'),
|
|
name=dict(required=True),
|
|
ram=dict(type='int'),
|
|
rxtx_factor=dict(type='float'),
|
|
state=dict(default='present', choices=['absent', 'present']),
|
|
swap=dict(type='int'),
|
|
vcpus=dict(type='int'),
|
|
)
|
|
|
|
module_kwargs = dict(
|
|
required_if=[
|
|
('state', 'present', ['ram', 'vcpus', 'disk'])
|
|
],
|
|
supports_check_mode=True
|
|
)
|
|
|
|
def run(self):
|
|
state = self.params['state']
|
|
id = self.params['id']
|
|
name = self.params['name']
|
|
name_or_id = id if id and id != 'auto' else name
|
|
flavor = self.conn.compute.find_flavor(name_or_id,
|
|
get_extra_specs=True)
|
|
|
|
if self.ansible.check_mode:
|
|
self.exit_json(changed=self._will_change(state, flavor))
|
|
|
|
if state == 'present' and not flavor:
|
|
# Create flavor
|
|
flavor = self._create()
|
|
self.exit_json(changed=True,
|
|
flavor=flavor.to_dict(computed=False))
|
|
|
|
elif state == 'present' and flavor:
|
|
# Update flavor
|
|
update = self._build_update(flavor)
|
|
if update:
|
|
flavor = self._update(flavor, update)
|
|
|
|
self.exit_json(changed=bool(update),
|
|
flavor=flavor.to_dict(computed=False))
|
|
|
|
elif state == 'absent' and flavor:
|
|
# Delete flavor
|
|
self._delete(flavor)
|
|
self.exit_json(changed=True)
|
|
|
|
elif state == 'absent' and not flavor:
|
|
# Do nothing
|
|
self.exit_json(changed=False)
|
|
|
|
def _build_update(self, flavor):
|
|
return {
|
|
**self._build_update_extra_specs(flavor),
|
|
**self._build_update_flavor(flavor)}
|
|
|
|
def _build_update_extra_specs(self, flavor):
|
|
update = {}
|
|
|
|
old_extra_specs = flavor['extra_specs']
|
|
new_extra_specs = self.params['extra_specs'] or {}
|
|
if flavor['swap'] == '':
|
|
flavor['swap'] = 0
|
|
|
|
delete_extra_specs_keys = \
|
|
set(old_extra_specs.keys()) - set(new_extra_specs.keys())
|
|
|
|
if delete_extra_specs_keys:
|
|
update['delete_extra_specs_keys'] = delete_extra_specs_keys
|
|
|
|
stringified = dict([(k, str(v))
|
|
for k, v in new_extra_specs.items()])
|
|
|
|
if old_extra_specs != stringified:
|
|
update['create_extra_specs'] = new_extra_specs
|
|
|
|
return update
|
|
|
|
def _build_update_flavor(self, flavor):
|
|
update = {}
|
|
|
|
flavor_attributes = dict(
|
|
(k, self.params[k])
|
|
for k in ['ram', 'vcpus', 'disk', 'ephemeral', 'swap',
|
|
'rxtx_factor', 'is_public', 'description']
|
|
if k in self.params and self.params[k] is not None
|
|
and self.params[k] != flavor[k])
|
|
|
|
if flavor_attributes:
|
|
update['flavor_attributes'] = flavor_attributes
|
|
|
|
return update
|
|
|
|
def _create(self):
|
|
kwargs = dict((k, self.params[k])
|
|
for k in ['name', 'ram', 'vcpus', 'disk', 'ephemeral',
|
|
'swap', 'rxtx_factor', 'is_public',
|
|
'description']
|
|
if self.params[k] is not None)
|
|
|
|
# Keep for backward compatibility
|
|
id = self.params['id']
|
|
if id is not None and id != 'auto':
|
|
kwargs['id'] = id
|
|
|
|
flavor = self.conn.compute.create_flavor(**kwargs)
|
|
|
|
extra_specs = self.params['extra_specs']
|
|
if extra_specs:
|
|
flavor = self.conn.compute.create_flavor_extra_specs(flavor.id,
|
|
extra_specs)
|
|
|
|
return flavor
|
|
|
|
def _delete(self, flavor):
|
|
self.conn.compute.delete_flavor(flavor)
|
|
|
|
def _update(self, flavor, update):
|
|
flavor = self._update_flavor(flavor, update)
|
|
flavor = self._update_extra_specs(flavor, update)
|
|
return flavor
|
|
|
|
def _update_extra_specs(self, flavor, update):
|
|
if update.get('flavor_attributes'):
|
|
# No need to update extra_specs since flavor will be recreated
|
|
return flavor
|
|
|
|
delete_extra_specs_keys = update.get('delete_extra_specs_keys')
|
|
if delete_extra_specs_keys:
|
|
self.conn.unset_flavor_specs(flavor.id, delete_extra_specs_keys)
|
|
# Update flavor after extra_specs removal
|
|
flavor = self.conn.compute.fetch_flavor_extra_specs(flavor)
|
|
|
|
create_extra_specs = update.get('create_extra_specs')
|
|
if create_extra_specs:
|
|
flavor = self.conn.compute.create_flavor_extra_specs(
|
|
flavor.id, create_extra_specs)
|
|
|
|
return flavor
|
|
|
|
def _update_flavor(self, flavor, update):
|
|
flavor_attributes = update.get('flavor_attributes')
|
|
if flavor_attributes:
|
|
# Because only flavor descriptions are updateable,
|
|
# flavor has to be recreated to "update" it
|
|
self._delete(flavor)
|
|
flavor = self._create()
|
|
|
|
return flavor
|
|
|
|
def _will_change(self, state, flavor):
|
|
if state == 'present' and not flavor:
|
|
return True
|
|
elif state == 'present' and flavor:
|
|
return bool(self._build_update(flavor))
|
|
elif state == 'absent' and flavor:
|
|
return True
|
|
else:
|
|
# state == 'absent' and not flavor:
|
|
return False
|
|
|
|
|
|
def main():
|
|
module = ComputeFlavorModule()
|
|
module()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|