#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (c) 2016 Pason System Corporation # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) DOCUMENTATION = ''' --- module: quota short_description: Manage OpenStack Quotas author: OpenStack Ansible SIG description: - Manage OpenStack Quotas. Quotas can be created, updated or deleted using this module. A quota will be updated if matches an existing project and is present. options: backup_gigabytes: description: Maximum size of backups in GB's. type: int backups: description: Maximum number of backups allowed. type: int cores: description: Maximum number of CPU's per project. type: int fixed_ips: description: - Number of fixed IP's to allow. - Available until Nova API version 2.35. type: int floating_ips: description: Number of floating IP's to allow. aliases: [compute_floating_ips, floatingip, network_floating_ips] type: int gigabytes: description: Maximum volume storage allowed for project. type: int groups: description: Number of groups that are allowed for the project type: int injected_file_content_bytes: description: - Maximum file size in bytes. - Available until Nova API version 2.56. type: int aliases: [injected_file_size] injected_files: description: - Number of injected files to allow. - Available until Nova API version 2.56. type: int injected_file_path_bytes: description: - Maximum path size. - Available until Nova API version 2.56. type: int aliases: [injected_path_size] instances: description: Maximum number of instances allowed. type: int key_pairs: description: Number of key pairs to allow. type: int load_balancers: description: The maximum amount of load balancers you can create type: int aliases: [loadbalancer] metadata_items: description: Number of metadata items allowed per instance. type: int name: description: Name of the OpenStack Project to manage. required: true type: str networks: description: Number of networks to allow. type: int aliases: [network] per_volume_gigabytes: description: Maximum size in GB's of individual volumes. type: int pools: description: The maximum number of pools you can create type: int aliases: [pool] ports: description: Number of Network ports to allow, this needs to be greater than the instances limit. type: int aliases: [port] ram: description: Maximum amount of ram in MB to allow. type: int rbac_policies: description: Number of policies to allow. type: int aliases: [rbac_policy] routers: description: Number of routers to allow. type: int aliases: [router] security_group_rules: description: Number of rules per security group to allow. type: int aliases: [security_group_rule] security_groups: description: Number of security groups to allow. type: int aliases: [security_group] server_group_members: description: Number of server group members to allow. type: int server_groups: description: Number of server groups to allow. type: int snapshots: description: Number of snapshots to allow. type: int state: description: A value of C(present) sets the quota and a value of C(absent) resets the quota to defaults. default: present type: str choices: [absent, present] subnets: description: Number of subnets to allow. type: int aliases: [subnet] subnet_pools: description: Number of subnet pools to allow. type: int aliases: [subnetpool] volumes: description: Number of volumes to allow. type: int extends_documentation_fragment: - openstack.cloud.openstack ''' EXAMPLES = ''' - name: Fetch current project quota openstack.cloud.quota: cloud: mycloud name: demoproject - name: Reset project quota back to defaults openstack.cloud.quota: cloud: mycloud name: demoproject state: absent - name: Change number of cores and volumes openstack.cloud.quota: cloud: mycloud name: demoproject cores: 100 volumes: 20 - name: Update quota again openstack.cloud.quota: cloud: mycloud name: demo_project floating_ips: 5 networks: 50 ports: 300 rbac_policies: 5 routers: 5 subnets: 5 subnet_pools: 5 security_group_rules: 5 security_groups: 5 backup_gigabytes: 500 backups: 5 gigabytes: 500 groups: 1 pools: 5 per_volume_gigabytes: 10 snapshots: 5 volumes: 5 cores: 5 instances: 5 key_pairs: 5 metadata_items: 5 ram: 5 server_groups: 5 server_group_members: 5 ''' RETURN = ''' quotas: description: Dictionary describing the project quota. returned: Regardless if changes where made or not type: dict contains: compute: description: Compute service quotas type: dict contains: cores: description: Maximum number of CPU's per project. type: int injected_file_content_bytes: description: Maximum file size in bytes. type: int injected_files: description: Number of injected files to allow. type: int injected_file_path_bytes: description: Maximum path size. type: int instances: description: Maximum number of instances allowed. type: int key_pairs: description: Number of key pairs to allow. type: int metadata_items: description: Number of metadata items allowed per instance. type: int ram: description: Maximum amount of ram in MB to allow. type: int server_group_members: description: Number of server group members to allow. type: int server_groups: description: Number of server groups to allow. type: int network: description: Network service quotas type: dict contains: floating_ips: description: Number of floating IP's to allow. type: int load_balancers: description: The maximum amount of load balancers one can create type: int networks: description: Number of networks to allow. type: int pools: description: The maximum amount of pools one can create. type: int ports: description: Number of Network ports to allow, this needs to be greater than the instances limit. type: int rbac_policies: description: Number of policies to allow. type: int routers: description: Number of routers to allow. type: int security_group_rules: description: Number of rules per security group to allow. type: int security_groups: description: Number of security groups to allow. type: int subnet_pools: description: Number of subnet pools to allow. type: int subnets: description: Number of subnets to allow. type: int volume: description: Block storage service quotas type: dict contains: backup_gigabytes: description: Maximum size of backups in GB's. type: int backups: description: Maximum number of backups allowed. type: int gigabytes: description: Maximum volume storage allowed for project. type: int groups: description: Number of groups that are allowed for the project type: int per_volume_gigabytes: description: Maximum size in GB's of individual volumes. type: int snapshots: description: Number of snapshots to allow. type: int volumes: description: Number of volumes to allow. type: int sample: quotas: compute: cores: 150, fixed_ips: -1, floating_ips: 10, injected_file_content_bytes: 10240, injected_file_path_bytes: 255, injected_files: 5, instances: 100, key_pairs: 100, metadata_items: 128, networks: -1, ram: 153600, security_group_rules: -1, security_groups: -1, server_group_members: 10, server_groups: 10, network: floating_ips: 50, load_balancers: 10, networks: 10, pools: 10, ports: 160, rbac_policies: 10, routers: 10, security_group_rules: 100, security_groups: 10, subnet_pools: -1, subnets: 10, volume: backup_gigabytes: 1000, backups: 10, gigabytes: 1000, groups: 10, per_volume_gigabytes: -1, snapshots: 10, volumes: 10, ''' from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule from collections import defaultdict class QuotaModule(OpenStackModule): # TODO: Add missing network quota options 'check_limit', 'health_monitors', # 'l7_policies', 'listeners' to argument_spec, DOCUMENTATION and # RETURN docstrings argument_spec = dict( backup_gigabytes=dict(type='int'), backups=dict(type='int'), cores=dict(type='int'), fixed_ips=dict(type='int'), floating_ips=dict( type='int', aliases=['floatingip', 'compute_floating_ips', 'network_floating_ips']), gigabytes=dict(type='int'), groups=dict(type='int'), injected_file_content_bytes=dict(type='int', aliases=['injected_file_size']), injected_file_path_bytes=dict(type='int', aliases=['injected_path_size']), injected_files=dict(type='int'), instances=dict(type='int'), key_pairs=dict(type='int', no_log=False), load_balancers=dict(type='int', aliases=['loadbalancer']), metadata_items=dict(type='int'), name=dict(required=True), networks=dict(type='int', aliases=['network']), per_volume_gigabytes=dict(type='int'), pools=dict(type='int', aliases=['pool']), ports=dict(type='int', aliases=['port']), ram=dict(type='int'), rbac_policies=dict(type='int', aliases=['rbac_policy']), routers=dict(type='int', aliases=['router']), security_group_rules=dict(type='int', aliases=['security_group_rule']), security_groups=dict(type='int', aliases=['security_group']), server_group_members=dict(type='int'), server_groups=dict(type='int'), snapshots=dict(type='int'), state=dict(default='present', choices=['absent', 'present']), subnet_pools=dict(type='int', aliases=['subnetpool']), subnets=dict(type='int', aliases=['subnet']), volumes=dict(type='int'), ) module_kwargs = dict( supports_check_mode=True ) # Some attributes in quota resources don't exist in the api anymore, mostly # compute quotas that were simply network proxies. This map allows marking # them to be skipped. exclusion_map = { 'compute': { # 'fixed_ips', # Available until Nova API version 2.35 'floating_ips', # Available until Nova API version 2.35 'name', 'networks', # Available until Nova API version 2.35 'security_group_rules', # Available until Nova API version 2.35 'security_groups', # Available until Nova API version 2.35 # 'injected_file_content_bytes', # Available until # 'injected_file_path_bytes', # Nova API # 'injected_files', # version 2.56 }, 'network': {'name'}, 'volume': {'name'}, } def _get_quotas(self, project): quota = {} if self.conn.has_service('block-storage'): quota['volume'] = self.conn.block_storage.get_quota_set(project) else: self.warn('Block storage service aka volume service is not' ' supported by your cloud. Ignoring volume quotas.') if self.conn.has_service('network'): quota['network'] = self.conn.network.get_quota(project.id) else: self.warn('Network service is not supported by your cloud.' ' Ignoring network quotas.') quota['compute'] = self.conn.compute.get_quota_set(project.id) return quota def _build_update(self, quotas): changes = defaultdict(dict) for quota_type in quotas.keys(): exclusions = self.exclusion_map[quota_type] for attr in quotas[quota_type].keys(): if attr in exclusions: continue if (attr in self.params and self.params[attr] is not None and quotas[quota_type][attr] != self.params[attr]): changes[quota_type][attr] = self.params[attr] return changes def _system_state_change(self, project_quota_output): """ Determine if changes are required to the current project quota. This is done by comparing the current project_quota_output against the desired quota settings set on the module params. """ if self.params['state'] == 'absent': return True return bool(self._build_update(project_quota_output)) def run(self): project = self.conn.identity.find_project( self.params['name'], ignore_missing=False) # Get current quota values quotas = self._get_quotas(project) changed = False if self.ansible.check_mode: self.exit_json(changed=self._system_state_change(quotas)) if self.params['state'] == 'absent': # If a quota state is set to absent we should assume there will be # changes. The default quota values are not accessible so we can # not determine if no changes will occur or not. changed = True self.conn.compute.revert_quota_set(project) if 'network' in quotas: self.conn.network.delete_quota(project.id) if 'volume' in quotas: self.conn.block_storage.revert_quota_set(project) # Necessary since we can't tell what the default quotas are quotas = self._get_quotas(project) elif self.params['state'] == 'present': changes = self._build_update(quotas) if changes: if 'volume' in changes: self.conn.block_storage.update_quota_set( quotas['volume'], **changes['volume']) if 'compute' in changes: self.conn.compute.update_quota_set( quotas['compute'], **changes['compute']) if 'network' in changes: quotas['network'] = self.conn.network.update_quota( project.id, **changes['network']) changed = True quotas = {k: v.to_dict(computed=False) for k, v in quotas.items()} self.exit_json(changed=changed, quotas=quotas) def main(): module = QuotaModule() module() if __name__ == '__main__': main()