ansible-collections-openstack/plugins/modules/quota.py
Ben Formosa 4a8214b196 Fix regression in quota module
I suspect that the change to `update_quota_set` in openstacksdk commit
[9145dce64](https://opendev.org/openstack/openstacksdk/commit/9145dcec64)
has caused a regession in the quota module, making it not work correctly
for volume and compute quotas.

This change updates the calls to `update_quota_set` with the new
signatures.

Closes-Bug: #2068568
Change-Id: I604a8ffb08a76c20397f43c0ed3b23ddb11e53eb
2024-10-08 16:24:04 +03:00

501 lines
17 KiB
Python

#!/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.id)
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:
quotas['volume'] = self.conn.block_storage.update_quota_set(
project.id, **changes['volume'])
if 'compute' in changes:
quotas['compute'] = self.conn.compute.update_quota_set(
project.id, **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()