#!/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()